1. 前言
因为后面要学习sb(springboot)和sc(springcloud),所以需要学习一些相关的注解和了解他们底层代码实现,例如@import注解,Aware接口,生命周期。
通过前面几个章节的学习,你会发现无论springboot还是springcloud的很多知识都是来源于spring相关的知识,章节还有一些内容没有补充完整,后续如果有时间会逐步更新完成!!!
2.组件注册
我们知道spring得IOC容器中存储了很多类的实例化对象,那么下面介绍几种往IOC容器中注册实体类的方式
2.1 xml配置文件实例化实体类(方式一)
首先新建spring.xml 文件,通过bean 标签实例化实体类
2.通过ClassPathXmlApplicationContext获取实体类
缺点:大型项目的实体类会非常多,那么配置文件会变得非常的臃肿,而且也不易于维护。
2.2 使用@ Configuration和@bean(方式二)
Spring提供了配置实体类的第二种方式,就是不用通过书写xml文件,通过两个注解就可以达到2.1 的功能。
- 编写配置类,配置bean
通过AnnotationConfigApplicationContext获取实体类
这里是new了一个AnnotationConfigApplicationContext对象,以前new的ClassPathXmlApplicationContext对象,的构造函数里面传的是配置文件的位置,而现在AnnotationConfigApplicationContext对象的构造函数里面传的是配置类的类型
2.3 @ComponentScan-自动扫描组件&指定扫描规则(方式三)
**实际上这个注解跟前面两种方式是配合使用的,避免书写ClassPathXmlApplicationContext或者AnnotationConfigApplicationContext** **获取IOC容器。**
我们知道,在实际开发中我们是不会通过ClassPathXmlApplicationContext这样的代码方式获取对象,而是通过包扫描的方式进行实例化对象并注入IOC容器中
他的扫描规则是:以下这几个注解都是继承自@Component
@controller(给web层的注解)
@service(给serivce层加的注解)
@repository(给dao层加的注解)
@component(给java类加注解,老版本spring只有这一个注解)
只要书写了上面四个注解的类,那么会自动装配到ioc容器中。Id默认是类名首字母小写
1. @ComponentScan注解的结构
1.这个注解上,也是可以指定要排除哪些包或者是只包含哪些包来进行管理:里面传是一个Filter[]数组。
2.Value :就相当于spring的xml配置文件-
2.用例
–那么我们使用配置类的方式实现component-sacn同样的功能
1.首先在配置文件类,添加扫描的范围
2.添加几个注解类-
@controller(UserController)
@service(UserService)
@repository (UserDao)
@component(ComponentTest)
3.书写IOC
输出:
发现-扫描进入IOC容器的bean的id默认是:类名首字母小写
4.使用Filter去除某些注解类
根据注解的方式排除,排除使用@Controller注解注解的类
–注意在使用includeFilters 扫描只包含那些组件的时候,要禁用spring默认全局扫描(跟配置文件一样,也是需要禁用的)
例子4
3. 扩展
我们打开Component注解的源码,发现他是:
多了Repeatable注解,也就是说明,这个Component注解是可以多次重复用的
那么你可能会问,如果不是jdk1.8,那么怎么书写多个扫描策略呢?
也就是说,我们可以在配置类,使用ComponentScans注解,配置多个扫描策略
例子:
跟 { 例子4 } 效果一样
4.1 FilterType 过滤规则
下面我们注重讲解一下,CUSTOM 自定义实现类
需要先实现 TypeFilter
1.首先定义一个扫描规则类
- 配置类,实现自定义过滤规则
表示 – 扫描的类中如果包含er那么就会被过滤掉(注意:他是会取扫描com.kingge下面的所有类-包括哪些没有被注解,注解的类也会被扫描)
2.4 @Import注解
有三种使用方式
第一种写法:直接在import注解中配置需要导入的类
他在IOC容器中的id是:(全类名)
缺点:如果有多个类需要注入IOC,那么代码量就很长
第二种实现方式:自定义导入逻辑,批量导入,只需要返回需要导入的全类名数组
实现ImportSelector 类
配置类上使用
这样 pp就注入到了IOC 容器中
第三种方式:ImportBeanDefinitionRegistrar 实现这个类。
例子:
配置类引用
调用:
2.4.1 总结
@Import[快速的给容器中导入一个组件]
(1)、 @Import(要导入容器中的组件);容器中就会自动的注册这个组件,id默认是全类名
(2)、 ImportSelector :返回需要的组件的全类名的数组;
(3)、 ImportBeanDefinitionRegistrar : 手动注册bean到容器中
前面学习的springboot中,用到了该注解的次数很多。
2.5 Factorybean 工厂bean
例子:
1.实现这个工厂bean
getObject 方法:当调用bean时候,调用这个方法获取bean实例。
getObjectType:返回对象类型
isSIngleton: 是否是单例。False-表示是多例。True-表示是单例。(如果配置类中配置@Scope注解,企图改变UserDao的单实例,无效,以isDingleto方法设置为准)
2.配置类配置
输出:
输出:
class com.kingge.dao.UserDao
false
、
第二个输出肯定是false
不过为什么第一个输出的是 Userdao的全类名而不是UserDaoFactoryBean的全类名呢?因为在构造的时候spring默认返回的就是getObjectType的值。
那么怎么获取这个工厂bean呢?spring提供了一个方式:加上&
输出:class com.kingge.utils.UserDaoFactoryBean
2.6 总结-组件注册
/**
\* 给容器中注册组件:
\* 1)、扫描+组件标注注解(@Controller/@Service/@Repository/@Component)
\* 【局限于要求是自己写的类,如果导入的第三方没有添加这些注解,那么就注册不上了】
*
\* 2)、@Bean[导入的第三方包里面的组件]
\* 3)、@Import[快速的给容器中导入一个组件]
\* (1)、 @Import(要导入容器中的组件);容器中就会自动的注册这个组件,id默认是全类名
\* (2)、 ImportSelector :返回需要的组件的全类名的数组;
\* (3)、 ImportBeanDefinitionRegistrar : 手动注册bean到容器中
*
\* 4)、使用Spring提供的FactoryBean(工厂bean)
\* (1)、默认获取到的是工厂bean调用getObject创建的对象
\* (2)、要获取工厂bean本身,我们需要给id前面加上一个“&”符号:&userDaoFactoryBean
第一种方式:一般用于自己定义的类,但是如果我们是通过导入第三方jar的方式导入了很多组件(类),但是我想把这些类注册到IOC容器中怎么办呢?这个时候就需要使用bean注解的方式注册组件。
第二种方式:可以实现自定义类或者第三方类的注入到IOC容器。缺点,那就是每实例化一个bean就得写个方法。这样代码量太多。
第三种方式:import标签(作用在配置类)
3.修饰Bean的相关注解
3.1 @Scope注解
他一般是和@Bean注解配套使用,标识实体类的作用范围。我们知道IOC容器中的实体类,默认是单实例的。
证明:
- 自定义IOC容器-实现配置类
2.获取 Person实体类
我们不难发现-这里输出的是true,所以spring扫描bean策略默认是单实例。
那么怎么修改这种作用域呢?
查看Scope注解源码,发现可以指定这四种类型的作用范围
第一个是多实例,第二个是:单实例(默认值)
第三个是:web环境下,用一个请求创建一次实例
第四个是:web环境下,同一个session创建一次实例
那么上诉代码只需要修改为
这样就是多实例。
总结:
单实例:在IOC容器启动的时候就已经实例化好Person(调用getPerson实例化),那么每次获取的时候直接从IOC容器中拿。
这段代码运行时候会去调用getPerson方法完成实例
多实例:IOC容器启动时,不会去实例化Person,而是每次获取的时候才会去调用getPerson获取对象。
3.2 @Lazy注解
和@Bean注解配套使用,解决单实例bean在IOC容器启动就马上创建实例的问题。
懒加载bean,这个只对于单实例的情况下才有用
也就是IOC容器初始化的时候,不会去调用getPerson,实例化Person。第一次获取的时候才会去创建,以后再使用该实例化,会使用以前获取的
3.3 @Conditional
根据满足某个特定的条件创建一个特定的Bean。
因为我们可能存在一个需求那就是,根据不同的业务场景我们会有选择性的实例化某些bean,那么就可以使用这个注解。
1.例子
需求:根据不同的系统实例化不同的bean。
输出:
l 增加需求 – 当使用windows系统时,在IOC容器中创建windows实体类,反之创建linux 实体类
\1. 实现两个 条件类
2.配置类
\2. 实例化IOC 容器
你会发现仅仅只是实例化了windows,linux实体类已经不见了,那么说明是条件生效了。
备注:
@Conditional注解是可以作用在配置类上面的,那么他的作用就是全局的条件,只有满足了这个条件,配置类里面的bean才能够实例化。(局部方法配置Conditional注解会失效)
4.生命周期
我们知道,Bean的生命周期是由IOC容器来管理的,那么我们也是可以自定义初始化方法和销毁方法。
Bean生命周期:bean创建-初始化-销毁,那么下面我们将来介绍,能够控制Bean生命周期的几种方式。
4.1. init-method、destory-method 管理bean生命周期
需要注意的是,单实例和多实例的情况下,bean生命周期是不一样的。单实例bean的生命周期全部托管给IOC容器,多实例部分托管。
1.在配置文件XML中:
这两个方法是来控制初始化和销毁的
2.代码控制初始化和销毁
单实例情况下
其实也就是在Bean注解,上填充init-method和destory-method方法
输出:
因为是单实例的原因-所以容器启动的时候就开始调用了无参构造器创建对象,然后调用init初始化方法,容器关闭时,调用销毁方法.
多实例情况下
我们把配置类获取Car对象的方法改为多实例的形式,观察输出。
输出
我们发现我们在关闭容器的时候,他并没有调用destory销毁实例,因为多实例的bean他是不归于容器管辖,需要我们自己手动销毁
3.总结
总的来说,bean在IOC容器的生命周期如下:
|
4.2 InitializingBean和DisposableBean 控制bean生命周期
1单实例情况下
1.新建Food类实现这两个接口
配置类扫描
测试
输出:
很明显单实例情况下,bean的生命周期是全部托管到IOC容器中。
2.多实例情况下
如果Food注入IOC容器时,选择多实例的方式的话,那么上面的案例在启动IOC容器时,不会有任何输出,因为多实例的情况下只有获取对象才会去做相关的初始化工作。
验证1:
没有任何输出。
验证2:
输出:
3.总结
很明显没有调用DisposableBean接口的destory方法和自定义的destory方法。
也就是说在多实例的情况下IOC容器只帮我们做创建和初始化bean的工作,但是销毁bean的工作他没有帮我们做,需要自己去实现。
4.3 @PostConstruct和@PreDestroy注解
这两个注解是作用在方法上面的。
可以使用JSR250规范里面定义的两个注解:
@PostConstruct :在bean创建完成并且属性赋值完成,来执行初始化方法
@PreDestroy :在容器销毁bean之前通知我们来进行清理工作
初始化容器
输出:
很明显-这两个注解的作用比4.2章节的两个接口的重载方法的调用更早,注意看官方的 注释说明
注意:这两个注解注解的方法,无返回值(void)
4.4 BeanPostProcessor 后置处理接口(重要)
我们发现上面三种管理bean生命周期的方式,他们的方法是没有入参和出参的。,下面这种方式提供了
BeanPostProcessor接口:bean的后置处理器,在bean初始化前后做一些处理工作,这个接口有两个方法:
postProcessBeforeInitialization:在初始化之前工作
postProcessAfterInitialization:在初始化之后工作
(1). 实现Food实体类
(2).配置类
(3). 启动容器查看输出
(4)输出
他没有销毁方法。
4.1-4.4总结
上面这四种方式调用顺序
对象构造器 –>> PostConstruct ->> afterPropertiesSet ->> init-method ->> ->> ->>PreDestroy注解 自定义实现的destory方法->> DisposableBean的destroy方法 ->>Food 自定义实现的destoryMethod方法
销毁:
@PreDestroy注解的 PreDestroy —》DisposableBean接口的destory —》 destroy-method
4.5BeanPostProcessor 原理
1.两个方法打上断点
Dubug方式启动IOC容器
2.查看方法栈调用
创建容器构造器
前置处理器调用的方法:调用getBeanPostProcessors()方法找到容器里面的所有的BeanPostProcessor,挨个遍历,调用BeanPostProcessor的postProcessBeforeInitialization方法,一旦调用postProcessBeforeInitialization方法的返回值为null的时候,就直接跳出遍历 ,后面的BeanPostProcessor 的postProcessBeforeInitialization也就不会执行了:
后置处理器调用的方法:调用getBeanPostProcessors()方法找到容器里面的所有的BeanPostProcessor,挨个遍历,调用BeanPostProcessor的postProcessAfterInitialization方法,一旦调用postProcessAfterInitialization方法的返回值为null的时候,就直接跳出遍历 ,后面的BeanPostProcessor 的postProcessAfterInitialization也就不会执行了:
2.BeanPostProcessor在springboot中的使用
查看该接口的实现类
这个接口,其实在spring的IOC容器中使用的频率是很多的,而且spring提供了很多实现类,例如如果我们想在bean中使用IOC容器的话,那么就可以使用
1.ApplicationContextAwareProcessor
给实体类,注入IOC容器。
ApplicationContextAware 接口,注入IOC容器
例如:Dog实体类需要使用到IOC容器,那么就可以实现这个接口
然后 他实际上是去 调用这个ApplicationContextAwareProcessor,方法,在创建Dog 对象他会去调用 postProcessBeforeInitialization 方法,判断实例化
然后判断当前Dog实体类是否实现了ApplicationContextAware,如果是,那么调用invokeAwareInterface注入,IOC容器。
最终去调用 Dog的 setApplicationContext 方法,赋值。
1. BeanValidationPostProcessor
实体类校验,后置处理器
2.InitDestroyAnnotationBeanPostProcessor
这个处理类,就是处理,我们3.3章节的两个注解。
3.AutowiredAnnotationBeanPostProcessor
这个类就是处理我们的Autoware注解的
4. BeanFactoryPostProcessor
BeanFactory的后置处理器,在BeanFactory的标准初始化之后调用
所有bean的定义已经保存加载到BeanFactory,但是bean的实例还未创建
运行IOC容器
查看输出:
很明显他是在bean实例创建之前执行的。
BeanFactoryPostProcessor原理:
1)、ioc容器创建对象
2)、invokeBeanFactoryPostProcessors(beanFactory);
如何找到所有的BeanFactoryPostProcessor并执行他们的方法;
1)、直接在BeanFactory中找到所有类型是BeanFactoryPostProcessor的组件,并执行他们的方法
2)、在初始化创建其他组件前面执行
5.BeanDefinitionRegistryPostProcessor
启动ioc容器查看输出
6. ApplicationListener
5.属性赋值
5.1 @Value注解
这个注解一般是作用在类的属性上面,他的作用等同于
那么他可以书写那些值呢?
第三种是取配置文件的数据
那么怎么使用 第三种方式赋值呢?下面讲解
5.2 @ PropertySource 注解
他的作用相当于XML的:
1.配置类添加配置注解
2.Person实体类
输出:
我们也可以通过IOC容器手动的去获取配置的信息
3.3 通过实现 EmbeddedValueResolverAware 获取属性值
l 见6.5 章节
6.自动装配
6.1 @Autowire、@Qualifier、@Primary(spring规范的注解)
在spring的项目中我们是经常这个Autowire来进行实体类之间的依赖注入,他的注入规则是:
\1. 默认按照类型去IOC容器中查找需要的实体类(例如UserDao.class)
\2. 如果找到多个同类型的实体类,那么他会根据属性名作为组件ID去进一步匹配。
例如:
然后IOC容器中有两个UserDao实例,一个是ID为userDao,一个ID为userDao1.
那么上面service注入的是哪一个呢?
答案:注入的是ID为userDao的实体类。如果想要注入userDao1,那么应该把属性名改为userDao1
@Qualifier,指定需要装配的ID,取消默认根据属性名去匹配。
默认是必须找到需要的依赖实体类,然后注入Service,否则就会报错,我们可以使用required属性来控制
@Primary :
这个注解是作用在被依赖的实体类(UserDao)上面,明确指定,当某个类(UserService)依赖这个实体类的时候,假设IOC容器中存在多个相同类型的被依赖类(UserDao)那么首选呗Primary注解的被依赖类。(
如果UserService同时使用了@Qualifier注解 ,那么@Primary的效果将会失效,以Qualifier注解需要的ID为主
@Autowire注解扩展
他可以标在构造器上,方法上
6.2 @Resource、@Inject(java规范的注解)
![1567417242591](spring注解-辅助学习springboot和springcloud\1567417242591.png)
@Resource注解
他的作用跟@Autowire注解的作用是一样的,默认根据属性名进行装配。Name属性可以更改装配的id
@Inject 的使用,需要添加依赖
支持@Primary功能,但是他没有属性
6.3 Aware接口 (重要)
自定义组件想要使用Spring容器底层的一些组件(ApplicationContext、BeanFactory…)
自定义组件实现xxxAware接口就可以实现,在创建对象的时候,会调用接口规定的方法注入相关的组件,把Spring底层的一些组件注入到自定义的bean中。 xxxAware等这些都是
利用后置处理器的机制,比如ApplicationContextAware 是通过ApplicationContextAwareProcessor来进行处理的。
如果我们想在自定义实体类中,使用IOC容器的context怎么办呢?
例子:我有在第四章节中 BeanPostProcessor中讲过。
例子:
1.下面我们就是用一个例子来详细讲解一下Aware接口的工作流程。
(1).实现一个entity,实现ApplicationContextAware 接口
实现该接口的setApplicationContext方法。
(2). 配置类,配置Blue实体类,实例化到IOC容器中
(3).获取IOC容器
(4)输出
2.源码分析
在setApplicationContext 打个断点。
发现他是去调用 ApplicationContextAwareProcessor 实体类,这个实体类实现了BeanPostProcessor 后置处理器。
2.执行postProcessBeforeInitialization 前置方法,判断当前的实体类是否继承了某些接口。做一些权限判断
3.然后调用 invokeAwareInterfaces ,紧接着调用实体类实现的 方法,注入IOC容器
6.4 @Profile注解
和Springboot的profile是一致的。
@profile注解是spring提供的一个用来标明当前运行环境的注解。我们正常开发的过程中经常遇到的问题是,开发环境是一套环境,qa测试是一套环境,线上部署又是一套环境。这样从开发到测试再到部署,会对程序中的配置修改多次,尤其是从qa到上线这个环节,让qa的也不敢保证改了哪个配置之后能不能在线上运行。
为了解决上面的问题,我们一般会使用一种方法,就是配置文件,然后通过不同的环境读取不同的配置文件,从而在不同的场景中跑我们的程序。
那么,spring中的@profile注解的作用就体现在这里。在spring使用DI来依赖注入的时候,能够根据当前制定的运行环境来注入相应的bean。最常见的就是使用不同的DataSource了。
下面-结合 properties配置文件的三种注入方式来讲解一下@Profile注解的用法
—-以作用在方法上,表示只要在当前设置的环境下才会往IOC容器中注册当前bean。
—-作用在类上面
类里面的所有bean,能够被注册到IOC容器中的条件是:只要开发环境满足了当前配置类上面的Prifile注解标识的环境。
例如:
主要开发环境是test 里面的bean才能够被注册。
-
6.5 @profile的使用
我们知道,如果在组件上标识了这个注解,那么如果没有激活,那么就不会被注册到IOC容器中。通过这个特性来过滤一些组件的注册。
@Profile(“default”) 是默认注册某个bean
1.配置类
2.测试
输出:
很明显,三个配置都没有被注册在IOC容器中,因为没有指定运行环境。
\3. 制定运行环境(第一种方式:虚拟机参数位置添加-Dspring.profiles.active=test)
这样
就会输出了。
\4. 制定运行环境(第二种方式-代码方式)
可以指定多个配置环境
输出:
7.AOP
什么叫AOP和他的作用
在程序运行期间,动态的将某段代码切入到指定方法运行时的指定时机运行,其实就是动态代理。
作用场景
可以在某个业务实现的过程前后,或者出现异常,进行一些额外业务的操作。例如当你调用add()方法进行加法运算的时候,我们可以在调用方法前,得到结果后,或者出现异常时,记录一些日志。以前我们传统的做法是,在方法里面打印日志(System.out.println),但是这样会造成耦合,而且我们也想把打印日志抽离成一个统一的模块。
1. 例子
Maven依赖:spring提供了对AOP的支持
(1)导入aop依赖
|
(2)MathCalculator.java
业务逻辑类:要求在业务方法运行时打印日志
(3):日志切面类:LogAspects.java
|
这四个方法,都是作用在MathCalculator的add方法,那么他们的切入点表达式都是一样的,为了避免重复书写,我们一般采用抽取公共切入点的方式,抽取出来,复用。---- 使用@PoinCut注解
切面类中的方法也称为通知方法:
前置通知(@Before):在目标方法运行之前运行
后置通知(@After):在目标方法运行之后运行,即使出现异常也会运行
返回通知(@AfterReturning):在目标方法正常返回之后运行
异常通知(@AfterThrowing):在目标方法运行出现异常之后运行
环绕通知(@Around):动态代理,手动推进目标方法的运行
(4)开启spring切面自动代理
**使用Spring的切面需要开启Spring的切面自动代理,只需要在配置类中加注解@EnableAspectJAutoProxy,Spring中有很多@EnableXxx(关于这点我们在springcloud中使用的最多,自动配置)注解,用来开启一些功能**
配置bean怎么区分哪个bean是切面类呢,它会看哪个类上有@Aspect注解,另外切面方法的执行仅对Spring容器中的bean起作用,对于我们自己new出来的对象是不起作用的,原因也很简单,我们自己创建的bean并没有被spring管理,也就没有为其设置切面方法等。 通过JoinPoint对象获取调用目标方法时的信息,比如方法名、参数等,使用returning指定用通知方法的哪个入参接收返回值,使用throwing指定用哪个入参接收异常,另外如果使用JoinPoint,则必须将其放在切面方法入参的第一个位置,否则会报错
(5)测试
正常计算
错误计算
给一个出现异常的 1 /0 运算处理,查看日志。
你会发现,无论是否出现异常 logStart 和 logEnd 都会正常输出,如果正常返回那么@AfterReturning标识的方法
会被调用,如果运算发生异常那么@AfterReturning标识的方法
不会被调用,而@AfterThrowing标识的异常处理方法会被调用
总结
2. AOP原理
通过上面的例子我们知道,实现AOP的关键点在于我么能使用@EnableAspectJAutoProxy注解,那么接下来我们查看一下这个注解到底做了什么工作
1.查看@EnableAspectJAutoProxy注解
两个属性的含义:
英文注解已经很详细了,这里简单介绍一下两个参数,一个是控制aop的具体实现方式,为true 的话使用cglib,为false的话使用java的Proxy,默认为false,第二个参数控制代理的暴露方式,解决内部调用不能使用代理的场景,默认为false
2.查看一下 AspectJAutoProxyRegistrar.java 到底导入了哪些类
很明显这个类是采用了ImportBeanDefinitionRegistrar的方式注册了某些类大oIOC容器中,那么我们看一下他到底注入了什么类。
|
一个AOP的工具类,这个工具类的主要作用是把AnnotationAwareAspectJAutoProxyCreator这个类定义为BeanDefinition放到spring容器中,这是通过实现ImportBeanDefinitionRegistrar接口来装载的,具体装载过程不是本篇的重点,这里就不赘述,我们重点看AnnotationAwareAspectJAutoProxyCreator这个类.
从类图是可以大致了解AnnotationAwareAspectJAutoProxyCreator这个类的功能.它实现了一系列Aware的接口,在Bean装载的时候获取BeanFactory(Bean容器),Bean的ClassLoader,还实现了order接口,继承了PorxyConfig,ProxyConfig中主要封装了代理的通用处理逻辑,比如设置目标类,设置使用cglib还是java proxy等一些基础配置.
而能够让这个类参与到bean初始化功能,并为bean添加代理功能的还是因为它实现了BeanPostProcessor这个接口.这个接口的postProcessAfterInitialization方法会在bean初始化结束后(赋值完成)被调用。
3.最顶部的抽象类:AbstractAutoProxyCreator
注意看bean初始化的方法
当我们开启了EbableAspectJAutoProxy后,每次Bean的装配时,都会执行这段逻辑.前面主要是校验是否需要对bean进行代理(特殊的类,和已经被代理),核心逻辑在后面几行.getAdvicesAndAdvisorsForBean方法来获取所有符合条件的切面,具体的实现在子类,这里是抽象方法,获取切面后就是创建代理:
TargetSource中存放被代理的对象,这段代码主要是为了构建ProxyFactory,将配置信息(是否使用java proxy,是否threadlocal等),目标类,切面,传入ProxyFactory中,而在ProxyFactory中,会通过createAopProxy()方法创建代理工厂DefaultAopProxyFactory,由代理厂生成具体的代理对目标类进行代理:
进入proxyFactory.getProxy(getProxyClassLoader());
的getProxy()
方法
跳到
进入createAopProxy()
,跳转到
我们可以查看AopProxy的都有哪些 ,在AOpProxy上按键:ctrl t,
很明显有我们熟悉的cglib和jdk、默认的实现
紧接着进入createAopProxy(this)
是个接口,查看他的默认实现类。
4. DefaultAopProxyFactory aop代理获取类
可以看到,在这里有我们在注解中设置的参数的判断逻辑,是创建java代理,还是cglib代理,有关cglib的讲解请看cglib的使用.
我们主要看一下JdkDynamicAopProxy的实现,因为我们没有设置@EnableAspectJAutoProxy(proxyTargetClass=true)
所以我们默认使用jdk自带实现。cglib其实差不多。
5. JdkDynamicAopProxy 默认切面代理类
|
findDefinedEqualsAndHashCodeMethods方法是为了查询被代理的接口是否包括equals和hashcode方法,这会影响到下面的调用。
可以看到InvocationHandler的实现就是this。我们看一下invoke方法的实现:
|
关键代码
|
构建代理链,因为一个方法可能有多个切点匹配上,这个时候就需要构建一个链式的执行结构。
进入getInterceptorsAndDynamicInterceptionAdvice()
方法
这里做了一个缓存,虽然new了一个对象作为key,但是对象的equals和hashcode方法都被重写了,所以没有问题,我们主要来看一下它是如何组装这个链式处理结构的:
进入getInterceptorsAndDynamicInterceptionAdvice()
方法,紧接着发现是一个接口,那么查看他的实现类
6.DefaultAdvisorChainFactory 处理链式切点
可以看到,它会遍历自己的所有切点,那这些advisor是从哪里来的呢:
还记得最开始,我们说过,AbstractAutoProxyCreator中通过getAdvicesAndAdvisorsForBean方法来装载切面,而这个是一个抽象方法,现在来看它的实现,在AbstractAdvisorAutoProxyCreator中:
|
|
可以看到,通过aspectJAdvisorsBuilder来将该类关心的所有的切面装载进来,并添加到父类的集合里面.aspectJAdvisorsBuilder里缓存了advisor的信息,拿到切面后,通过findAdvisorsThatCanApply方法来筛选合适的切面,之后对切面进行排序(如果实现了Order接口),然后返回切面的链表.
8.声明式事务@Transactional注解
1.前言
我们知道spring的事务管理分为两大部分:声明式和编程式,两种方式均为我们提供便捷的事务管理方法,各自优劣。
声明式事务
声明式的事务管理对业务代码基本0入侵,能够很好的把事务管理和业务代码剥离开来,提高代码扩展性和可读性但是控制的粒度只能是方法级别而且必须是public,同时还不能在一个类中调用等。
编程式事务
编程式事务则需要通过编写具体的事务代码来获得事务的管理能力,TransactionTemplate,或者直接使用PlatformTransactionManager,好处是控制粒度小,没有太多限制,坏处就是对业务代码有入侵,如果事务需要嵌套或者事务本身很繁琐,使用编程式则会十分麻烦。
这里讲述的是声明式事务,因为他比较常用。而且两种方式的源码其实是一样的。
2. 环境搭建
1.1导入相关依赖:数据源、数据库驱动、Spring-jdbc模块
1.2配置数据源、JdbcTemplate(Spring提供简化数据库操作的工具)操作数据
1.3新建PersonDao、PersonService
1.4 测试
插入成功
1.5 测试事务
修改Service方法,故意暴露一个异常
我们运行测试,发现还是插入成功,那么怎么阻止这种行为呢?添加事务
1.6 添加事务
- 给insertUser方法添加注解- @Transactional
@EnableTransactionManagement 开启基于注解的事务管理功能
配置事务管理器来控制事务
事务添加成功
1.8 再次运行测试
发现插入失败,满足事务的原子性。
1.7 源码分析
|
3.源码深入分析
未完待续
9 总结
通过使用AOP和声明式事务,我们知道了一个套路,如果我们想使用某项功能,例如上面的aop和声明式事务、或者以后的springcloud的eureka、zull、feign等等功能,都遵循一下三点:
1.导入功能组件相关的依赖
2.在配置类开启组件(@EnableXXXX)
3.在关键位置标示使用的地方(例如@Aspect、@Transactional)
所以以后需要在spring中使用某个组件,一般都是遵循这样的思路
#