Skip to content

Spring框架面试题

注:(1-7)为重要,下面8-16暂时不需要掌握。

1、什么是Spring

轻量级的开源的J2EE框架。它是一个容器框架,用来装javabean(java对象),中间层框架(万能胶)

可以起一个连接作用,比如说把Struts和hibernate粘合在一起运用,可以让我们的企业开发更快、更简

Spring是一个轻量级的控制反转(IoC)和面向切面(AOP)的容器框架

--从大小与开销两方面而言Spring都是轻量级的。

--通过控制反转(IoC)的技术达到松耦合的目的

--提供了面向切面编程的丰富支持,允许通过分离应用的业务逻辑与系统级服务进行内聚性的

开发

--包含并管理应用对象(Bean)的配置和生命周期,这个意义上是一个容器。

--将简单的组件配置、组合成为复杂的应用,这个意义上是一个框架

1618035553676-8b4fa68c-cf7f-4bc8-b630-7b2e512cb628.png

2、Spring的优缺点

1.优点:

1.1Spring属于低侵入式设计,代码的污染极低。

1.2Spring的DI机制将对象之间的依赖关系交由框架处理,减低组件的耦合性。

1.3Spring提供了AOP技术,支持将一些通用任务,如安全、事务、日志、权限等进行集中式管理,从而提供更好的复用。

1.4Spring对主流的应用框架提供了集成支持。

2.缺点

使用大量的反射机制,反射机制非常占用内存。

3、Spring IOC的理解?以及他的实现原理?

容器概念、控制反转、依赖注入

ioc容器:实际上就是个map(key,value),里面存的是各种对象(在xml里配置的bean节点、@repository、@service、@controller、@component),在项目启动的时候会读取配置文件里面的bean节点,根据全限定类名使用反射创建对象放到map里、扫描到打上上述注解的类还是通过反射创建对象放到map里。这个时候map里就有各种对象了,接下来我们在代码里需要用到里面的对象时,再通过DI注入(autowired、resource等注解,xml里bean节点内的ref属性,项目启动的时候会读取xml节点ref属性,根据id注入,也会扫描这些注解,根据类型或id注入;id就是对象名)。

IOC叫做控制反转,指的是通过Spring来管理对象的创建、配置和生命周期,这样相当于把控制权交给了Spring,不需要人工来管理对象之间复杂的依赖关系,这样做的好处就是解耦。在Spring里面,主要提供了BeanFactory和ApplicationContext两种容器,通过他们来实现对Bean的管理。

IOC:将对象的创建和管理交给spring,我们只需要负责使用对象,这就是控制反转的思想

依赖注入

“获得依赖对象的过程被反转了”。控制被反转之后,获得依赖对象的过程由自身管理变为了由IOC容器主动注入。依赖注入是实现IOC的方法,就是由IOC容器在运行期间,动态地将某种依赖关系注入到对象之中。

4、Spring AOP的理解?以及他的实现原理

AOP叫做面向切面编程,它是一个编程范式,目的就是提高代码的模块性。Spring AOP基于动态代理的方式实现,如果实现了接口的话就会使用JDK动态代理,反之则使用CGLIB代理,Spring中AOP的应用主要体现在事务、日志、异常处理等方面,通过在代码前后做一些增强的处理,可以实现对业务逻辑的隔离,提高代码的模块化功能,同时也是解耦。Spring主要提供了Aspect切面、JoinPoint连接点、PointCut切入点、Advice增强等实现方式。

AOP将程序中的交叉业务逻辑(比如安全,日志,事务等),封装成一个切面,然后注入到目标对象(具体业务逻辑)中去。AOP可以对某个对象或某些对象的功能进行增强,比如对象中的方法进行增强,可以在执行某个方法之前额外的做一些事情,在某个方法执行之后额外的做一些事情

5.spring事务的传播机制

多个事务方法相互调用时,事务如何在这些方法间传播

方法A是一个事务的方法,方法A执行过程中调用了方法B,那么方法B有无事务以及方法B对事务的要求不同都

会对方法A的事务具体执行造成影响,同时方法A的事务对方法B的事务执行也有影响,这种影响具体是什么就

由两个方法所定义的事务传播类型所决定。

REQUIRED(Spring默认的事务传播类型):如果当前没有事务,则自己新建一个事务,如果当前存在事务,则加入这个事务。

SUPPORTS:当前存在事务,则加入当前事务,如果当前没有事务,就以非事务方法执行。

