目录
1.Spring 是一个轻量级的 IOC/DI 和 AOP 容器的开源框架。使用Spring框架的好处是什么?
2.什么是IOC(控制反转)思想?Spring中关于IOC思想的具体方式DI(依赖注入)?这篇文章
3.Spring中核心组件Bean、Context、Core
4. ApplicationContext常见的实现是什么?
Spring Context初始化流程看这里和这里(看源码分析每一步做的事,哪些步骤是可以通过bean重写方法自定义的)
Bean的构造方法、@PostConstruct注解、InitializingBean、init-method的执行顺序是怎样的?
10.Spring中Placeholder动态替换发生的时间:
13.Spring中核心接口和类:ApplicationContext、BeanFactory、BeanWrapper、FactoryBean
14.Spring中bean的作用域Scope?singleton、prototype、session、request看这里
11.Spring如何解决循环依赖:bean作用域为singleton单例看这里
基础概念
1.Spring 是一个轻量级的 IOC/DI 和 AOP 容器的开源框架。使用Spring框架的好处是什么?
轻量级、容器化、控制反转、面向切面编程、事务管理、异常处理、可以集成多种优秀的框架。
2.什么是IOC(控制反转)思想?Spring中关于IOC思想的具体方式DI(依赖注入)?这篇文章
- IOC(inverse of control):控制反转
对象的创建以及依赖关系可以由spring完成和注入,即,在应用程序中对象的创建、销毁等不再由程序本身的编码实现,而是由外部的Spring容器在程序运行时根据需要注入到程序中,也就是对象的生命周期不是由程序本身来决定,而是由容器来控制,所以称之为控制反转。- DI,依赖注入(Dependency Injection)
1.Spring实现IoC思想是通过DI实现
2.注入方式:这篇文章
使用Setter方法注入(需要加注解@Autowired等,或者自己创建Bean,调用该Setter方法设进去);
构造器注入 (需要加注解@Autowired等,或自己创建Bean,放入构造器中创建);
此外,当时用@Autowire注解时,没有set方法,Spring依然可以通过反射的方式完成注入。
3.Spring中核心组件Bean、Context、Core
所有被 Spring 管理的、由 Spring 创建的、用于依赖注入的对象,就叫作一个 Bean。Spring 创建并完成依赖注入后,所有 Bean 统一放在一个叫作 Context 的上下文中进行管理。Core主要是为context在管理Spring中bean与bean之间关系时为其服务的,换句话说Core就是为Spring管理bean提交工具的一个工具类。
4. ApplicationContext常见的实现是什么?
FileSystemXmlApplicationContext:从绝对路径中加载xml配置文件
ClassPathXmlApplicationContext:从项目的编译路径下加载xml
XmlWebApplicationContext:从项目的WEB-INF路径下加载xml
Spring Context初始化流程看这里和这里(看源码分析每一步做的事,哪些步骤是可以通过bean重写方法自定义的)
Spring Context初始化主要是在AbstractApplicationContext的refresh方法里完成,大部分的Bean实例化也是在这个方法中完成的(少量懒加载的要在时用的时候才会实例化),主要流程如下:
- 生成BeanFactory
- 注入实现了BeanFactoryPostProcessor接口的类
- 执行实现了BeanDefinitionRegistryPostProcessor接口的方法完成BeanDefinition的生成,这个接口继承了BeanFactoryPostProcessor
- 执行剩下的实现了BeanFacotryPostProcessor接口的方法,完成一些后置处理
- 添加所有的实现了BeanPostProcessor接口的类
- 添加ApplicationListener
- 最后完成Bean的实例化,在Bean的实例化的过程中会调用上面添加的BeanPostProcessor接口的类的前置和后置处理方法。
因此在实际开发中,如果需要扩展功能,想在Bean实例化或实例化之前做一些处理,需要通过实现BeanDefinitionRegistryPostProcessor、BeanFactoryPostProcessor、BeanPostProcessor这三个接口来达到目的。
关于spring中的bean看这里
Bean的生命周期:
(1)调用 Bean 的构造方法创建 Bean;
(2)对Bean进行属性的依赖注入;
(3)如果实现 BeanNameAware 接口的话,会设置 Bean 的 name;
(4)如果实现了 BeanFactoryAware,会把 BeanFactory 设置给 Bean;
(5)如果实现了 ApplicationContextAware,会给 Bean 设置 ApplictionContext;
(6)如果实现了 BeanPostProcessor 接口,则执行前置处理方法;
(7)如果使用@PostConstruct注解,则执行使用了该注解的方法
(8)实现了 InitializingBean 接口的话,执行 afterPropertiesSet 方法;
(9)执行自定义的 init-method 方法;
(10)执行 BeanPostProcessor 接口的后置处理方法。
使用完 Bean 需要销毁时,会先执行 DisposableBean 接口的 destroy 方法,然后在执行自定义的 destroy 方法。
Bean的构造方法、@PostConstruct注解、InitializingBean、init-method的执行顺序是怎样的?
构造方法->PostConstruct->InitializingBean->init-method,注意如果有类实现了BeanPostProcessor接口的前置处理方法,会先执行前置处理方法然后才会执行@PostConstruct注解的方法
说一下循环依赖机制?
判断方法是利用BeanFactory实现类的一个ThreadLocal类型的变量prototypesCurrentlyInCreation,该变量会保存正在创建的prototype类型的Bean,假设:现在A依赖B,B依赖A,那么在创建A的时候会将A放入prototypesCurrentlyInCreation中,而由于依赖B,则又会马上去创建B,B也被放入prototypesCurrentlyInCreation中,而由于B依赖于A,则又会去创建A,此时发现prototypesCurrentlyInCreation里已经存在A了,说明此时A正在被创建,这时就发现了循环依赖。
4.AOP是什么?(看这里)
AOP,就是面向切面编程。一般我们的程序执行流程是从 Controller 层调用 Service 层、然后 Service 层调用 DAO 层访问数据。对执行流程中的不同位置进行横切,完成各服务共同需要实现的功能。
切面(Aspect)(切面=切入点+通知):简单的理解就是把那些与核心业务无关的代码提取出来,进行封装成一个或几个模块用来处理那些附加的功能代码。(如日志,事务,安全验证)我们把这个模块的作用理解为一个切面。其实切面就是我们写一个类,这个类中的代码原来是在业务模块中完成的,现在单独成一个或几个类。在业务模块需要的时候才织入。
织入(Weaving):把切面(aspect)连接到其它的应用程序类型或者对象上,并创建一个被通知(advised)的对象。 这些可以在编译时,类加载时和运行时完成。Spring和其它纯Java AOP框架一样,在运行时完成织入。
切入点(Pointcut):也就是切点。本质上是一个捕获连接点的结构。在AOP中,可以定义一个pointcut,来捕获相关方法的调用。
通知(Advice):在切面的某个特定的连接点(Joinpoint)上执行的动作。通知有各种类型,其中包括“around”、“before”和“after”等通知。 通知的类型将在后面部分进行讨论。许多AOP框架,包括Spring,都是以拦截器做通知模型,并维护一个以连接点为中心的拦截器链。
具体机制和实现原理
10.Spring中Placeholder动态替换发生的时间:
- 我们在配置文件中 context:property-placeholder默认使用的类是PropertySourcesPlaceholderConfigurer,该类的父类实现了 BeanFactoryPostProcessor接口,而容器初始化时,AbstractApplicationContext在初始化方法refresh()方法中调用invokeBeanFactoryPostProcessors()方法,在invokeBeanFactoryPostProcessors方法内部会调用使用所有的BeanFactoryPostProcessor实现类来对BeanDefinition中的属性做修改和预处理,此时所有的Bean都还没用实例化,因此配置文件中的值动态替换的时间就是此时。
Spring中Placeholder有两种实现方式:PropertySourcesPlaceholderConfigurer(默认),PropertyPlaceholderConfigurer(以前)
如果想自定义配置替换可以通过扩展(继承)上面两个类实现。
11.AOP采用了什么设计模式?
AOP通过代理模式实现,动态代理和静态代理,动态代理又有两种实现,JDK代理和CGLIB代理,分别在什么情况下使用?(看org.springframework.aop.framework.DefaultAopProxyFactory的源码)动态代理的区别:
- JDK动态代理是基于接口的,如果要代理的类没有实现接口,就代理不了。
因此注意如果要代理的类既实现了接口,又有自己定义的方法,而我们想代理实现类重写的方法和它自己定义的方法,那么必须将aop代理方式配置proxy-target-class设置为true,即默认采用CGLib代理。否则Spring默认采用JDK代理,而采用JDK动态代理,则生成的代理类是基于接口的,无法代理实现类自己定义的方法。
注意:SpringBoot在1.4之后的版本默认proxy-target-class默认为true,即SpringBoot1.4之后默认采用CGLIB代理。 - CGLib动态代理是通过字节码底层继承要代理类来实现(如果被代理类被final关键字所修饰,那么抱歉会失败)。
相关注解的使用方式?@Aspect 、@PointCut、@Before、@After、@Around看这里
JavaComplier是JDK提供一个编译工具类,方便我们通过代码的方式编译java文件。通过继承ClassLoader类来实现自定义加载器。
有了编译工具和自定义类加载器,我们就可以实现自定义的动态代理,步骤说简单一点就是:
- 首先将Java代码写入到自定义的java文件里,比如Test.java,要注意文件名和类名必须一样,这是Java规范,否则会报错
写入的Java代码是继承我们需要代理的类,代理类的信息都可以通过反射拿到。
代理类需要重写被代理类的非私有方法,方法实现就一行代码,就是通过super调用被代理类的方法。
比如被代理类里有个public方法test,那么代理类生成重写代码实现时,只需要super.test(..)就行了。 - 通过JavaComplier编译我们生成的java文件,生成class文件。
- 通过自定义类加载器加载生成的class文件
- 然后再通过反射实例化类。
通过上面4步我们就能实现类似CGLib一样的动态代理了,就算被代理类没有实现接口也能做动态代理。
AOP实现原理
思考一下AOP为什么要用代理,知道AOP为什么要用代理了,基本也明白AOP的实现原理了。
为什么要用代理:
AOP达到的目的就是在调用方法的时候能够在方法调用前和调用后实现方法增强(就是在方法调用前和调用后做一些我们定义好的操作)。代理模式就可以完美的实现这个功能,代理模式的初衷就是不去关注被代理类,只需要关注我们调用的方法,在调用方法前后做我们需要的操作。因此AOP采用了代理模式。
Spring中AOP的实现原理
1. 在切点配置(可以通过注解也可以通过继承StaticMethodMatcherPointcut重写里面的matcher方法自定义判定切点的规则)时,我们会配置的注解、类的类型、包等条件来判断哪些类是需要实现AOP的类
2. 在Spring实例化Bean的时候将符合这些条件的类直接生成代理类添加到Spring的Bean容器中(如果Bean的Scope设为prototype那么每次调用都会生成新的代理类),因此我们debug调试代码的时候可以看到Bean容器里面有那些符合条件的类的代理类。
3. 在我们访问具体方法的时候,就会使用代理类调用,在代理类中还有我们需要执行的前置或后置方法Method。
以上就是AOP为什么使用代理模式和AOP的实现原理。
12.Spring中的事务
- 隔离级别和传播类型?
隔离级别:读未提交、读已提交、可重复读、可串行化,
传播类型:REQUIRED、MANDATORY、REQUIRES_NEW、SUPPORTS、NOT_SUPPROTED、NEVER、NESTED - 在使用Spring事务时,如果使用了try-catch必须将异常抛出,否则会使事务失效
事务实现原理
Spring中事务的实现原理,Spring的事务其实底层仍然是具体数据库的事务,比如我们使用MySQL数据库,Spring实际上用的就是MySQL的事务。
我们知道在使用JDBC操作数据库使用事务就是我们主动通过Connection设置setAutoCommit为false,执行完后然后自己commit。而在我们通过ORM框架执行sql的时候,实际上底层还是使用的是JDBC。
而Spring的事务,就是利用SpringAOP,在我们调用具体使用事务的方法的时候,通过AOP前置通知自动帮我们setAutoCommit为false,在方法执行完后通过AOP后置通知帮我们commit。
Spring使用事务有两种方式一种是在配置文件里面配置切点和通知,另一种是直接在在类或方法上使用@Transaction注解,这两种方式,都将切点对应的Bean和使用Transaction注解的Bean生成代理类,在我们调用具体的方法的时候会直接通过代理类调用具体的方法。
可能引起事务失效的情况:
1.数据库引擎不支持事务。比如MySQL,注意表要使用支持事务的引擎,比如InnoDB,如果是MyISAM,事务是不起作用的
2.标注事务的方法不是public的,@Transactional只能用于public方法上。因为AOP是基于代理模式的。
3.自调用问题(看这里)。检查是不是同一个类中的方法调用(如a方法调用同一个类中的b方法,但只在b方法上使用了注解)。
4.事务的传播属性设置错了。如:设置传播属性为Propagation.NOT_SUPPORTED。
5.UNCHECKED异常被try-catch抓住处理了。
6.使用@Transaction注解事务,却没有开启事务的注解功能
7.使用事务的类没有被Spring管理,即使用注解事务的类没有被加载成bean。
8.使用事务注解的时候,不能用在接口上,必须在实现类的实现方法上使用,否则AOP会找不到实现类,不只是事务注解其他所有用到AOP的注解都不能直接用在接口上,而必须是接口的实现类上,或者接口实现类重写的方法上。
9.异常类型不是unchecked异常
如果我想check异常也想回滚怎么办,需要注解上面写明异常类型即可@Transactional(rollbackFor=Exception.class)
,类似的还有norollbackFor,自定义不回滚的异常
10.数据源没有配置事务管理器
注意:由于自调用,还会引起其他与Spring AOP相关的注解失效。如:@Cache
, @Async
,@Transaction。
原因:这里那@Transaction注解举例,Spring 在扫描bean的时候会扫描方法上是否包含@Transaction
注解,如果包含,spring会为这个bean动态地生成一个子类(即代理类,proxy),如果是CGLib代理,那么代理类是通过字节码底层继承原来那个bean实现的。此时,当这个有注解的方法A被调用的时候,实际上是由代理类来调用的,代理类在调用之前就会启动transaction。然而,如果这个有注解的方法A是被同一个类中没有使用注解的方法B调用的(自调用),那么该方法A的调用并没有通过代理类,而是直接通过被代理的实例对象调用,所以就不会启动transaction,我们看到的现象就是@Transactional注解无效。这样自调用引起注解试下的情况也会发生在@Cache和@Async中。
这里需要解释:Java中子类实例化时会先调用父类的构造函数,因此子类实例化也会实例化父类,因此采用CGLib代理自调用的时候失效实际上调用的是代理类的父类。
而JDK代理也是利用实例类生成的代理对象。
自调用的解决方法:1.避免自调用 2.在需要用到相关注解的方法上都加上注解,避免让自调用发生在代理类中 3.有自调用的地方朱勇用代理Bean调用。
13.Spring中核心接口和类:ApplicationContext、BeanFactory、BeanWrapper、FactoryBean
-
ApplicationContext 保存了 IoC 的整个应用上下文,可以通过其中的 BeanFactory 获取到任意到 Bean;
-
BeanFactory 主要的作用是根据 Bean Definition 来创建具体的 Bean;
-
BeanWrapper 是对 Bean 的包装,一般情况下是在 Spring IoC 内部使用,提供了访问 Bean 的属性值、属性编辑器注册、类型转换等功能,方便 IoC 容器用统一的方式来访问 Bean 的属性;
-
FactoryBean 通过 getObject 方法返回实际的 Bean 对象。Shiro的ShiroFilterFactoryBean就是实现了该接口重写了getObject方法。
14.Spring中bean的作用域Scope?singleton、prototype、session、request看这里
15.Spring的事件机制(先了解事件驱动模式):
ContextRefreshedEvent、ContextStartedEvent、ContextStoppedEvent、ContextClosedEvent、RequestHandledEvent
如何在项目启动后在注入事件监听器?
方式1:实现ApplicationListener接口,实现ApplicationReadyEvent的监听,然后在监听到ApplicationReadyEvent事件时在通过
event.getApplicationContext().addApplicationListener(listener)添加监听器
方式2:利用@EventListener实现事件处理的监听方法,事件参数仍然是ApplicationReadyEvent,处理内容还是通过上诉方式添加监听器
方式3:利用@EventListener注解和原子类型的变量,事件参数仍然是ApplicationReadyEvent,处理内容还是改变原子变量的值,而另一个需要监听的事件处理方法也是用@EventListener标注,内部处理逻辑会根据原子变量的值做出改变
具体开发相关
5.Spring配置的方式:xml、注解、API
6.Spring自动装配:
byName、byType、Constructor、Autodetect(spring3.0之后过时,不再支持)
@Autowired默认使用byType来装配属性,如果匹配到类型的多个实例,再通过byName来确定Bean。看这里
@Qualifier和@Resource配合@Autowired使用看这里
自动装配的原理:在启动spring IoC时,容器自动装载了一个AutowiredAnnotationBeanPostProcessor处理器,通过调用处理器内部的方法,扫描到@Autowied、@Resource或@Inject时,就会在IoC容器自动查找需要的bean,并装配给该对象的属性
7.Spring自动装配集合:注解和xml看这里
8.Spring内部bean看这里、父子bean看这里
9.Spring常用的注解
- 类型类:
@Controller、@Service、@Repository、@Component、@Configuration、@Bean - 设置类:
@Required、@Autowired(与@Resource的区别)和@Qualifier、@Scope、@DependsOn、@Lazy - Web类:
@RequestMapping&&@GetMapping&&@PostMapping、@PathVariable&&@RequestParam、@RequestBody&&@ResponseBody - 功能类
@ImportResource、@ComponentScan、@EnableCaching&&Cacheable、@Transactional、@Aspect&&@Pointcut、@Scheduled
10.SpringMVC的工作流程是怎样的看这里
大致可以分为:
- DispatcherServlet根据请求url调用对应的 HandlerMapping,比如RequestMappingHandlerMapping、SimpleUrlHandlerMapping,
- 通过HandlerMapping找到对应的Handler,比如我们通过@Controller和@RestController指定的Controller类
- 返回Hander给DispatcherServlet,通过HandlerAdapter利用反射调用对应的Handler,比如RequestMappingHandlerAdatper
- 执行完成,将 ModelAndView返回给DispatcherServlet
- 最后进行视图解析、返回视图
11.Spring如何解决循环依赖:bean作用域为singleton单例看这里
- 不能解决的情况:
1. 构造器注入循环依赖
2. bean作用域 prototype属性注入循环依赖
只能抛出BeanCurrentlyIn CreationException异常表示循环依赖。 - 能解决的情况:
1. bean作用域为singleton属性注入,setter方法注入循环依赖
12. 三级缓存
singletonObjects:用于存放完全初始化好的 Bean,这里面的Bean是完成属性注入的
earlySingletonObjects:提前曝光的单例对象的Cache,存放原始的 Bean 对象(尚未填充属性),用于解决循环依赖
singletonFactories:单例对象工厂的Cache,存放 Bean 工厂对象,用于解决循环依赖
循环依赖获取Bean的流程:先从singletonObjects一级缓存里面去拿初始化好的Bean,如果一级缓存里面没有Bean,则从二级缓存里面去拿,二级缓存里面Bean是还没有填充属性的只是进行了实例化分配了内存空间,如果二级缓存里面仍然没有,则通过对象工厂去实例化Bean,这里实例化的Bean是没有填充属性的,所以这里获取到Bean之后,会把Bean放入二级缓存,并且清除三级缓存里面的Bean。
13.@AliasFor 用法:
- 用到注解的属性上,表示两个属性互相为别名,互相为别名的属性值必须相同,若设置成不同,则会报错。互为别名的注解必须成对出现,比如value属性添加了@AliasFor(“path”),那么path属性就必须添加@AliasFor(“value”),另外还有一点,互为别名的属性必须定义默认值。
- 注解是可以继承的,但是注解是不能继承父注解的属性的,也就是说,我在类扫描的时候,拿到的注解的属性值,依然是父注解的属性值,而不是你定义的注解的属性值,所以此时可以在子注解对应的属性上加上@AliasFor
注意: @AliasFor注解是Spring特有的注解,因此在获取相关注解的时候要使用Spring提供的AnnotationUtils来处理,而通过Jdk反射的形式获取注解信息会出问题。
Spring的扩展点
BeanDefinitionRegistryPostProcessor:可以通过实现该接口来注册自定义的bean
BeanFactoryPostProcessor:bean factory初始化完成后对bean factory 进行预处理
BeanPostProcessor:支持在bean初始化前、后对bean进行处理
上面三个扩展点可以通过实现Ordered和PriorityOrdered接口来指定执行顺序,实现PriorityOrdered接口的Processor会先于实现Ordered接口的Processor执行
默认情况下执行顺序是BeanDefinitionRegistryPostProcessor->BeanFactoryPostProcessor->BeanPostProcessor,BeanPostProcessor是在实例化Bean的时候做前置处理和后置处理,BeanFactoryPostProcessor是BeanFactory实例化的时候做前置处理和后置处理,BeanDefinitionRegistryPostProcessor是生成BeanDefinition的时候做处理,因此想在Bean实例化之前修改Bean的属性可实现BeanDefinitionRegistryPostProcessor接口。
BeanFactoryAware:可以获得当前的BeanFactory
ApplicationContextAware:可以获得ApplicationContext以及其中的bean,当需要在代码中动态获取bean的时候,可以通过实现这个接口进行处理
InitializingBean:在bean创建完成,所有属性注入完成后执行
DisposableBean:在bean销毁前执行
ApplicationListener:用来监听产生的应用事件
FactoryBean:为IOC容器中Bean的实现提供了更加灵活的方式,Spring可以在getObject()方法来返回我们自定义创建的Bean,Spring在通过getBean获取Bean时,会判断拿到的Bean是否是FactoryBean,如果是FactoryBean,实现类重写的getObject方法返回的对象
关于配置的动态替换可以通过继承PropertySourcesPlaceholderConfigurer(默认),PropertyPlaceholderConfigurer(以前)实现
问题:
- Spring的核心特点是什么?Spring提供了哪些常用的功能
- IOC、DI是什么意思
- 依赖注入的方式(两种)?通过@Autowire注解注入时可以不写set方法吗?
- Spring中Bean、Context、Core是什么
- ApplicationContext常见的三种实现
- Spring的初始化流程
- Bean的初始化过程,也可说Bean的生命周期?这个问题还会涉及到构造方法、@PostConstruct、InitializingBean、init-method指定方法的执行顺序
- Spring中Placeholder动态替换发生的时间
- AOP是什么?切面、切点、通知是什么
- AOP的实现原理?
- 为什么AOP要使用动态代理?AOP两种动态代理的区别?
- Spring中事务仍然是数据库的事务
- Spring事务的隔离级别
- Spring事务的传播属性
- Spring事务的实现原理
- Spring事务注解失效的原因
- Spring中核心的接口和类:ApplicationContext、BeanFactory、BeanWrapper、FactoryBean、BeanDefinitionRegistryPostProcessor、BeanFactoryPostProcessor、BeanPostProcessor、InitializingBean、DisposableBean、ApplicationListener各有什么用处
- 怎么控制执行BeanFactoryPostProcessor接口的先后顺序?
实现PriorityOrdered接口,重写getOrder方法或者使用@Order注解 - Spring 中Bean的作用域
- Spring常用的几种事件
- Spring配置的方式
- Spring自动装配是什么?Spring怎么装配集合?自动装配的原理?可以通过哪些方式?默认使用什么方式?
- Spring中内部Bean是什么?父子Bean是什么?
- Spring中有哪些常用的注解?主要就是SpringMVC用到的、AOP用到的、配置用到的、事务用到的、SpringBoot用到的
- SpringMVC的基本工作流程是怎样的
- Spring如何解决循环依赖的问题?三级缓存是什么