一. 概述
1.Spring是什么?
Spring是一个轻量级的开发框架。是为Java应用程序提供基础性服务的一套框架,目的是用于简化企业应用程序的开发,它使得开发者只需要关心业务需求。
2.Spring的一些重要模块
- Spring Core: 基础,可以说 Spring 其他所有的功能都需要依赖于该类库。主要提供 IOC 依赖注入功能。
- Spring Aspects : 该模块为与AspectJ的集成提供支持。
- Spring AOP :提供了面向方面的编程实现。
- Spring JDBC : Java数据库连接。
- Spring JMS :Java消息服务。
- Spring ORM : 用于支持Hibernate等ORM工具。
- Spring Web : 为创建Web应用程序提供支持。
- Spring Test : 提供了对 JUnit 和 TestNG 测试的支持。
¥3. Spring 的优点
- spring属于低侵入式设计,代码的污染极低;
- spring的DI机制将对象之间的依赖关系交由框架处理,减低组件的耦合性;
- Spring提供了AOP技术,支持将一些通用任务,如安全、事务、日志、权限等进行集中式管理,从而提供更好的复用。
- spring对于主流的应用框架提供了集成支持。
¥4.Spring 框架中都用到了哪些设计模式?
- 工厂模式:BeanFactory就是简单工厂模式的体现,用来创建对象的实例;
- 单例模式:Bean默认为单例模式。
- 代理模式:Spring的AOP功能用到了JDK的动态代理和CGLIB字节码生成技术;
- 模板方法:用来解决代码重复的问题。比如. RestTemplate, JmsTemplate, JpaTemplate。
- 观察者模式:定义对象键一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都会得到通知被制动更新,如Spring中listener的实现–ApplicationListener。
¥5. Spring框架中有哪些不同类型的事件
Spring 提供了以下5种标准的事件:
- 上下文更新事件(ContextRefreshedEvent):在调用ConfigurableApplicationContext 接口中的refresh()方法时被触发。
- 上下文开始事件(ContextStartedEvent):当容器调用ConfigurableApplicationContext的Start()方法开始/重新开始容器时触发该事件。
- 上下文停止事件(ContextStoppedEvent):当容器调用ConfigurableApplicationContext的Stop()方法停止容器时触发该事件。
- 上下文关闭事件(ContextClosedEvent):当ApplicationContext被关闭时触发该事件。容器被关闭时,其管理的所有单例Bean都被销毁。
- 请求处理事件(RequestHandledEvent):在Web应用中,当一个http请求(request)结束触发该事件。如果一个bean实现了ApplicationListener接口,当一个ApplicationEvent 被发布以后,bean会自动被通知。
二. SpringIOC
¥1.对Spring的IOC的理解,IOC的意义
- IoC(Inverse of Control:控制反转)是一种设计思想,就是将原本在程序中手动创建对象的控制权,交由Spring框架来管理。
- IoC 容器就像是一个工厂一样,当我们需要创建一个对象的时候,只需要配置好配置文件/注解即可,完全不用考虑对象是如何被创建出来的。 在实际项目中一个 Service 类可能有几百甚至上千个类作为它的底层,假如我们需要实例化这个 Service,你可能要每次都要搞清这个 Service 所有底层类的构造函数,这可能会把人逼疯。如果利用 IoC 的话,你只需要配置好,然后在需要的地方引用就行了,这大大增加了项目的可维护性且降低了开发难度。
¥2. Spring 中的 bean 的作用域有哪些?
- singleton:单例的,每次容器返回的对象是同一个(默认作用域)。
- prototype :多例的,每次返回的对象是新创建的实例。
- request:仅作用于HttpRequest,每次Http请求都会创建一个新的bean。
- session:仅作用于HttpSession,不同的Session使用不同的实例,相同的Session使用同一个实例。
- global session :仅作用于HttpSession,所有的Session使用同一个实例。
3. @Component 和 @Bean 的区别是什么?
- 作用对象不同: @Component 注解作用于类,而@Bean注解作用于方法。
- @Component通常是通过类路径扫描来自动侦测以及自动装配到Spring容器中。@Bean 注解通常是我们在标有该注解的方法中定义产生这个 bean,@Bean告诉了Spring这是某个类的示例,当我需要用它的时候还给我。
- @Bean 注解比 Component 注解的自定义性更强,而且很多地方我们只能通过 @Bean 注解来注册bean。比如当我们引用第三方库中的类需要装配到 Spring容器时,则只能通过 @Bean来实现。
4. Spring框架中的单例Beans是线程安全的么?Spring如何处理线程并发问题?
- Spring框架并没有对单例bean进行任何多线程的封装处理。关于单例bean的线程安全和并发问题需要开发者自行处理。
- 但实际上,大部分的Spring bean并没有可变的状态(比如Serview类和DAO类),所以在某种程度上说Spring的单例bean是线程安全的。如果你的bean有多种状态的话(比如 View Model 对象),就需要自行保证线程安全。
解决方案:
- 将多态bean的作用域由“singleton”变更为“prototype”。(不同线程操作不同对象,自然没有线程安全问题)
- 在Bean对象中尽量避免定义可变的成员变量
- 在类中定义一个ThreadLocal成员变量,将需要的可变成员变量保存在 ThreadLocal 中(推荐的一种方式)。
5.依赖注入的方式,注入注解的差别
- 构造器注入
- Set方法注入
- 注解注入:
- @Autowired自动按类型注入,如果有多个匹配则按照指定bean的id查找, 默认情况下它要求依赖对象必须存在(可以设置它required属性为false);@Qualifier在自动按照类型注入的基础之上,再按照 Bean 的 id 注入,给成员变量注入时必须搭配@Autowired,给方法注入时可单独使用;
- @Resource直接按照 Bean 的 id 注入;只有当找不到与名称匹配的bean才会按照类型来装配注入。
- @Value用于注入基本数据类型和String。
6. 构造器依赖注入和 Setter方法注入的区别
- 构造函数注入:没有部分注入,不会覆盖 setter 属性,任意修改都会创建一个新实例,适用于设置很多属性
- setter 注入:有部分注入,会覆盖 setter 属性,任意修改不会创建一个新实例,适用于设置少量属性
¥7. 单例bean如何引用多例bean
- 如果使用@Autowired将多例bean注入单例bean中,该单例bean保存了一个创建好的多例bean的引用;但每次使用该单例bean时都去使用其保存好的那个多例bean,因此每次使用的多例bean都是同一个
- 解决方法:
- spring官方使用@Lookup或标签,解决单例引用多例的问题
- 阿里官方推荐,这种方法最暴力,多例bean直接不使用spring管理,让用户调用方法时,自己new一个,这样每次使用的都是新创建的bean。这种做法即完全脱离spring管理,用户自己负责实例的生命周期
¥8. 如何指定Bean的初始化和销毁方法
初始化方法会在Bean实例化且赋值完毕后调用,销毁方法会在容器关闭时调用(单例Bean)
- 在@Bean注解的initMethod和destroyMethod属性中指定
- 实现InitialzingBean接口定义初始化方法,实现Disposable接口定义销毁方法
- @PostConstruct注解定义初始化方法,@PreDestroy定义销毁方法
初始化和销毁方法可用于获取/关闭资源
9. Aware接口的作用和使用
- BeanNameAware、BeanFactoryAware、ApplicationContextAware
学名叫感知器,可以让bean能感知到整个容器上下文信息的接口。 - 让Bean实现这几个接口,即可在该Bean中注入BeanFactory,ApplicationContext,BeanName从而感知到容器中的其他Bean
public class LifeCycleBean implements BeanNameAware,
BeanFactoryAware, ApplicationContextAware{
@Override
public void setBeanName(String name) {
System.out.println("BeanNameAware被调用, 获取到的beanName:" + name);
}
@Override
public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
this.beanFactory = beanFactory;
}
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
this.applicationContext = applicationContext;
}
}
- 实现原理:spring在创建过程中,AwareProcessor发现该Bean实现了Aware接口,则回调子类的setBeanName, setBeanFactory, setApplicationContext,将注入BeanFactory,ApplicationContext,BeanName注入Bean中
¥10. Spring能解决什么状况下的循环依赖,不能解决什么状况下的循环依赖,为什么?
- spring只能解决setter注入单例模式下的循环依赖问题
- 不能解决构造器注入和原型模式下的循环依赖
- 因为解决循环依赖必须要满足2个条件:
- 需要用于提前曝光的缓存
- 属性的注入时机必须发生在提前曝光动作之后,不管是填充还是初始化都行,总之不能在实例化,因为提前曝光动作在实例化之后
- 构造器注入,实例化的过程是调用new A(B b);的过程,这时的A还未创建出来,根本是不可能提前曝光的,因此无法获取到三级缓存,进而导致⑩异常的抛出
- 原型模式每次都是重新生成一个全新的bean,根本没有缓存一说。这将导致实例化A完,填充发现需要B,实例化B完又发现需要A,而每次的A又都要不一样,所以死循环的依赖下去。唯一的做法就是利用循环依赖检测,发现原型模式下存在循环依赖并抛出异常
11.举出几个Spring框架中的后置处理器
- ApplicationContextAwareProcessor:如果Bean实现了ApplicationContextAware接口,在初始化前会调用setApplicationContext方法将applicationContext注入bean中,即可以在初始化过程中感知IOC容器中的其他Bean
12. BeanFactoryPostProcessor的作用,举出一个具体的BeanFactoryPostProcessor
在Spring框架中,BeanFactoryPostProcessor 是一个非常有用的接口,它允许你在所有 bean 定义加载完成后、bean 实例化之前对 BeanFactory 进行修改。这个接口的主要作用是提供一个钩子点,让你可以在 bean 实例化之前对 bean 定义进行自定义处理。
- PropertyPlaceholderConfigurer:在所有 bean 定义加载完成后,解析 application.properties 文件中的属性,并将这些属性值替换到 XML 配置文件中的占位符中。这样 bean 在实例化时就会使用这些实际的属性值。
三. SpringIOC源码剖析
¥1. SpringIOC运行原理简述
spring IoC主要分为两个阶段:
- 将XML,注解类等配置文件读入到spring容器内生成一个个beanDefinition,并最终将所有的beanDefinition注册到一个BeanDefinitionMap中
- 根据BeanDefinition对Bean的定义对Bean进行装配,交付给用户
¥2. 什么是BeanDefinition
BeanDefinition用于管理各种对象以及对象间的依赖关系,是对依赖关系的数据抽象
3. 配置文件的解析
运行原理中的第一阶段,根据配置方式的不同有不同的解析实现:
- XmlBeanFactory解析过程包括将XML文件转换为Resource——>Resource经过编码通过getInputStream获取文件流——>读取文件流转化成DOM树——>将DOM树解析为BeanDefinition——>将BeanDefinition注册到BeanDefinitionMap中
- 注解方式的解析方式更为复制,其在容器中添加了一个BeanFactory的后置处理器,并在执行invokeBeanFactoryPostProcess()时完成配置类的解析,且解析过程中还创建了配置类的代理类
配置文件的解析了解即可,更重要的第二阶段,如何对bean进行装配(依赖注入)
4. DefaultListableBeanFactory
- DefaultListableBeanFactory是整个bean加载的核心部分,是Spring注册及加载bean的默认实现
- XmlBeanFactory继承自DefaultListableBeanFactory,不同的地方其实是在XmlBeanFactory中使用了自定义的XML读取器XmlBeanDefinitionReader,实现了个性化的BeanDefinitionReader读取
- ApplicationContext中容器的实现也是DefaultListableBeanFactory,但是添加了许多额外的功能
¥5. BeanFactory和FactoryBean,ApplicationContext的区别?
- BeanFactory和ApplicationContext的区别
- ①BeanFactory和ApplicationConext都是IOC容器,用来管理bean的IOC容器或对象工厂,但BeanFactory只实现了最基本但容器功能,如读取bean配置文档,管理bean的加载、实例化,控制bean的生命周期,维护bean之间的依赖关系;ApplicationConext是BeanFactory的子接口,对其功能进行了扩展,如支持不同的信息源,应用事件等
- BeanFactroy采用的是延迟加载形式来注入Bean的,即只有在使用到某个Bean时(调用getBean()),才对该Bean进行加载实例化。
ApplicationContext,它是在容器启动时,一次性创建了所有的Bean(懒加载和多例bean除外)。这样,在容器启动时,我们就可以发现Spring中存在的配置错误,这样有利于检查所依赖属性是否注入。 - 相对于基本的BeanFactory,ApplicationContext 唯一的不足是占用内存空间。当应用程序配置Bean较多时,程序启动较慢。
- FactoryBean是一个向容器中配置Bean的接口,可以生产或者装饰对象的工厂Bean,可以通过实现该接口自定义的实例化Bean的逻辑。
6. ApplicationContext的初始化流程
ApplicationContext的refresh()方法流程:
- prepareRefresh() :刷新前的预处理
- obtainFreshBeanFactory(): 获取BeanFactory,Xml配置文件则在该步完成将配置文件解析成BeanDefinition
- prepareBeanFactory(beanFactory):BeanFactory预准备工作(添加部分后置处理器等)
- postProcessBeanFactory(beanFactory):空方法,用于后序实现
- invokeBeanFactoryPostProcessors(beanFactory):执行BeanFactory的后置处理器,注解方式将配置类解析成BeanDefinition在这步实现加粗样式
- registerBeanPostProcessors(beanFactory):注册Bean的后置处理器
- initMessageSource():初始化MessageSource组件(用于国际化)
- initApplicationEventMulticaster():初始化事件派发器
- onRefresh():留给子容器重写
- registerListeners() :注册监听器
- finishBeanFactoryInitialization(beanFactory):初始化所有非懒加载单实例Bean,重中之重!即上面提到的第二阶段!
- 完成容器初始化创建工作
7. Bean依赖注入流程
在finishBeanFactoryInitialization(beanFactory)中,会迭代获取所有的beanName,依次对各个单例Bean进行依赖注入,流程如下:
- 判断是否为FactoryBean,如果是,则通过FactoryBean的方式生产Bean
- 尝试从缓存(singletonObjects)中获取,第一次获取时还未创建,自然获取不到,则启动单实例bean的创建流程
singletonObject = singletonFactory.getObject();
- 实例化阶段
- resolveBeforeInstantiation(beanName, mbdToUse);实例化前置处理,如果该步创建了代理对象则返回(一般不会在这里创建)
- createBeanInstance(beanName, mbd, args);通过反射创建出该bean实例
- applyMergedBeanDefinitionPostProcessors(mbd, beanType, beanName);beanDefinition合并后的后置处理,比如@Autowired在属性上,就是在这里解析的,但是仅仅只是单纯的解析,还没有实现注入
- populateBean(beanName, mbd, instanceWrapper);属性填充阶段
- 拿到后置处理器InstantiationAwareBeanPostProcessor
- 执行postProcessProperties:@Autowired在属性上,就是在这里注入的
- 执行postProcessPropertyValues:bean属性的后置处理器
- applyPropertyValues(beanName, mbd, bw, pvs);利用反射对属性进行赋值
- 初始化阶段
- invokeAwareMethods(beanName, bean);回调各类Aware的set方法,实现注入
- applyBeanPostProcessorsBeforeInitialization(wrappedBean, beanName);执行后置处理器的初始化前置处理
- invokeInitMethods(beanName, wrappedBean, mbd); 触发自定义初始化方法
- applyBeanPostProcessorsAfterInitialization(wrappedBean, beanName);执行后置处理器的初始化后置处理
- 将创建好的Bean添加到SingletonObjects中
¥ 7.Spring中bean对象的生命周期
- Spring对bean进行实例化。
- bean属性赋值和依赖注入
- 处理Aware接口(invokeAwareMethod)
- 调用BeanPostProcessor的post-ProcessBeforeInitialization方法。
- bean的初始化
- 调用BeanPostProcessor的post-ProcessAfterInitialization方法。
- 将bean放入IOC容器以供使用。
- 关闭容器时,调用DisposableBean的destroy方法,如果bean使用destroy-method声明了自定义销毁方法,该方法也会被调用。
在整个实例化期间涉及到9次BeanPostProcessor的调用,在各个时期对bean进行增强
8. 后置处理器BeanPostProcessor及其生命周期
spring使用模板模式,在bean的创建过程中安插了许多锚点,用户寻找对应的锚点,通过重写BeanPostProcessor的各个方法介入到bean的创建过程当中,从而对Bean实现增强。
后置处理器可以插入的位置:
- postProcessBeforeInstantiation被调用
- 构造方法被调用
- postProcessAfterInstantiation被调用
- postProcessProperties被调用
- BeanNameAware,BeanFactoryAware,ApplicationContextAware被调用,
- postProcessBeforeInitialization被调用
- afterPropertiesSet被调用
- myInit自定义初始化方法被调用,
- postProcessAfterInitialization被调用
- bean创建完成 name
- DisposableBean被调用
- destroy-method自定义销毁方法被调用
除此之外BeanPostProcessor还有很多实现类,可以完成其他各个位置处Bean的增强
8.5 如何指定多个后置处理器的执行顺序
通过设置 BeanPostProcessor 实现的 Ordered 接口提供的 order 属性来控制这些 BeanPostProcessor 接口的执行顺序
9.¥¥Spring如何解决循环依赖
Spring通过提前曝光机制,利用三级缓存解决循环依赖
三级缓存分别是:
- singletonObject:一级缓存,该缓存key = beanName, value = bean;这里的bean是已经创建完成的,该bean经历过实例化->属性填充->初始化以及各类的后置处理。因此,一旦需要获取bean时,我们第一时间就会寻找一级缓存
- earlySingletonObjects:二级缓存,该缓存key = beanName, value = bean;这里跟一级缓存的区别在于,该缓存所获取到的bean是提前曝光出来的,是还没创建完成的。也就是说获取到的bean只能确保已经进行了实例化,但是属性填充跟初始化肯定还没有做完,因此该bean还没创建完成,仅仅能作为指针提前曝光,被其他bean所引用
- singletonFactories:三级缓存,该缓存key = beanName, value = beanFactory;在bean实例化完之后,属性填充以及初始化之前,如果允许提前曝光,spring会将实例化后的bean提前曝光,也就是把该bean转换成beanFactory并加入到三级缓存。在需要引用提前曝光对象时再通过singletonFactory.getObject()获取。
¥10. Spring通过提前曝光,直接曝光到二级缓存已经可以解决循环依赖问题了,为什么一定要三级缓存?
- 三级缓存是一个工厂类,通过该工厂类获取提前曝光的bean时会获取AOP后置处理器对需要代理的类进行代理
因此需要三级缓存的原因
- 由于提前曝光在AOP代理之前,因此需要通过三级缓存的工厂来对bean进行提前AOP代理
- 另外,所有的单例bean在实例化后都会被进行提前曝光到三级缓存中,但是曝光的时候并不提前AOP代理,只有曝光,且被提前引用的时候才调用,确保了被提前引用这个时机触发(如果没有三级缓存,则必须要提前代理好了直接放入二级缓存中,没这必要)
¥11. 如果循环依赖的时候,所有类又都需要Spring AOP自动代理,那Spring如何提前曝光?曝光的是原始bean还是代理后的bean?
- 在三级缓存的工厂类bean进行了提前代理,曝光的已经是代理后的bean,并缓存了提前代理的bean;
- 后续再进行AOP代理时,如果在缓存中发现已经进行过提前代理,那么就不再进行重复AOP代理;
- 返回bean时如果已经进行了提前代理,则将二级缓存中提前代理过的bean返回给容器
- 从而保证已经被AOP代理的类不会进行第二次AOP代理,且返回的Bean也是代理后的bean
// AbstractAutoProxyCreator.java
public Object postProcessAfterInitialization(@Nullable Object bean, String beanName) {
if (bean != null) {
// 获取缓存key
Object cacheKey = getCacheKey(bean.getClass(), beanName);
/// 查看该bean是否被Spring AOP提前代理!而缓存的是原始的bean,因此如果bean被提前代理过,这此处会跳过
// 如果bean没有被提前代理过,null != bean 则进入AOP代理
if (this.earlyProxyReferences.remove(cacheKey) != bean) {
return wrapIfNecessary(bean, beanName, cacheKey);
}
}
return bean;
}
if (earlySingletonExposure) {
// earlySingletonReference:二级缓存,缓存的是经过提前曝光提前Spring AOP代理的bean
Object earlySingletonReference = getSingleton(beanName, false);
if (earlySingletonReference != null) {
// exposedObject跟bean一样,说明初始化操作没用应用Initialization后置处理器(指AOP操作)改变exposedObject
// 主要是因为exposedObject如果提前代理过,就会跳过Spring AOP代理,所以exposedObject没被改变,也就等于bean了
if (exposedObject == bean) {
// 将二级缓存中的提前AOP代理的bean赋值给exposedObject,并返回
exposedObject = earlySingletonReference;
}
........
12. 提前曝光的bean怎么拥有后面属性注入的依赖bean的引用
- JDK动态代理时,会将目标对象target保存在最后生成的代理$proxy中,当调用$proxy方法时会回调h.invoke,而h.invoke又会回调目标对象target的原始方法。
- 因此,其实在Spring AOP动态代理时,原始bean已经被保存在提前曝光代理中了。而后原始Bean继续完成属性填充和初始化操作。因为AOP代理$proxy中保存着traget也就是是原始bean的引用,因此后续原始bean的完善,也就相当于Spring AOP中的target的完善,这样就保证了Spring AOP的属性填充与初始化了!
¥13. Spring循环依赖全过程
14. 怎么才能知道Spring的IOC容器已经完全初始化了
Spring的IOC容器完全初始化后,会发布一个ContextRefreshedEvent事件
可以自定义一个监听器监听该事件,当监听器方法被回调时说明IOC容器已经完全初始化
@Component
public class ListenerTest implements ApplicationListener<ContextRefreshedEvent> {
@Override
public void onApplicationEvent(ContextRefreshedEvent event) {
System.out.println("容器初始化完毕");
}
}
四. SpringAOP
¥1. 什么是AOP?为什么要使用AOP
AOP(Aspect-Oriented Programming:面向切面编程)能够将那些与业务无关,却为业务模块所共同调用的逻辑或责任(例如事务处理、日志管理、权限控制等)封装起来,便于减少系统的重复代码,降低模块间的耦合度,并有利于未来的可拓展性和可维护性。
2. Spring Aop的相关术语
- Joinpoint(连接点):指那些被拦截到的点,在 spring 中这些点指的是方法,因为 spring 只支持方法类型的连接点。
- Pointcut(切入点):指我们要对哪些 Joinpoint 进行拦截的定义。例如业务层实现类中被增强的方法都是切入点,切入点一定是连接点,但连接点不一定是切入点。
- Advice(通知/增强):指拦截到 Joinpoint 之后所要做的事情。
- Introduction(引介):引介是一种特殊的通知,在不修改类代码的前提下可以在运行期为类动态地添加一些方法或 Field。
- Weaving(织入):是指把增强应用到目标对象来创建新的代理对象的过程。spring 采用动态代理织入,而 AspectJ 采用编译期织入和类装载期织入。
- Proxy(代理):一个类被 AOP 织入增强后,就产生一个结果代理类。
- Target(目标):代理的目标对象。
- Aspect(切面):是切入点和通知(引介)的结合。
3.Spring Aop有哪些通知类型及相关注解?
- @Before前置通知
- @AfterThrowing异常通知:方法抛出异常
- @AfterReturning返回通知:方法必须正确返回结果
- @After后置通知:无论是否正常执行都会执行
- @Around环绕通知
4. SpringAOP和AspectJ的区别和联系
区别:
- AOP是一种编程思想,而SpringAOP和AspectJ都是实现AOP的方式
- Spring AOP 属于运行时增强,而 AspectJ 是编译时增强。 Spring AOP 基于代理,而 AspectJ 基于字节码操作。
- AspectJ 相比于 Spring AOP 功能更加强大,但是 Spring AOP 相对来说更简单
联系:
- Spring AOP 已经集成了 AspectJ ,AspectJ 应该算的上是 Java 生态系统中最完整的 AOP 框架了。,
- 通过@EnableAspectJAutoProxy使得Spring AOP支持AspectJ 风格的语法
5. 静态代理与动态代理的区别
- AspectJ是静态代理的增强,所谓静态代理,就是AOP框架会在编译阶段生成AOP代理类,因此也称为编译时增强,他会在编译阶段将AspectJ(切面)织入到Java字节码中,运行的时候就是增强之后的AOP对象。
- Spring AOP使用的动态代理,在每次运行时在内存中通过操作字节码临时为方法生成一个AOP对象,这个AOP对象包含了目标对象的全部方法,并且在特定的切点做了增强处理,并回调原对象的方法。
- 静态代理与动态代理区别在于生成AOP代理对象的时机不同,相对来说AspectJ的静态代理方式具有更好的性能,但是AspectJ需要特定的编译器进行处理,而Spring AOP则无需特定的编译器处理。
五. SpringAOP原理及源码剖析
1. ¥Spring Aop的基本原理
Spring AOP是基于动态代理的,如果要代理的对象,实现了某个接口,那么Spring AOP会使用JDK Proxy,去创建代理对象,而对于没有实现接口的对象,Spring AOP会使用Cglib生成一个被代理对象的子类来作为代理
2. JDK动态代理的使用
动态代理实现步骤:
- 创建一个实现接口InvocationHandler的类,它必须实现 invoke(Object proxy, Method method, Object[] args)方法
第一个参数obj一般是指代理类,method是被代理的方法,如上例中的request(),args为该方法的参数数组
- 创建被代理的类以及接口
- 通过Proxy的静态方法
newProxyInstance(ClassLoaderloader, Class[] interfaces, InvocationHandler h)创建一个代理
第一个参数是类加载器,第二个参数是代理接口的class文件,第三个接口是上面实现的InvocationHandler
- 通过代理调用方法
TargetInterface newProxyInstance = (TargetInterface) Proxy.newProxyInstance(Target.class.getClassLoader(),
new Class[]{TargetInterface.class},
new InvocationHandler(){
@Override
public Object invoke(Object proxy, Method method, Object[] args){
return method.invoke(new Target(), args);
}
);
¥3. JDK动态代理的原理
- 根据接口信息为其方法生成字节码生成代理类的字节码(.class)
- 该字节码反编译后的类
- 继承自Proxy并实现了代理的接口
- 在构造器中传入了创建的InvocationHandler h
- 对接口方法的实现变为
this.h.invoke(this, m, new Object[] { paramString });
即通过代理类调用接口的方法实际上是去调用InvocationHandler中的invoke实现,从而实现了代理功能
- 该字节码反编译后的类
- 通过传入的类加载器加载该字节码生产Class对象
- 通过反射该Class对象生成代理类,将InvocationHandler作为构造器参数传入
¥4. JDK动态代理和CGLIB动态代理
- JDK动态代理只提供接口的代理,不支持类的代理。核心InvocationHandler接口和Proxy类,Proxy为目标类生成一个继承自Proxy符合某一接口的代理对象,并在调用目标方法时通过传入的InvocationHandler 通过invoke()方法反射来调用目标类中的代码,动态地将横切逻辑和业务编织在一起。
- 如果代理类没有实现接口,那么Spring AOP会选择使用CGLIB来动态代理目标类。CGLIB(Code Generation Library),是一个代码生成的类库,可以在运行时动态的生成指定类的一个子类对象,并覆盖其中特定方法并添加增强代码,从而实现AOP。CGLIB是通过继承的方式做的动态代理(生成的代理对象继承自目标对象)。
¥5. AOP进行代理的时机
- 当自定义了TargetSource,则在bean实例化前完成Spring AOP代理并且直接发生短路操作,返回bean
- 正常情况下,都是在bean初始化后进行Spring AOP代理
- 遇到循环依赖时,在三级缓存中提前曝光代理
¥6.¥AOP原理详解**
- @EnableAspectJAutoProxy向容器中导入AnnotationAwareAspectJAutoProxyCreator组件
- 该组件实质为一后置处理器,对postProcessAfterInitialization方法进行了实现,当bean完成初始化后调用该方法开始AOP代理
- 该方法中调用wrapIfNecessary()方法;
- 获取拦截器链(先获取所有Advisor,然后在所有Advisor中挑选适用的Advisor(该bean的拦截器,利用pointcut进行匹配))
- 创建代理对象
- 将Target(目标对象)、Interface(代理接口)、Advice(增强)保存在AdvisedSupport中
- 根据目标对象对类型选择具体的动态代理策略JdkDynamicAopProxy(JDK或Cglib,这里以JDK为例)
- JdkDynamicAopProxy根据AdvisedSupport中保存的属性创建代理对象
Proxy.newProxyInstance(classLoader, proxiedInterfaces, this);
,这里传入this是因为JdkDynamicAopProxy实现了InvocationHandler,并实现了invoke()方法 - invoke()主要包括三个步骤:
- 获取之前创建的该类的拦截器链
- 将拦截器统一封装成ReflectiveMethodInvocation
- 执行拦截器链
- 返回代理对象
- 对目标方法进行调用时,实质执行JdkDynamicAopProxy的invoke()方法,即执行拦截器链
- 在拦截器链中通过责任链设计模式来保证各个通知的执行顺序
7. Cglib是怎么操作字节码的,ASM操作字节码的原理
CGLIB包的底层是通过使用一个小而快的 字节码处理框架ASM,来转换字节码并生成新的类。除了CGLIB包, 脚本语言例如Groovy和BeanShell,也是使用ASM来生成java的 字节码。当然不鼓励直接使用ASM,因为它要求你必须对JVM内部结构包括class文件的格式和指令集都很熟悉。
ASM可以以二进制的方式创建一个类的字节码,对于ClassWriter的每一个方法的调用会创建类的相应部分,例如调用visit方法会创建一个类的声明部分,调用visitMethod方法就会在这个类中创建一个新的方法,调用visitEnd方法表明对于该类的创建已经完成了。它最终会通过toByteArray方法返回一个数组,这个数组包含了整个class文件的完整字节码内容。
六. Spring声明式事务
1. Spring中的事务隔离级别
- ISOLATION_DEFAULT:这是个 PlatfromTransactionManager 默认的隔离级别,使用数据库默认的事务隔离级别。
- ISOLATION_READ_UNCOMMITTED:读未提交,允许另外一个事务可以看到这个事务未提交的数据。
- ISOLATION_READ_COMMITTED:读已提交,保证一个事务修改的数据提交后才能被另一事务读取,而且能看到该事务对已有记录的更新。
- ISOLATION_REPEATABLE_READ:可重复读,保证一个事务修改的数据提交后才能被另一事务读取,但是不能看到该事务对已有记录的更新。
- ISOLATION_SERIALIZABLE:一个事务在执行的过程中完全看不到其他事务对数据库所做的更新。
¥2.Spring的事务传播行为
Spring事务的传播行为指多个事务同时存在的时候,spring如何处理这些事务的行为
- 支持当前事务的情况:
- PROPAGATION_REQUIRED(默认):如果当前没有事务,就创建一个新事务,如果当前存在事务,就加入该事务,该设置是最常用的设置。
- PROPAGATION_SUPPORTS:支持当前事务,如果当前存在事务,就加入该事务,如果当前不存在事务,就以非事务执行。‘
- PROPAGATION_MANDATORY:支持当前事务,如果当前存在事务,就加入该事务,如果当前不存在事务,就抛出异常。
- 不支持当前事务的情况:
- PROPAGATION_REQUIRES_NEW:创建新事务,如果当前存在事务,则把当前事务挂起。
- PROPAGATION_NOT_SUPPORTED:以非事务方式执行操作,如果当前存在事务,就把当前事务挂起。
- PROPAGATION_NEVER:以非事务方式执行,如果当前存在事务,则抛出异常。
- 其他情况:
- PROPAGATION_NESTED:如果当前存在事务,则在嵌套事务内执行。如果当前没有事务,则按REQUIRED属性执行。
- 在外部方法未开启事务的情况下Propagation.NESTED和Propagation.REQUIRED作用相同,修饰的内部方法都会新开启自己的事务,且开启的事务相互独立,互不干扰。
- 如果外部方法开启事务的话,Propagation.NESTED修饰的内部方法属于外部事务的子事务,外部主事务回滚的话,子事务也会回滚,而内部子事务可以单独回滚而不影响外部主事务和其他子事务。
2.5 require和require_new的区别
现在在事务1中包含事务2和事务3,事务2的传播属性是REQUIRED_NEW,事务3的传播属性是REQUIRED
事务1开始执行,当执行到事务2,若事务2发生异常,则事务2自己回滚,换句话说,事务1不会回滚到自己的初始状态,而是由事务2自己回滚到自己的初始状态。
接下来继续执行事务3,由于事务3的传播属性是REQUIRED,所以当事务3发生异常时,事务1会回滚到初始状态
3. @Transactional(rollbackFor = Exception.class)注解了解吗?
- 当@Transactional注解作用于类上时,该类的所有 public 方法将都具有该类型的事务属性,同时,我们也可以在方法级别使用该标注来覆盖类级别的定义。如果类或者方法加了这个注解,那么这个类里面的方法抛出异常,就会回滚,数据库里面的数据也会回滚。
- 在@Transactional注解中如果不配置rollbackFor属性,那么事务只会在遇到RuntimeException的时候才会回滚,加上rollbackFor=Exception.class,可以让事务在遇到非运行时异常时也回滚。
4. @Transactional在什么场景下会失效
- @Transactional 应用在非 public 修饰的方法上:在Spring AOP 代理时,会检查目标方法的修饰符是否为 public,不是 public则不会获取@Transactional 的属性配置信息。
- @Transactional 注解属性 propagation 设置错误
- @Transactional 注解属性 rollbackFor 设置错误:Spring默认抛出了未检查unchecked异常(继承自 RuntimeException 的异常)或者 Error才回滚事务
- 同一个类中方法调用,导致@Transactional失效:由于使用Spring AOP代理造成的,因为只有当事务方法被当前类以外的代码调用时,才会由Spring生成的代理对象来管理。而在同一个类中的方法调用不会通过代理对象,因此事务拦截器无法捕获到事务注解。
- 异常被 catch捕获导致@Transactional失效
- 数据库引擎不支持事务
5. 事务的超时属性
timeout 事务的超时时间,默认值为-1(不会超时)。如果超过该时间限制但事务还没有完成,则自动回滚事务。
6. 事务只读属性
- 对于只有读取数据查询的事务,可以指定事务类型为 readonly,即只读事务。只读事务不涉及数据的修改,数据库会提供一些优化手段,适合用在有多条数据库查询操作的方法中。
- 适用场景:如果你一次执行多条查询语句,例如统计查询,报表查询,在这种场景下,多条查询 SQL 必须保证整体的读一致性,否则,在前条 SQL 查询之后,后条 SQL 查询之前,数据被其他用户改变,则该次整体的统计查询将会出现读数据不一致的状态,此时,应该启用事务支持
7. @Transactional注解的实现原理
- @EnableTransactionalManagement注解给IOC容器中注册了InfrastrutureAdvisorAutoProxyCreator后置处理器和BeanFactoryTransactionAttributeSourceAdvisor事务增强器
- 在bean完成初始化后,该后置处理器会去判断该类是否添加了@Transactional注解,如果添加了,则把增强器添加到该bean的拦截器链中
- 在执行目标方法时,会调用增强器的TransactionInterceptor的invoke方法,在其实现逻辑中,先开启事务,然后执行目标方法,如果出现异常,则获取到事务管理器进行回滚操作,如果正常执行,则利用事务管理器提交事务
七. SpringBoot
1. Spring Boot 的优点
- Spring Boot 不需要编写大量配置文件,可以自动导入组件和默认配置,简化开发,提升效率。
- Spring 引导应用程序可以很容易地与Spring 生态系统集成,只需导入相关的Spring Boot Starters即可。
- Spring Boot 应用程序提供嵌入式 HTTP 服务器,如 Tomcat 和 Jetty,可以轻松地开发和测试 web 应用程序。
2. 什么是 Spring Boot Starters?
- Spring Boot Starters 是一系列依赖关系的集合,通过导入Starter依赖可直接导入该服务所需的所有依赖。
- Spring Boot Starters包含六该服务的组件和默认配置,做到开箱即用
3. Spring Boot 常用的两种配置文件
- 可以通过 application.properties或者 application.yml 对 Spring Boot 程序进行简单的配置。如果,你不进行配置的话,就是使用的默认配置。
- YAML 是一种人类可读的数据序列化语言。相比于 Properties 配置的方式,YAML 配置的方式更加直观清晰,简介明了,有层次感。
4. Spring Boot 常用的读取配置文件的方法
- 通过 @value 读取比较简单的配置信息
@Value("${name}")
String name;
- 通过@ConfigurationProperties读取并与 bean 绑定
@ConfigurationProperties(prefix = "library")
- @PropertySource读取指定的 properties 文件
@PropertySource("classpath:website.properties")
¥5. @SpringBootApplication 注解
package org.springframework.boot.autoconfigure;
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan(excludeFilters = {
@Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),
@Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) })
public @interface SpringBootApplication {
......
}
可以把 @SpringBootApplication看作是 @Configuration、@EnableAutoConfiguration、@ComponentScan 注解的集合。这三个注解的作用分别是:
- @EnableAutoConfiguration:启用 SpringBoot 的自动配置机制
- @ComponentScan: 扫描被@Component (@Service,@Controller)注解的 bean,注解默认会扫描该类所在的包下所有的类。
- @Configuration:允许在上下文中注册额外的 bean 或导入其他配置类
6. Spring Boot 的自动配置原理
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage
@Import({AutoConfigurationImportSelector.class})
public @interface EnableAutoConfiguration {
String ENABLED_OVERRIDE_PROPERTY = "spring.boot.enableautoconfiguration";
Class<?>[] exclude() default {};
String[] excludeName() default {};
}
- @EnableAutoConfiguration 注解通过 Spring 提供的 @Import 注解导入了AutoConfigurationImportSelector类。
- AutoConfigurationImportSelector类中getCandidateConfigurations方法会将所有自动配置类的信息以 List 的形式返回。这些配置信息会被 Spring 容器作 bean 来管理。
- 自动配置类通过@Conditional 注解决定是否加入容器中。如@ConditionalOnClass(指定的类必须存在于类路径下),@ConditionalOnBean(容器中是否有指定的 Bean),@ConditionalMissingOnBean(容器中不能有指定的 Bean)
7. Json相关注解
- @JsonIgnoreProperties 作用在类上用于过滤掉特定字段不返回或者不解析。
- @JsonIgnore一般用于类的属性上,作用和上面的@JsonIgnoreProperties 一样。
//生成json时将userRoles属性过滤
@JsonIgnoreProperties({"userRoles"})
public class User {
private String userName;
@JsonIgnore
private List<UserRole> userRoles = new ArrayList<>();
}
- @JsonFormat用来格式化 json 数据
@JsonFormat(shape=JsonFormat.Shape.STRING, pattern="yyyy-MM-dd'T'HH:mm:ss.SSS'Z'", timezone="GMT")
private Date date;
- @JsonUnwrapped扁平化对象