MANDATORY:当前存在事务,则加入当前事务,如果当前事务不存在,则抛出异常。

REQUIRES_NEW:创建一个新事务,如果存在当前事务,则挂起该事务。

NOT_SUPPORTED:以非事务方式执行,如果当前存在事务,则挂起当前事务

NEVER:不使用事务,如果当前事务存在,则抛出异常

NESTED:如果当前事务存在,则在嵌套事务中执行,否则REQUIRED的操作一样(开启一个事务)

和REQUIRES_NEW的区别

REQUIRES_NEW是新建一个事务并且新开启的这个事务与原有事务无关,而NESTED则是当前存在事务时(我

们把当前事务称之为父事务)会开启一个嵌套事务(称之为一个子事务)。 在NESTED情况下父事务回滚时,

子事务也会回滚,而在REQUIRES_NEW情况下,原有事务回滚,不会影响新开启的事务。

和REQUIRED的区别

REQUIRED情况下,调用方存在事务时,则被调用方和调用方使用同一事务,那么被调用方出现异常时,由于

共用一个事务,所以无论调用方是否catch其异常,事务都会回滚 而在NESTED情况下,被调用方发生异常

时,调用方可以catch其异常,这样只有子事务回滚,父事务不受影响

6、Spring容器的启动流程

7、Spring基于xml注入bean的几种方式

1.构造方式注入

2.set方法注入

3.自动装载

4.注解

1.构造方法注入

构造方法注入是指在构造方法中注入属性或者对象来实现依赖注入,

如下所示,在标签中定义一个id为userDaoImpl的Bean,并通过注入了name为username,value为admin的值,注入完成后直接通过this.username获取到值admin。其中引用类型使用ref属性,基本类型使用value属性。

java
public class UserServiceImpl {

    private String username;

    public UserServiceImpl(String username) {

        this.username = username;

    }
}
java
<bean id="UserServiceImpl" class="com.example.UserServiceImpl">
    <constructor-arg name="username" value="admin"></constructor-arg>
</bean>

2.Set方法注入

set方法注入就是通过在类中实现get、set方法来实现属性或者对象的依赖注入,

如下所示,在标签中定义一个id为UserServiceImpl的Bean,并通过注入了name为username,value为admin的值,注入完成后直接通过getUsername()获取到值admin。

java
   private String username;

   public String getUsername() {
        return username;
    }

   public void setUsername(String username) {
        this.username = username;
    }
}
java
<bean id="UserServiceImpl" class="com.example.UserServiceImpl">
    <property name="username" value="admin"></property>
</bean>

3.自动装配

Spring提供了自动装配的功能,简化了我们的配置,自动装配默认是不打开的,常用的方式有两种: byName:通过参数名自动装配,如下所示,id为userService的的autowire被设置为byName后,IOC容器会通过名字来自动装配,发现UserService类中有个叫userDao的属性,然后看看IOC容器中有没有id为userDao的,如果有就装配进去。

java
<bean id="userDao" class="com.example.UserDao"></bean>
<bean id="userService" class="com.example.UserService" autowire="byName"/>

byType:通过参数类型自动装配,当autowire被设置为byType后,IOC容器会看看里面有没有UserDao类型的,有就装配进去。

java
<bean id="userDao" class="com.example.UserDao"></bean>
<bean id="userService" class="com.example.UserService" autowire="byType"/>

4.注解

@Autowired注解可以实现自动装配,只要在对应的属性上标记该注解,但是@Autowired注解只按照byType注入

java
public class UserController {

    @Autowired
    private IUserService userService;

}

@Resource注解可以实现自动装配,它有两个重要属性name和type,name属性解析为bean的名字,type属性则解析为bean的类型。所以如果使用name属性,则使用byName的自动注入策略,而使用type属性则使用byType自动注入策略。如果既不指定name也不指定type属性,这时将通过反射机制使用byName自动注入策略。

@Autowired注解和@Resource注解的作用相同,只不过@Autowired按照byType注入,如果@Autowired想使用名称可以结合@Qualifier注解进行使用。

8、Spring 框架中都用到了哪些设计模式

1.工厂模式: 在各种BeanFactory以及ApplicationContext创建中都用到了;

2.模版模式: 这个也很明显,在各种BeanFactory以及ApplicationContext实现中也都用到了;

3.代理模式: 在Aop实现中用到了JDK的动态代理;

