一.Spring4和Spring5 AOP执行流程差别
advice
1.Spring4
正常执行:@Before======@After======@AfterReturning
异常执行:@Before======@After======@AfterThrowing
2.Spring5
正常执行:@Before(前置通知)=======@AfterReturning(返回后通知)=======@After(后置通知)
异常执行:@Before========@AfterThrowing====@After
二.AOP的底层实现原理
AOP是IOC的扩展功能,先有IOC,AOP是整个流程的一个新增扩展点:BeanPostProcessor(后置处理方法)
1.代理对象的创建过程(advice,切面,切点)
2.通过jdk或cglib的方式来生成代理对象。
面向切面编程,与面向对象编程相辅相成。在OOP中以类为基本单元,而AOP是aspect切面。实现方式有静态代理和动态代理。
(1)静态代理:编译阶段就可生成AOP代理类。
(2)动态代理:运行时生成。分为JDK(利用反射,并且要求被代理的类必须实现一个接口,核心是invocationHandler接口和proxy类)和CGLIB(没有实现接口,就用整个代理目标类,通过继承实现,如果一个类是final修饰无法动态代理)
三.循环依赖及三级缓存
什么是循环依赖?多个bean之前相互依赖,形成一个闭环。spring容器一般指默认的单例Bean里。
两种注入方式(基于构造方法和单例模式下的setter的依赖注入,非单例无法处理)对循环依赖的影响,避免使用构造方法会抛出异常,spring解决不了,直接抛出异常。我们AB循环依赖问题只要A的注入方式是setter且singleton就不会有循环依赖问题。
spring的单例对象初始化分为:createbeanInstance实例化(调用构造方法);populateBean填充属性(多bean的依赖属性注入);InitializeBean初始化(init方法)。依赖注入就发生在第一二步。
默认的单例(singleton)的场景是支持循环依赖的,不报错;原型(prototype)的场景是不支持循环依赖的,会报错。
关键在于先把非完整状态的对象优先赋值,即只完成了实例化但是还未初始化。提前暴露不完整对象的引用,先赋值再等后续操作完成赋值。当所有对象完成实例化和初始化操作后,还要把完整对象放到容器里,此时容器里有两个状态,完成实例化还未初始化,完整状态。因为都在容器中,所有要使用不同的map来进行存储,就有了一级缓存和二级缓存。如果一级缓存有了,那么二级缓存中就不会存在同名对象。一级放的是完整对象,二级缓存是非完整对象。
为啥需要三级缓存?三级缓存的value类型是ObjectFactory,是一个函数式接口。存在的意义是保证整个容器的运行工程中同名的bean对象只能有一个。
如果一个对象被代理,或者要生成代理对象,那么要优先生成一个普通对象。两者不能再容器中共存,当一个对象要被代理时,使用代理对象覆盖普通对象。当调用对象时,会回调机制,优先判断对象是否需要被代理。lambda表达式执行对象覆盖。
因此,所有bean对象再创建时优先放到三级缓存中,在后续使用中,如果需要被代理则返回代理对象,不然直接返回普通对象。
三级缓存:createBeanInstance之后,addSingletonFactory
二级缓存:第一次从三级缓存确定对象是代理对象还是普通对象,同时删除三级缓存;
一级缓存:生成完整对象后放一级,删除二三级缓存。
spring内部通过三级缓存解决缓存依赖(DefaultSingletonBeanRegistry)
第一级缓存(也叫单例池)singletonObjects:存放已经经历了完成生命周期的bean对象
第二级缓存: earlySingletonObjects,存放早期暴露出来的bean对象,bean的生命周期未结束,也就是已经实例化(申请了内存空间)但未初始化(还没放值)的bean放入该缓存里。
第三级缓存:singletonFactories, 存放可以生成bean的工厂
refresh()方法就是加载容器初始化的方法。
四,spring的事务如何回滚
就是怎么实现?(分为编程式事务管理和声明式事务管理)
总:spring的事务是由AOP来实现的,首先要生成具体的代理对象,然后按照AOP的流程执行具体操作。正常要通过通知完成核心功能,但是事务不是,是通过一个TransactionInterceptor(事务拦截器),然后调用invoke(动态代理)实现具体的逻辑。
分:1.准备工作,解析各个方法上事务相关属性,根据属性判断是否开启新事务。
2.当开启时,获取数据库连接,关闭自动提交,开启事务
3.执行具体SQL逻辑操作
4.操作失败通过completeTransactionAfterThrowing,完成事务回滚,具体回滚通过doRollBack方法实现。
5.没有意外,commiteTransactionAfterThrowing,实现doCommite
五.spring的传播特性
1.使用Spring的好处
(1)轻量,基本版本2M;
(2)控制反转;
(3)AOP;面向切面编程,提取公共业务逻辑,和服务分开;
(4)容器;
(5)MVC框架;
(6)事务管理
(7)异常处理。
2.依赖注入
不必创建对象,但必须描述怎么创建。描述配置文件中哪些组件需要哪些服务,有IOC容器将它们装配在一起。
(1)构造函数注入
(2)setter注入
(3)接口注入
在spring framework中,仅使用构造函数和setter注入。
3.
BeanFactory(主要的有DefaultListableBeanFactory的功能)将利用BeanDefinition来生成bean对象,BeanDefinition相当于BeanFactory的原材料,bean对象就是产品。
4.构造函数和setter注入区别
5.spring的配置方式
(1)XML配置,以bean标签开头;
(2)注解配置,需要在spring配置文件中启动;
(3)基于Java API配置:通过@Bean和@Configuration
6.spring的bean的作用域
(1)singleton:默认是单例的
(2)prototype:每次请求都会创建一个新的bean实例
(3)request:每次HTTP请求产生一个新的bean,仅在当前HTTP request内有效;
(4)session:在一个session中,一个session对应一个实例。
(5)Global-session
最后三个仅当用户使用applicationcontext可用。
7.如何理解IOC和DI
一个是思想,一个是设计模式。
IOC不用我们自己创建对象,交给bean工厂创建管理,通过面向接口编程的方式来实现对业务组件的动态依赖。IOC是spring争对解决程序耦合存在的。
实际中,spring通过配置文件(如XML或properties)指定需要实例化的Java类,包括类的一组初始化值,通过加载读取配置文件,用spring提供的方法(getbean())获取进行初始化的实例对象。
传统Java SE程序设计,我们直接在对象内部通过new进行创建对象,是程序主动去创建依赖对象;而IoC是有专门一个容器来创建这些对象,即由Ioc容器来控制对象的创建;谁控制谁?当然是IoC 容器控制了对象;控制什么?那就是主要控制了外部资源获取(不只是对象包括比如文件等)。有了IoC容器后,把创建和查找依赖对象的控制权交给了容器,由容器进行注入组合对象,所以对象与对象之间是松散耦合,这样也方便测试,利于功能复用,更重要的是使得程序的整个体系结构变得非常灵活。
为何是反转,哪些方面反转了:有反转就有正转,传统应用程序是由我们自己在对象中主动控制去直接获取依赖对象,也就是正转;而反转则是由容器来帮忙创建及注入依赖对象;为何是反转?因为由容器帮我们查找及注入依赖对象,对象只是被动的接受依赖对象,所以是反转;哪些方面反转了?依赖对象的获取被反转了。
DI—Dependency Injection,即“依赖注入”:是组件之间依赖关系由容器在运行期决定,形象的说,即由容器动态的将某个依赖关系注入到组件之中。依赖注入的目的并非为软件系统带来更多功能,而是为了提升组件重用的频率,并为系统搭建一个灵活、可扩展的平台。通过依赖注入机制,我们只需要通过简单的配置,而无需任何代码就可指定目标需要的资源,完成自身的业务逻辑,而不需要关心具体的资源来自何处,由谁实现。
IoC的一个重点是在系统运行中,动态的向某个对象提供它所需要的其他对象。这一点是通过DI(Dependency Injection,依赖注入)来实现的。比如对象A需要操作数据库,以前我们总是要在A中自己编写代码来获得一个Connection对象,有了 spring我们就只需要告诉spring,A中需要一个Connection,至于这个Connection怎么构造,何时构造,A不需要知道。在系统运行时,spring会在适当的时候制造一个Connection,然后像打针一样,注射到A当中,这样就完成了对各个对象之间关系的控制。A需要依赖 Connection才能正常运行,而这个Connection是由spring注入到A中的,依赖注入的名字就这么来的。那么DI是如何实现的呢? Java 1.3之后一个重要特征是反射(reflection),它允许程序在运行的时候动态的生成对象、执行对象的方法、改变对象的属性,spring就是通过反射来实现注入的。
DI(依赖注入)其实就是IOC的一种类型,还有一种是DL(Dependency Lookup依赖查找)。 DL由Martin Fowler 在2004年初的一篇论文中首次提出的。他总结:控制的什么被反转了?就是:获得依赖对象的方式反转了。
容器所为SpringIOC的核心它主要有两种:
BeanFactory:BeanFactory为IOC容器提供了基础功能,Spring文档中提到,当前该类仅仅是为了向后兼容老的版本,除非你有更好的原因否则就应该使用第二种容器。
ApplicationContext:通过API文档可以知道,ApplicationContext是BeanFactory的子接口,并且从文档中也可以看到ApplicaionContext除了包含有BeanFactory的所有功能还支持了更多的功能。
ApplicationContext的实现有四种方式:
FileSystemXmlApplicationContext:加载配置文件的时候采用的是项目的路径。
ClassPathXmlApplicationContext:加载配置文件的时候根据ClassPath位置。
XmlWebApplicationContext:在Web环境下初始化监听器的时候会加载该类。
AnnotationConfigApplicationContext:根据注解的方式启动Spring 容器。
8.将一个类声明为bean的注解
一般使用@autowired注解自动装配bean,要想把类标识成可用于@autowired注解自动装配的bean的类,可用以下:
(1)@component,通用注解
(2)@repository ,对应持久层即Dao层
(3)@service,对应服务层,需要用到Dao层
(4)@controller对应MVC的控制层,接受用户请求并调用service层返回的数据给前端页面。
9.bean的生命周期
10,spring的内部bean
只有将bean用作另一个bean的属性时,才能将bean声明为内部bean。
不谈扩展性,spring开始时只有两个流程,对象的实例化和属性填充。
对象的实例化就是使用类构造器进行创建对象。而一个类中可能有很多的构造器,那么怎么知道使用哪个构造器进行实例化对象呢?
在实例化前,需要确定候选的构造器,即构造器推断。
构造器推断
功能描述:寻找beanClass中所有符合候选条件的构造器 。
负责角色:AutowiredAnnotationBeanPostProcessor
候选条件:构造器上加了@Autowired注解,@value和@Inject注解,也可以用setter
推断流程:
1、获取beanClass中的所有构造器进行遍历,使用的是beanclass.getDeclaredConstructors()判断构造器上是否标识@Autowired注解,是则将构造器添加到候选构造器集合中
2、并进一步判断Autowired注解中required属性是否为true(默认为true),是则表示该beanClass已存在指定实例化的构造器,不可再有其他加了@Autowired注解的构造器,如果有则抛出异常。
3、如果Autowired注解中required属性为false,则可继续添加其他@Autowired(required=false)标识的构造器
4、如果候选构造器集合不为空(有Autowired标识的构造器),并且beanClass中还有个空构造器,那么同样将空构造器也加入候选构造器集合中。
5、如果候选构造器集合为空,但是beanClass中只有一个构造器且该构造器有参,那么将该构造器加入候选构造器集合中。
以上判断条件很多,但始终是围绕这一个逻辑:这个beanClass中有没有被Autowired
标识的构造器,有的话required是true还是false,如果是true, 那其他的构造器都不要了。如果是false,那想加多少个构造器就加多少个。
那要是没有autowired标识的构造器呢?
都要有兜底的,这里就是看beanclass中是不是只有一个构造器且是有参的。
那如果只有无参,那就是没有候选构造器,那spring会在没有候选构造器时默认使用无参构造器。
如果有很多个构造器,spring也不知道用哪个,同样进入兜底使用无参构造器(不抛出异常)。
得到构造器,下面对对象进行实例化
功能描述:根据候选构造器集合中的构造器优先级对beanclass进行实例化。
负责角色:ConstructorResolver
需要关注:1.构造器的优先级是怎样?2.如果有多个构造器,但是部分构造器需要的bean不存在于spring容器中会发生说明?也就是出现异常怎么处理?
1.构造器优先级怎么样的?
答:多个构造器称为构造器的重载,重载的方式有两种:参数的数量不同,参数的类型不同。
在Spring中,优先级则是由构造器的修饰符(public or private)和参数的数量决定。
规则如下:
(1)、public修饰的构造器 > private修饰的构造器(非public)
(2)、修饰符相同的情况下参数数量更多的优先
/ 如果一个是public,一个不是,那么public优先
int result = Boolean.compare(Modifier.isPublic(e2.getModifiers()), Modifier.isPublic(e1.getModifiers()));
// 都是public,参数多的优先
return result != 0 ? result : Integer.compare(e2.getParameterCount(), e1.getParameterCount());
2.spring出现异常怎么处理?
这一部分的具体过程如下:
(1)、将根据优先级规则排序好的构造器进行遍历
(2)、逐个进行尝试查找构造器中的需要的bean是否都在Spring容器中,如果成功找到将该构造器标记为有效构造器,并立即退出遍历
(3)、否则记录异常继续尝试使用下一个构造器
(4)、当所有构造器都遍历完毕仍未找到有效的构造器,抛出记录的异常
(5)、使用有效构造器进行实例化
到这里,beanClass实例化了一个bean,接下来需要做的便是对bean进行赋值,但我们知道,Spring中可以进行赋值的对象不仅有通过@Autowired
标识的属性,还可以是@Value
,@Resource
,@Inject
等等。
为此,Spring为了达到可扩展性,将获取被注解标识的属性的过程与实际赋值的过程进行了分离。
该过程在Spring中被称为处理beanDefinition
处理beanDefinition
功能描述:处理beanDefinition的元数据信息
负责角色:
1、AutowiredAnnotationBeanPostProcessor:处理@Autowird
,@Value
,@Inject
注解
2、CommonAnnotationBeanPostProcessor:处理@PostConstruct
,@PreDestroy
,@Resource
注解
这两个后置处理器的处理过程十分类似, 我们以AutowiredAnnotationBeanPostProcessor
为例:
1、遍历beanClass中的所有Field
、Method
(java中统称为Member
)
2、判断Member
是否标识@Autowird
,@Value
,@Inject
注解
3、是则将该Member
保存,封装到一个叫做InjectionMetadata
的类中
4、判断Member
是否已经被解析过,比如一个Member
同时标识了@Autowired
和@Resource
注解,那么这个Member
就会被这两个后置处理器都处理一遍,就会造成重复保存
5、如果没被解析过就将该Member
放置到已检查的元素集合中,用于后续填充属性时从这里直接拿到所有要注入的Member
现在,beanclass中可注入属性都找到了,接下来就要进行属性填充。
属性填充
功能:对bean中需要自动装配的属性进行填充
角色:
1、AutowiredAnnotationBeanPostProcessor
2、CommonAnnotationBeanPostProcessor
我们同样以AutowiredAnnotationBeanPostProcessor
为例
1、使用beanName为key,从缓存中取出InjectionMetadata
2、遍历InjectionMetadata
中的checkedElements
集合
3、取出Element
中的Member
,根据Member
的类型在Spring中获取Bean
4、使用反射将获取到的Bean设值到属性中
在Spring中,Bean填充属性之后还可以做一些初始化的逻辑,比如Spring的线程池ThreadPoolTaskExecutor
在填充属性之后的创建线程池逻辑,RedisTemplate
的设置默认值。
Spring的初始化逻辑共分为4个部分:
1、invokeAwareMethods:调用实现特定Aware
接口的方法,主要是得到bean所在容器名字和classcloader,beanfactory
2、applyBeanPostProcessorsBeforeInitialization:初始化前的处理
3、invokeInitMethods:调用初始化方法
4、applyBeanPostProcessorsAfterInitialization:初始化后的处理
初始化逻辑
Spring的初始化逻辑共分为4个部分:
1、invokeAwareMethods:调用实现特定Aware
接口的方法
2、applyBeanPostProcessorsBeforeInitialization:初始化前的处理
3、invokeInitMethods:调用初始化方法
4、applyBeanPostProcessorsAfterInitialization:初始化后的处理
初始化前的处理
功能:调用初始化方法前的一些操作
角色:
1、InitDestroyAnnotationBeanPostProcessor:处理@PostContrust注解
2、ApplicationContextAwareProcessor:处理一系列Aware接口的回调方法
这一步骤的功能没有太大的关联性,完全按照使用者自己的意愿决定想在初始化方法前做些什么,我们一个一个来过。
1.InitDestroyAnnotationBeanPostProcessor
这里的逻辑与属性填充过程非常相似,属性填充过程是取出自动装配
相关的InjectionMetadata
进行处理,而这一步则是取@PostContrust
相关的Metadata
进行处理,这个Metadata
同样也是在处理BeanDefinition过程解析缓存的
初始化方法
在Spring中的初始化方法有两种
1、实现InitializingBean
接口的afterPropertiesSet
方法
2、@Bean
注解中的initMethod
属性
调用顺序是先调用afterPropertiesSet
再initMethod
1、判断Bean是否实现InitializingBean
接口
2、是则将Bean强转成InitializingBean
,调用afterPropertiesSet
方法
3、判断BeanDefinition中是否有initMethod
4、是则找到对应的initMethod
,通过反射进行调用
初始化后的处理
在Spring的内置的后置处理器中,该步骤只有ApplicationListenerDetector
有相应处理逻辑:将实现了ApplicationListener接口的bean添加到事件监听器列表中
如果使用了Aop相关功能,则会使用到
AbstractAutoProxyCreator
,进行创建代理对象。
ApplicationListenerDetector
的流程如下
1、判断Bean是否是个ApplicationListener
2、是则将bean存放到applicationContext
的监听器列表中
11.spring装配
当bean在容器中组合在一起,称为bean装配。spring容器需要知道需要什么bean以及如何使用依赖将bean绑定起来。
通过检查beanfactory,不同模式:
(1)no;(2)byName;(3)byType(4)构造函数,有大量参数;(5)autodetect,尝试通过构造函数使用auto wired装配,不能就尝试通过bytype自动装配。
12.如果出现同名bean
(1)同一配置文件的,以最上面定义为准
(2)不同配置文件的,以后解析的为准,覆盖
(3)同文件的@bean和@componentscan,以bean生效;
13.spring中单例bean的线程安全问题
线程安全问题都是由全局变量及静态变量引起的。如果只读没问题,但是多线程同时写有问题。
无状态和有状态bean
有状态就是有实例变量的对象,可以保存数据,是非线程安全的。适用于prototype原型模式。
无状态就是一次操作,不能保存数据。是线程安全的。适用于单例模式。
spring适用threadlocal解决线程安全问题。
@value($())是在XML中找Key,@value(“”)是直接指定字符串,
IOC的理解:
autowire:
Spring有个自动装配的机制会帮我们自动的把userDaoImpl注入到userServiceImpl中,通过配置autowire可以实现。直接举个栗子。可以看到我们在配置文件中删掉了userDaoImpl,但是Spring一样会帮我们成功注入。但是要注意的是,如果我们选择的是byType,那么对于存在两个相同Class的userDaoImpl Spring会不知道要注入哪一个,因此会抛出异常。
针对上述的问题,Spring提供了两个方法来解决。第一种如果你在id为userDaoImpl2上面添加了autowire-candidate,那么就说明如果自动装配的时候出现冲突,就会忽略当前这个bean即userDaoImpl2。而primary则是反过来,它表示的是一种优先级,如果遇到冲突情况优先使用带有该属性的bean。
@Autowired,@Resource;
@Autowired跟@Resource都可以作用在构造方法,sette方法,以及属性值上面注入。它们的区别在于:
Autowired是Spring的规范,而Resource是java的规范;
Autowired默认按类型匹配但是可以搭配@Qualifier来指定名称,而Resource默认按名称;
Configuration表明当前这个类对应一个XML文件,另外在Junit中我们不再使用ClassPathXMLApplicationContext,而是改用了AnnotationConfigApplicationContext。因为我们不再通过XML的方式来获取Bean。
Spring在代码中获取bean的几种方式
方法一:在初始化时保存ApplicationContext对象
方法二:通过Spring提供的utils类获取ApplicationContext对象
方法三:继承自抽象类ApplicationObjectSupport
方法四:继承自抽象类WebApplicationObjectSupport
方法五:实现接口ApplicationContextAware
方法六:通过Spring提供的ContextLoader
springboot bean扫描路径
1:默认扫描启动类所在路径下全部的beanservice
2:能够在启动类中添加注解,手动指定扫描路径:vi
@ComponentScan(basePackages = {"com.xxx.service1.*","com.xxx.service2.**"})
1.@Component: 注解表明一个类会作为组件类,并告知Spring要为这个类创建bean,使用 @Component注解在一个类上,表示将此类标记为Spring容器中的一个Bean。(相当于创建对象)
2.@Bean是将组件注册到Bean,让IOC容器知道这个组件存在。(相当于创建对象)
一定要配合@configuration使用
@Component 作用于类,@Bean作用于方法。
如果你想要将第三方库中的组件装配到你的应用中,在这种情况下,是没有办法在它的类上添加@Component注解的,因此就不能使用自动化装配的方案了,但是我们可以使用@Bean,当然也可以使用XML配置。
3.@Autowire:是组件和组件相互调用的时候,自动从ioc中取出来需要用的组件。(调用对象)
比如Service,Controller,Dao的关系,这三个组件都分别加上了注册的注解:@Service,@Controller,@Component,ioc中已经有了注册信息,但是Service要用到Dao操作数据,所以在Service中的Dao头上就要用@Autowired来给Dao自动赋值,来供Service用,同理,Controller中也要用到Service,那么就要在Service上边加上@Autowired