前言
- 本次将会总结5篇对于spring aop的知识点,次要围绕:AOP应用篇、AOP原理篇、事务应用篇、事务原理篇、事务同步器应用篇 五个主题进行论述。
AOP原理篇分为两个主题:
1、源码中是如何将咱们定义的各种告诉与指标办法绑定起来的2、咱们的aop代理对象的执行程序是怎么的
- 事务原理篇次要是以一个事务流传机制的案例来解说spring事务在底层的执行过程
- 而本次总结的外围为:如何应用Spring AOP,以及理解相干
告诉
的个性(相干的概念在这就不再累述了,大家能够参考其余文章),废话不多说,咱们间接开始吧!
一、测试案例
1.1 预览测试案例我的项目构造
我的项目启动入口类Entry.java:
public class Entry { public static void main(String[] args) { AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class); ObjectService objectService = context.getBean(ObjectService.class); objectService.list("hi"); } // 配置扫描类,启动了aop性能 @Configuration @ComponentScan("com.eugene.sumarry.aop.csdn") @EnableAspectJAutoProxy(proxyTargetClass = false, exposeProxy = true) public static class AppConfig { } // 被这个注解标识的办法会被加强 @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.METHOD) public @interface AspectAnnotation { }}
接口类ObjectService.java
public interface ObjectService { String[] list(String str); String findOne();}
接口实现类ObjectServiceImpl.java
@Servicepublic class ObjectServiceImpl implements ObjectService { @Entry.AspectAnnotation @Override public String[] list(String str) { return new String[] {str, "avenger", "eug"}; } @Override public String findOne() { return "avengerEug"; }}
定义切面AspectDefinition.java
@Aspect@Componentpublic class AspectDefinition { /** * 指定蕴含@AspectAnnotation注解的办法才会被加强 */ @Pointcut("@annotation(com.eugene.sumarry.aop.csdn.Entry.AspectAnnotation)") public void pointcutAnnotation() { } /** * 前置告诉:执行指标办法前 触发 */ @Before(value = "pointcutAnnotation()") public void methodBefore(JoinPoint joinPoint) { String methodName = joinPoint.getSignature().getName(); System.out.println("前置告诉:办法名:" + methodName + ",参数" + Arrays.asList(joinPoint.getArgs())); } /** * 后置告诉:执行指标办法后触发 */ @After(value = "pointcutAnnotation()") public void methodAfter(JoinPoint joinPoint) { String methodName = joinPoint.getSignature().getName(); System.out.println("后置告诉:办法名:" + methodName + ",参数" + Arrays.asList(joinPoint.getArgs())); } /** * 返回告诉:指标办法执行完并返回参数后触发。 */ @AfterReturning(value = "pointcutAnnotation()", returning = "result") public void methodAfterReturning(JoinPoint joinPoint, Object result) { String methodName = joinPoint.getSignature().getName(); System.out.println("返回告诉:办法名:" + methodName + "," + "参数" + Arrays.asList(joinPoint.getArgs()) + "," + "返回后果:"); if (result instanceof String[]) { Arrays.stream((String[]) result).forEach(System.out::println); } else { System.out.println(result); } } /** * 异样告诉:指标办法抛出异样后触发 */ @AfterThrowing(value = "pointcutAnnotation()", throwing="ex") public void methodExceptionOccurred(JoinPoint joinPoint, Exception ex) { String methodName = joinPoint.getSignature().getName(); System.out.println("异样告诉:办法名:" + methodName + ",参数" + Arrays.asList(joinPoint.getArgs()) + ",异样信息:" + ex.getMessage()); }}
1.2 测试案例我的项目构造剖析
整个我的项目构造采纳java config的办法来启动spring,在AppConfig类中,定义了spring我的项目的外围性能:
1、@Configuration注解标识AppConfig是一个配置类
2、@ComponentScan指定了spring扫描的包门路为:com.eugene.sumarry.aop.csdn,这将阐明在此包下的所有spring辨认的注解都会被解析
3、@EnableAspectJAutoProxy注解示意导入了spring aop相干的组件,代表着aop性能启动了。其中,proxyTargetClass属性指定了aop应用哪种代理形式来加强指标类,其规定如下所示:
proxyTargetClass 含意 默认为false 应用的是jdk动静代理,最终代理对象和指标对象会实现同一个接口 设置为true 看指标类的构造,如果指标类实现了接口,这里采纳的还是jdk动静代理。如果指标类没有实现接口,将应用cglib代理。后续将在 AOP原理篇
中证实而将exposeProxy设置为true,则示意spring会将代理对象裸露到线程变量ThreadLocal中,具体有哪些场景,能够接着往下看。
而在切面定义类AspectDefinition中,咱们定义了一个切面,而切的点就是:如果spring中的bean中有办法被@AspectAnnotation注解标识了,那么这个bean的对应办法将会被加强,并且加强的逻辑蕴含:前置告诉、后置告诉、返回告诉、异样告诉这四个局部,这里应用告诉来形容可能不太形象,大家能够把它了解成4个钩子函数,其对应钩子函数的触发机会及特色如下表所示(也能够参考AspectDefinition类中的定义):
告诉类型(钩子函数) 触发机会 特色 前置告诉 调用指标办法前触发 只有程序没有重大问题并且定义了前置告诉则必定会触发 后置告诉 指标办法逻辑执行结束后触发,或指标办法外部抛异样 只有程序没有重大问题并且定义了后置告诉则必定会触发 返回告诉 当指标办法的返回值返回后触发 与异样告诉互斥,一山不容二虎 异样告诉 当指标办法执行过程中产生异样时触发 与返回告诉互斥,一山不容二虎 根据上述的介绍,咱们很容易可能发现,针对于ObjectServiceImpl类而言,list办法被加强了,findOne办法没有被加强。这代表着,当执行list办法时,会有两种状况产生。状况一:触发
前置告诉、后置告诉、返回告诉
。状况二:前置告诉、后置告诉、异样告诉
。 对于异样告诉和返回告诉互斥的起因,会在后续的AOP原理篇
中证实。
1.3 测试list办法的三种调用状况
1.3.1 指标对象的list办法外部无异样抛出
间接运行Entry.java类的main办法,失去如下后果
- 指标办法list无异样往外抛时,只触发
前置告诉、后置告诉、返回告诉
,合乎上述剖析的第一种状况。
1.3.2 指标对象的list办法外部抛出异样
将list办法改成一下,改成如下模样:
@Entry.AspectAnnotation@Overridepublic String[] list(String str) { int x = 1 / 0; return new String[] {str, "avenger", "eug"};}
间接运行Entry.java类的main办法,失去如下后果
- 指标办法list有异样往外抛时,只触发
前置告诉、后置告诉、异样告诉
,合乎上述剖析的第二种状况。应用程序也证实了返回告诉和异样告诉是互斥的,不能同时存在。(后续会在AOP原理篇
中证实)
1.3.3 在指标对象的list办法中调用另一个被加强的办法
革新下下面的ObjectServiceImpl.java类,将findOne办法也增加上
@Entry.AspectAnnotation
注解,革新后的构造如下所示:@Servicepublic class ObjectServiceImpl implements ObjectService { @Entry.AspectAnnotation @Override public String[] list(String str) { // 要在此处调用具备加强性能的findOne办法,如何做? return new String[] {str, "avenger", "eug"}; } // 此处增加@Entry.AspectAnnotation注解 @Entry.AspectAnnotation @Override public String findOne() { return "avengerEug"; }}
- 如上述的正文:要在list办法中调用具备加强性能的findOne办法,如何做?间接应用
this.findOne
可行吗?这里能够明确的告知:应用this.findOne的形式必定无奈调用到具备加强性能的findOne办法。这波及到aop生效的一些场景,要想理解这些场景,得理解aop的实现原理。这里后续再独自写篇AOP生效场景文章进行阐明。那么有哪些形式来实现这个需要呢?
形式1:本人依赖注入本人
如下代码所示:
@Servicepublic class ObjectServiceImpl implements ObjectService { // 本人依赖注入本人,此时注入的是一个代理对象 @Autowired ObjectService objectService; @Entry.AspectAnnotation @Override public String[] list(String str) { objectService.findOne(); return new String[] {str, "avenger", "eug"}; } @Entry.AspectAnnotation @Override public String findOne() { return "avengerEug"; }}
形式2:应用spring上下文从新获取bean
如下代码所示:
@Servicepublic class ObjectServiceImpl implements ObjectService, ApplicationContextAware { // 应用spring上下文。或者网上有许多的SpringContextUtils工具类,能够应用它们,但原理都一样,都实现了ApplicationContextAware接口 ApplicationContext applicationContext; @Override public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { this.applicationContext = applicationContext; } @Entry.AspectAnnotation @Override public String[] list(String str) { applicationContext.getBean(ObjectService.class).findOne(); return new String[] {str, "avenger", "eug"}; } @Entry.AspectAnnotation @Override public String findOne() { return "avengerEug"; }}
形式3:利用@EnableAspectJAutoProxy注解的exposeProxy属性
- 下面有介绍到exposeProxy属性,将exposeProxy设置为true的话,spring会将代理对象裸露到线程变量中。因而,在exposeProxy设置为true的状况下,持续批改ObjectServiceImpl类来实现加强办法list外部调用加强办法findOne的需要
其代码批改如下所示:
@Servicepublic class ObjectServiceImpl implements ObjectService { @Entry.AspectAnnotation @Override public String[] list(String str) { ((ObjectService) AopContext.currentProxy()).findOne(); return new String[] {str, "avenger", "eug"}; } @Entry.AspectAnnotation @Override public String findOne() { return "avengerEug"; }}
应用AopContext.currentProxy() api来获取spring 裸露在线程变量中的代理对象。因为代理对象是存在于线程变量中的,即ThreadLocal中,因而,咱们须要保障整个调用链不会切换线程,否则将无奈获取到spring裸露进去的代理对象。
不论是下面的哪种形式,最终的执行后果都是这样的:
在触发list办法的前置告诉后,就执行到了findOne办法,这也间接表明了,前置告诉是执行指标办法之前触发的,因为findOne是在指标办法list外部执行的,因而指标办法list的逻辑并还没有执行结束,要等到findOne办法的逻辑执行结束后才会触发list办法的后置告诉、返回告诉或异样告诉。其调用流程如下图所示:
1.4 基于1.3.3的形式3,测试list办法抛异样的几种状况
1.4.1 在调用findOne办法之前抛异样
革新ObjectServiceImpl.java类:
@Servicepublic class ObjectServiceImpl implements ObjectService { @Entry.AspectAnnotation @Override public String[] list(String str) { // 此处抛异样,还没调用到findOne办法 int x = 1 / 0; ((ObjectService) AopContext.currentProxy()).findOne(); return new String[] {str, "avenger", "eug"}; } @Entry.AspectAnnotation @Override public String findOne() { return "avengerEug"; }}
其运行后果为:
很显著:由上论断能够得悉,在调用findOne办法之前抛异样,仅仅是影响到了list办法而已。这也比拟好了解,因为我还没有调用到findOne办法,当然不会影响到findOne办法。
1.4.2 在调用findOne办法之后抛出异样
革新ObjectServiceImpl.java类:
@Servicepublic class ObjectServiceImpl implements ObjectService { @Entry.AspectAnnotation @Override public String[] list(String str) { ((ObjectService) AopContext.currentProxy()).findOne(); // 调用findOne办法之后抛异样 int x = 1 / 0; return new String[] {str, "avenger", "eug"}; } @Entry.AspectAnnotation @Override public String findOne() { return "avengerEug"; }}
其执行后果如下所示:
很显著,咱们在findOne办法执行之后抛异样也没有影响到findOne办法的执行,拿咱们之前画的流程图来阐明:
如图所示,在继续执行指标办法list残余的逻辑
节点处抛了异样,也就是上述的int x = 1 / 0;
这段代码,它仅仅影响到的只是list办法的逻辑,不会再影响到findOne办法,因为findOne办法的加强逻辑都曾经执行结束了。
- 说句额定话:如果有对Spring事务的REQUIRES_NEW流传机制比拟理解的话,应该也会呈现这种状况。假如咱们的list是默认流传机制REQUIRED,而findOne是REQUIRES_NEW流传机制。那么在这种状况下,findOne的事务会被提交,而list的事务会被回滚。其原理和上述的剖析统一,就是因为findOne是REQUIRES_NEW流传机制,它会额定创立一个新的事务,在findOne事务提交后继续执行list残余逻辑时抛异样,这时,findOne的事务曾经提交了,当然不会影响到findOne,所以此时仅仅影响到的是list办法。
1.4.3 在调用findOne办法时,在findOne办法外部抛出异样
革新ObjectServiceImpl.java类:
@Servicepublic class ObjectServiceImpl implements ObjectService { @Entry.AspectAnnotation @Override public String[] list(String str) { ((ObjectService) AopContext.currentProxy()).findOne(); return new String[] {str, "avenger", "eug"}; } @Entry.AspectAnnotation @Override public String findOne() { // 此处抛异样 int x = 1 / 0; return "avengerEug"; }}
其运行后果如下所示:
依据后果可知:它影响到了list和findOne办法,也就是说在findOne办法外部抛出的异样,会影响整个调用链上的办法,进而都触发了list和findOne的异样告诉。不晓得看到这里的小伙伴们心里有没有一点感觉:这十分像责任链的执行过程,如果咱们没有对每一个链上的节点做额定的异样捕获的话,只有有一个链抛出了异样,就会把异样一层一层的向上抛。 十分侥幸的是,在Spring的AOP的调用过程应用的就是责任链的设计模式。咱们将在下一篇AOP原理篇来进行阐明。
- 说句额定话:如果有对Spring事务的REQUIRED流传机制比拟理解的话,应该也会呈现外部办法抛异样最终会将整个调用链给回滚的状况。这种状况和咱们当初测试的这种用例的原理是统一的,因为两个办法应用的是同一个事务,最终异样会向上一层一层的抛,当事务管理器感知到由异样产生时,事务管理器就会做回滚操作。
二、总结
- 通过本次的学习,咱们理解了前置告诉、后置告诉、返回告诉、异样告诉实际上就是一个钩子函数的回调,而针对于
返回告诉和异样告诉互斥
以及Spring如何确定应用哪种代理形式来生成代理对象
的疑难,我将在后续的AOP原理篇证实。 - 在1.3和1.4章节中,针对aop的各种调用状况、抛异常情况都做了测试,还额定提了一嘴对于事务相干的知识点,如果这块不太明确的话,后续会在事务原理篇具体解释。
- 源码链接:点击查看
- 如果你感觉我的文章有用的话,欢送点赞、珍藏和关注。
- I'm a slow walker, but I never walk backwards