4.单例模式: 比如在创建bean的时候。

5.Tomcat中有很多场景都使用到了外观模式,因为Tomcat中有很多不同的组件,每个组件需要相互通信,但又不能将自己内部数据过多地暴露给其他组件。用外观模式隔离数据是个很好的方法。

6.策略模式在Java中的应用,这个太明显了,因为Comparator这个接口简直就是为策略模式而生的。

7.原型模式:使用原型模式创建对象比直接new一个对象在性能上好得多,因为Object类的clone()方法是一个native方法,它直接操作内存中的二进制流,特别是复制大对象时,性能的差别非常明显。

8.迭代器模式:Iterable接口和Iterator接口** **这两个都是迭代相关的接口,可以这么认为,实现了Iterable接口,则表示某个对象是可被迭代的;Iterator接口相当于是一个迭代器,实现了Iterator接口,等于具体定义了这个可被迭代的对象时如何进行迭代的

9、BeanFactory和ApplicationContext有什么区别?

BeabFactory可以理解为含有bean集合的工厂类。BeanFactory包含了多种bean的定义,以便在接收到客户请求时将对应的bean实例化。

BeanFactory还能在实例化对象的时候生成协作类之间的关系。此举将bean自身与bean客户端的配置中解放出来。BeanFactory还包含了bean生命周期的控制,调用客户端的初始化方法(initialization methods)和销毁方法(destruction methods)。

从表面上看,applicationcontext如同beanfactory一样具有bean定义,bean关联关系的设置,根据请求分发bean的功能。但applicationcontext在此基础上还提供了其他功能。

1.提供了支持国际化的文本消息。

2.统一的资源文件读取方式。

3.事件监听。

BeanFactoryApplicationContext
使用的是懒加载使用及时加载
它使用语法显示提供资源对象它自己创建和管理资源对象
不支持国际化支持国际化
不支持基于依赖的注解支持基于依赖的注解

10、Spring框架中的Bean是线程安全的么?如果线程不安全,那么如何处理?

Spring容器本身没有提供Bean的线程安全策略,因此可以说Spring容器中的bean并不是线程安全的

Spring的作用域(scope):

  • singleton:单例,默认作用域。
  • prototype:原型,每次创建一个新对象。
  • request请求, 每次Http请求创建一个新对象,适用于WebApplicationContext环境下。
  • session: 会话,同一个会话共享一个实例,不同会话使用不用的实例。
  • global:-session全局会话,所有会话公享一个实例。

线程安全问题

  1. 对于prototype作用域,每次都生成一个新的对象,所以不存在线程安全问题。
  2. 对于singleton作用域,默认就是线程安全的。但是对于开发中的大部分的bean,其实都是无状态的,不需要保证线程安全。

无状态:表示这个对象没有属性对象,就是指没有实例变量的对象,不能保存数据,是不变类,是线程安全的。比如:controller、service、dao

有状态:表示实例是有属性的对象,可以保存数据,是线程不安全的;比如:pojo

但是如果要保证线程安全,可以将bean的作用域改为prototype,比如像Model View

另外还可以采用ThreadLocal来解决线程安全问题。ThreadLocal为每个线程保存一个副本变量。每个线程只操作自己的副本变量。

ThreadLocal和线程同步机制都是为了解决多线程中相同变量的访问冲突问题。同步机制采用了"时间换空间"的方式,仅提供一份变量,不同的线程在访问前需要获取锁,没获得锁的线程则需要排队。而ThreadLocal采用了“空间换时间”的方式。ThreadLocal会为每一个线程提供一个独立的变量副本,从而隔离了多个线程对数据的访问冲突。因为每一个线程都拥有自己的变量副本,从而也就没有必要对该变量进行同步了。

11、Spring事务的实现方式和实现原理

Spring 事务的本质其实就是数据库对事务的支持,没有数据库的事务支持,spring 是无法提供事务功能的。真正的数据库层的事务提交和回滚是通过binlog 或者 redo log 实现的。

事务的实现方式(编程式事务管理、编程式事务管理)

  • 编程式事务管理对基于POJO的应用来说是唯一的选择。我们需要在代码中调用beginTransaction()、commit()、rollback()等事务管理相关方法,这就是编程式事务管理。
  • 基于TransactionProxyFactoryBean的声明式事务管理。
  • 基于Transactional的声明式事务管理
  • 基于Aspectj AOP配置事务。

注:此处侧重讲解声明式事务,编程式事务在实际开发中得不到广泛使用,仅供学习参考。

12、Spring中有哪些注解,谈谈你对他的理解

1.用于创建对象

相当于:<bean id="" class="">

1.@Component(把xx类注入到spring容器中)

   作用:

           把资源让给spring来管理。相当于在xml中配置了一个bean。

   属性:

           value:指定bean的id。如果不指定value属性,默认的id是当前类的类名。首字母小写

例:@Component(value="userService")

2.@Controller @Service @Repository

他们三个注解都是针对一个衍生注解,他们的作用及属性都是一样的,他们只不过是提供了更加明确的语义化。

@Controller:一般用于表现层注解。

@Service:一般用于业务层的注解。

@Repository:一般用于持久层的注解。(dao层)

细节:如果注解中有且只有一个属性要赋值时,且名称是value,value在赋值时可以不写。

2.用于注入数据的

相当于:<property name="" ref="">

         &lt;property name="" value=""&gt;

1.@Autowired

作用:自动按照类型注入。当使用注解注入属性时,set方法可以省略。它只能注入其他bean类型。当有多个类型匹配时,使用要注入的对象变量名称作为bean的id,在spring容器查找,找到了也可以注入成功,找不到就报错。

例:@Autowired作用在属性上:(最常用)

java
@Autowired
private Student student;

2.@Qualifier

作用:

在自动按照类型注入的基础上,再按照Bean的id注入。它在给字段注入时不能独立使用,必须和@Autowired一起使用;但是给方法参数注入时,可以独立使用。

属性:

value:指定bean的id。

3.@Resource

作用:

直接按照bean的id注入。他也只能注入其他的bean类型。

属性:

name:指定bean的id。

4.@Value

作用:

注入基本数据类型和String类型数据的。

属性:

value:用于指定值

例:

1645428144064-ebc7f2cc-4aa7-4b30-90e3-11781c4fb15f.png

1645428161944-6d8d4064-3199-433f-bcd4-4a6321be400d.png

3.用于改变作用范围:

相当于:<bean id="" class="" scope="">

1.@Scope

作用:

指定bean的作用范围。

属性:

value:指定范围的值。

取值:singleton prototype request session globalsession

目的是用来调节作用域。

java
//多实例,ioc容器启动并不会去调用方法创建对象放到IOC容器中,每次获取的时候才会调用方法创建对象
@Scope("prototype")
//单实例的(默认的):ioc容器启动会调用方法创建对象放到ioc容器中,以后每次获取就是直接从容器(map.get())中拿
@Scope("singleton")
//同一个请求创建一个实例
@Scope("request")
//同一个session创建一个实例
@Scope("session")

4.@Requestmapping

@Requestmapping注解可以将HTTP请求映射给controller来处理,包括返回视图页面的controller和Rest服务的controller。

例:

java
@Controller
@RequestMapping(value = "/admin", method = RequestMethod.GET)
public class AdminController {

    @RequestMapping(value = "/adminPage", method = RequestMethod.GET)
    public String toAdminPage() {
        return "admin";
    }

    @RequestMapping(value = "/contact", method = RequestMethod.GET)
    public String toContactForOhersPage() {
        return "contact";
    }
}

则:请求路径为:ip:端口号/项目名称/具体方法映射路径。

注解解释
@Controller组合注解(组合了@Component注解),应用在MVC层(控制层),DispatcherServlet会自动扫描注解了此注解的类,然后将web请求映射到注解了@RequestMapping的方法上。
@Service组合注解(组合了@Component注解),应用在service层(业务逻辑层)
@Reponsitory组合注解(组合了@Component注解),应用在dao层(数据访问层)
@Component表示一个带注释的类是一个“组件”,成为Spring管理的Bean。当使用基于注解的配置和类路径扫描时,这些类被视为自动检测的候选对象。同时@Component还是一个元注解。
@AutowiredSpring提供的工具(由Spring的依赖注入工具(BeanPostProcessor、BeanFactoryPostProcessor)自动注入。)
@ResourceJSR-250提供的注解
@InjectJSR-330提供的注解
@Configuration声明当前类是一个配置类(相当于一个Spring配置的xml文件)
@ComponentScan自动扫描指定包下所有使用@Service,@Component,@Controller,@Repository的类并注册
@Bean注解在方法上,声明当前方法的返回值为一个Bean。返回的Bean对应的类中可以定义init()方法和destroy()方法,然后在@Bean(initMethod=”init”,destroyMethod=”destroy”)定义,在构造之后执行init,在销毁之前执行destroy。
@Aspect声明一个切面(就是说这是一个额外功能)
@After后置建言(advice),在原方法前执行。
@Before前置建言(advice),在原方法后执行。
@Around环绕建言(advice),在原方法执行前执行,在原方法执行后再执行(@Around可以实现其他两种advice)
@PointCut声明切点,即定义拦截规则,确定有哪些方***被切入
@Transactional声明事务(一般默认配置即可满足要求,当然也可以自定义)
@Cacheable声明数据缓存
@EnableAspectJ***开启Spring对AspectJ的支持
@Value值得注入。经常与Sping EL表达式语言一起使用,注入普通字符,系统属性,表达式运算结果,其他Bean的属性,文件内容,网址请求内容,配置文件属性值等等
@PropertySource指定文件地址。提供了一种方便的、声明性的机制,用于向Spring的环境添加PropertySource。与@configuration类一起使用。
@PostConstruct标注在方法上,该方法在构造函数执行完成之后执行。
@PreDestroy标注在方法上,该方法在对象销毁之前执行。
@Profile表示当一个或多个指定的文件是活动的时,一个组件是有资格注册的。使用@Profile注解类或者方法,达到在不同情况下选择实例化不同的Bean。@Profile(“dev”)表示为dev时实例化。
@EnableAsync开启异步任务支持。注解在配置类上。
@Async注解在方法上标示这是一个异步方法,在类上标示这个类所有的方法都是异步方法。
@EnableScheduling注解在配置类上,开启对计划任务的支持。
@Scheduled注解在方法上,声明该方法是计划任务。支持多种类型的计划任务:cron,fixDelay,fixRate
@Conditional根据满足某一特定条件创建特定的Bean
@Enable*通过简单的@Enable来开启一项功能的支持。所有@Enable注解都有一个@Import注解,@Import是用来导入配置类的,这也就意味着这些自动开启的实现其实是导入了一些自动配置的Bean(1.直接导入配置类2.依据条件选择配置类3.动态注册配置类)
@RunWith这个是Junit的注解,springboot集成了junit。一般在测试类里使用:@RunWith(SpringJUnit4ClassRunner.class) — SpringJUnit4ClassRunner在JUnit环境下提供Sprng TestContext Framework的功能
@ContextConfiguration用来加载配置ApplicationContext,其中classes属性用来加载配置类:@ContextConfiguration(classes = {TestConfig.class(自定义的一个配置类)})
@ActiveProfiles用来声明活动的profile–@ActiveProfiles(“prod”(这个prod定义在配置类中))
@EnableWebMvc用在配置类上,开启SpringMvc的Mvc的一些默认配置:如ViewResolver,MessageConverter等。同时在自己定制SpringMvc的相关配置时需要做到两点:1.配置类继承WebMvcConfigurerAdapter类2.就是必须使用这个@EnableWebMvc注解。
@RequestMapping用来映射web请求(访问路径和参数),处理类和方法的。可以注解在类和方法上,注解在方法上的@RequestMapping路径会继承注解在类上的路径。同时支持Serlvet的request和response作为参数,也支持对request和response的媒体类型进行配置。其中有value(路径),produces(定义返回的媒体类型和字符集),method(指定请求方式)等属性。
@ResponseBody将返回值放在response体内。返回的是数据而不是页面
@RequestBody允许request的参数在request体中,而不是在直接链接在地址的后面。此注解放置在参数前。
@PathVariable放置在参数前,用来接受路径参数。
@RestController组合注解,组合了@Controller和@ResponseBody,当我们只开发一个和页面交互数据的控制层的时候可以使用此注解。
@ControllerAdvice用在类上,声明一个控制器建言,它也组合了@Component注解,会自动注册为Spring的Bean。
@ExceptionHandler用在方法上定义全局处理,通过他的value属性可以过滤拦截的条件:@ExceptionHandler(value=Exception.class)–表示拦截所有的Exception。
@ModelAttribute将键值对添加到全局,所有注解了@RequestMapping的方法可获得次键值对(就是在请求到达之前,往model里addAttribute一对name-value而已)。
@InitBinder通过@InitBinder注解定制WebDataBinder(用在方法上,方法有一个WebDataBinder作为参数,用WebDataBinder在方法内定制数据绑定,例如可以忽略request传过来的参数Id等)。
@WebAppConfiguration一般用在测试上,注解在类上,用来声明加载的ApplicationContext是一个WebApplicationContext。他的属性指定的是Web资源的位置,默认为src/main/webapp,我们可以修改为:@WebAppConfiguration(“src/main/resources”)。
@EnableAutoConfiguration此注释自动载入应用程序所需的所有Bean——这依赖于Spring Boot在类路径中的查找。该注解组合了@Import注解,@Import注解导入了EnableAutoCofigurationImportSelector类,它使用SpringFactoriesLoader.loaderFactoryNames方法来扫描具有META-INF/spring.factories文件的jar包。而spring.factories里声明了有哪些自动配置。
@SpingBootApplicationSpringBoot的核心注解,主要目的是开启自动配置。它也是一个组合注解,主要组合了@Configurer,@EnableAutoConfiguration(核心)和@ComponentScan。可以通过@SpringBootApplication(exclude={想要关闭的自动配置的类名.class})来关闭特定的自动配置。
@ImportResource虽然Spring提倡零配置,但是还是提供了对xml文件的支持,这个注解就是用来加载xml配置的。例:@ImportResource({“classpath
@ConfigurationProperties将properties属性与一个Bean及其属性相关联,从而实现类型安全的配置。例:@ConfigurationProperties(prefix=”authot”,locations={“classpath
@ConditionalOnBean条件注解。当容器里有指定Bean的条件下。
@ConditionalOnClass条件注解。当类路径下有指定的类的条件下。
@ConditionalOnExpression条件注解。基于SpEL表达式作为判断条件。
@ConditionalOnJava条件注解。基于JVM版本作为判断条件。
@ConditionalOnJndi条件注解。在JNDI存在的条件下查找指定的位置。
@ConditionalOnMissingBean条件注解。当容器里没有指定Bean的情况下。
@ConditionalOnMissingClass条件注解。当类路径下没有指定的类的情况下。
@ConditionalOnNotWebApplication条件注解。当前项目不是web项目的条件下。
@ConditionalOnResource条件注解。类路径是否有指定的值。
@ConditionalOnSingleCandidate条件注解。当指定Bean在容器中只有一个,后者虽然有多个但是指定首选的Bean。
@ConditionalOnWebApplication条件注解。当前项目是web项目的情况下。
@EnableConfigurationProperties注解在类上,声明开启属性注入,使用@Autowired注入。例:@EnableConfigurationProperties(HttpEncodingProperties.class)。
@AutoConfigureAfter在指定的自动配置类之后再配置。例:@AutoConfigureAfter(WebMvcAutoConfiguration.class)

13、Spring 框架中bean生命周期?

  1. Spring启动,查找并加载需要被Spring管理的bean,进行Bean的实例化
  2. Bean实例化后对将Bean的引入和值注入到Bean的属性中
  3. 如果Bean实现了BeanNameAware接口的话,Spring将Bean的Id传递给setBeanName()方法
  4. 如果Bean实现了BeanFactoryAware接口的话,Spring将调用setBeanFactory()方法,将BeanFactory容器实例传入
  5. 如果Bean实现了ApplicationContextAware接口的话,Spring将调用Bean的setApplicationContext()方法,将bean所在应用上下文引用传入进来。
  6. 如果Bean实现了BeanPostProcessor接口,Spring就将调用他们的postProcessBeforeInitialization()方法。
  7. 如果Bean 实现了InitializingBean接口,Spring将调用他们的afterPropertiesSet()方法。类似的,如果bean使用init-method声明了初始化方法,该方法也会被调用
  8. 如果Bean 实现了BeanPostProcessor接口,Spring就将调用他们的postProcessAfterInitialization()方法。
  9. 此时,Bean已经准备就绪,可以被应用程序使用了。他们将一直驻留在应用上下文中,直到应用上下文被销毁。
  10. 如果bean实现了DisposableBean接口,Spring将调用它的destory()接口方法,同样,如果bean使用了destory-method 声明销毁方法,该方法也会被调用。

14.Spring声明式事务在哪些情况下会失效?

编程式事务

在Spring中事务管理的方式有两种,编程式事务和声明式事务。下面是两种事务的实现方式

配置类

java
@Configuration
	@EnableTransactionManagement
	@ComponentScan("com.javashitang")
	public class AppConfig {
		@Bean
	    public DruidDataSource dataSource() {
	        DruidDataSource ds = new DruidDataSource();
	        ds.setDriverClassName("com.mysql.jdbc.Driver");
	        ds.setUrl("jdbc:mysql://localhost:3306/test?
	                  characterEncoding=utf8&useSSL=true");
	                  ds.setUsername("test");
	                  ds.setPassword("test");
	                  ds.setInitialSize(5);
	                  return ds;
	                  }

		@Bean
		public DataSourceTransactionManager dataSourceTransactionManager() {
			return new DataSourceTransactionManager(dataSource());
		}

		@Bean
		public JdbcTemplate jdbcTemplate(DataSource dataSource) {
			return new JdbcTemplate(dataSource);
		}

		@Bean
		public TransactionTemplate transactionTemplate() {
			return new TransactionTemplate(dataSourceTransactionManager());
		}
	}
java
public interface UserService {
		void addUser(String name, String location);

		default void doAdd(String name) {
		};
	}
java
@Service
	public class UserServiceV1Impl implements UserService {
		@Autowired
		private JdbcTemplate jdbcTemplate;
		@Autowired
		private TransactionTemplate transactionTemplate;

		@Override
		public void addUser(String name, String location) {
			transactionTemplate.execute(new TransactionCallbackWithoutResult() {
				@Override
				protected void doInTransactionWithoutResult(TransactionStatus status) {
					try {
						String sql = "insert into user (`name`) values (?)";
						jdbcTemplate.update(sql, new Object[] { name });
						throw new RuntimeException("保存⽤户信息失败");
					} catch (Exception e) {
						e.printStackTrace();
						status.setRollbackOnly();
					}
				}
			});
		}
	}

可以看到编程式事务的方式并不优雅,因为业务代码和事务代码耦合到一起,当发生异常的时候还需要手动回滚事务(比使用JDBC方便很多,JDBC得先关闭自动提交,然后根据情况手动提交或者回滚事务。)。

如果让你优化事务方法的执行?你会怎么做?

其实我们完全可以用AOP来优化这种代码,设置好切点,当方法执行成功时提交事务,当方法发生异常时回滚事务,这就是声明式事务的实现原理。

使用AOP后,当我们调用事务方法时,会调用到生成的代理对象,代理对象中加入了事务提交和回滚的逻辑。

声明式事务

Spring aop动态代理的方式有如下几种方法:

  1. JDK动态代理实现(基于接口)(JdkDynamicAopProxy)
  2. CGLIB动态代理实现(动态生成子类的方式)(CGLIBAopProxy)
  3. AspectJ适配实现

Spring aop默认只会使用JDK和CGLIB来生成代理对象

@Transactional可以用在哪里

@Transactional可以用在类、方法、接口上

  1. 用在类上,该类的所有public方法都具备有事务
  2. 用在方法上,方法具有事务。当类和方法同时配置事务的时候,方法属性会覆盖类的属性
  3. 用在接口上,一般不建议这样使用,因为只有基于接口的代理会生校,如果spring AOP使用cglib来实现动态代理,会导致事务失效(因为注解不能继承)

@Transactional 失效的场景

  1. @Transactional 注解应用到非public方法(除非特殊配置,例如使用AspectJ静态织入实现AOP)

为什么只有public⽅法上的@Transactional注解才会⽣效?

⾸相JDK动态代理肯定只能是public,因为接⼝的权限修饰符只能是public。cglib代理的⽅式是可以代理protected⽅法的(private不⾏哈,⼦类访问不了⽗类的private⽅法)如果⽀持protected,可能会造成当切换代理的实现⽅式时表现不同,增⼤出现bug的可能醒,所以统⼀⼀下。如果想让⾮public⽅法也⽣效,你可以考虑使⽤AspectJ

  1. 自调用,因为@Transactional 是基于动态代理实现的

当自调用时,方法执行不会经过代理对象,所以会导致事务失效

java
// 事务失效
	@Service
	public class UserServiceV2Impl implements UserService {
		@Autowired
		private JdbcTemplate jdbcTemplate;

		@Override
		public void addUser(String name, String location) {
			doAdd(name);
		}

		@Transactional
		public void doAdd(String name) {
			String sql = "insert into user (`name`) values (?)";
			jdbcTemplate.update(sql, new Object[] { name });
			throw new RuntimeException("保存⽤户失败");
		}
	}

可以通过如下方式解决

- @Autowired  注入自己,假如为self,然后通过self调用方法
- @Autowired  ApplicationContext,从ApplicationContext通过getBean获取自己
java
// 事务⽣效
	@Service
	public class UserServiceV2Impl implements UserService {
		@Autowired
		private JdbcTemplate jdbcTemplate;
		@Autowired
		private UserService userService;

		@Override
		public void addUser(String name, String location) {
			userService.doAdd(name);
		}

		@Override
		@Transactional
		public void doAdd(String name) {
			String sql = "insert into user (`name`) values (?)";
			jdbcTemplate.update(sql, new Object[] { name });
			throw new RuntimeException("保存⽤户失败");
		}
	}
  1. 异常在代码中被你自己try catch了

这个逻辑从源码理解比较清晰,只有当执行事务抛出异常才能进入completeTransationAfterThrowing方法,这个方法里面有回滚的逻辑,如果事务方法都没有抛出异常就只会正常提交。

java
//org.springframework.transaction.interceptor.TransactionAspectSupport#invokeWithinTransa
ction
try {
// This is an around advice: Invoke the next interceptor in the chain.
// This will normally result in a target object being invoked.
// 执⾏事务⽅法
retVal = invocation.proceedWithInvocation();
}
catch (Throwable ex) {
// target invocation exception
completeTransactionAfterThrowing(txInfo, ex);
throw ex;
}
finally {
cleanupTransactionInfo(txInfo);
}
  1. 异常类型不正确,默认只支持RuntimeException和error,不支持检查异常

异常体系图如下。当抛出检查异常时,spring事务不会回滚。如果抛出任何异常都回滚,可以配置rollbackFor为Exception

java
@Transactional(rollbackFor = Exception.class)

1649744588611-8897f79f-d0f4-43cc-a893-a548c66d41be.png

15.Spring是怎么解决循环依赖的?

首先。Spring解决循环依赖有两个前提条件:

  1. 不全是构造器方式的循环依赖
  2. 必须是单例

基于上面的问题,我们知道Bean的生命周期,本质上解决循环依赖的问题就是三级缓存,通过三级缓存提前拿到未初始化的对象。

第一级缓存:用来保存实例化、初始化都完成的对象

第二级缓存:用来保存实例化完成,但是未初始化完成的对象

第三级缓存:用来保存一个对象工厂,提供一个匿名内部类,用于创建二级缓存中的对象

1650504806330-cec1da23-0811-4c11-934f-11a654927382.png

假设一个简单的循环依赖场景,A、B互相依赖。

1650504861226-2d0d40fb-bc60-4499-973d-5951a3fe9433.png

A对象的创建过程:

  1. 创建对象A,实例化的时候把A对象工厂放入三级缓存

1650504966891-fac5ce1a-109a-4f92-9985-042d02cc0135.png

  1. A注入属性时,发现依赖B,转而去实例化B
  2. 同样创建对象B,注入属性时发现依赖A,依次从一级缓存到三级缓存查询A,从三级缓存通过对象工厂拿到A,把A放入二级缓存,同时删除三级缓存中的A,此时,B已经实例化并且初始化完成,把B放入一级缓存。

1650505228854-c4e0ce33-7e7c-48f8-ab6e-5a33022ddda3.png

  1. 接着继续创建A,顺利从一级缓存拿到实例化且初始化完成的B对象,A对象创建也完成,删除二级缓存中的A,同时把A放入一级缓存
  2. 最后,一级缓存中保存着实例化、初始化都完成的A、B对象

1650505483524-3e4ea5a7-3985-44ec-b910-c9fa5b2d0665.png

由于把实例化和初始化的流程分开了,所以如果都是用构造器的话,就没法分离这个操作,所以都是构造器的话就无法解决循环依赖的问题了。

为什么要三级缓存?二级缓存不行吗?

不可以,主要是为了生成代理对象。

因为三级缓存中放的是生成具体对象的匿名内部类,他可以生成代理对象,也可以生成普通的实例对象。

使用三级缓存主要是为了保证不管什么时候使用的都是同一个对象。

假设只有二级缓存的情况,往二级缓存中放显示一个普通的Bean对象,BeanPostProcessor去生成代理对象后,覆盖掉二级缓存中的普通Bean对象,那么多线程环境下可能取到的对象就不一致了。

1650505861441-17db450b-4136-4732-85a4-3653147712f2.png

最近更新