前言
一说Spring AOP大家肯定不陌生,它作为Spring Framwork的两大基石之一,在Spring的产品线中有着大量的应用。相信小伙伴们在平时工作的项目中,自己也写过类似的AOP代码。
那么本文主要从Spring AOP运行过程上,结合一定的源码整体上介绍Spring AOP的一个运行过程。
知其然,知其所以然,相信我们使用起来才更有底气。
什么是AOP
AOP是Spring框架面向切面的编程思想,AOP采用一种称为“横切”的技术,将涉及多业务流程的通用功能抽取并单独封装,形成独立的切面,在合适的时机将这些切面横向切入到业务流程指定的位置中。
请注意,本文所指、所讲的AOP,只代表AOP在Spring中的应用
另外,需要说明的是,本文对AOP最基本的概念、和最基本的使用,将不再占用篇幅说明了,因为默认你已经掌握了这些概念和基础知识
AOP能做什么
Spring框架的AOP机制可以让开发者把业务流程中的通用功能抽取出来,单独编写功能代码。在业务流程执行过程中,Spring框架会根据业务流程要求,自动把独立编写的功能代码切入到流程的合适位置。
比如:登录校验、日志输出等等。。。
前面我们已经大篇幅得聊了Spring IoC技术的原理和源码分析,那么本篇文章将从实践到理论上分析一下Spring得另外一大强大基石技术:Spring AOP
环境准备
导入jar包,基于之前得基础spring-web基础环境,还需导入如下jar包
<!-- AOP相关Jar包 此包其实一般都可以不显导入,但aspectjweaver这个jar是必须的--> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-aop</artifactId> <version>${spring.framework.version}</version> </dependency> <dependency> <!-- 大名鼎鼎得@Aspect/@Pointcut系列的注解 都在此处 --> <groupId>org.aspectj</groupId> <artifactId>aspectjweaver</artifactId> <version>1.9.2</version> </dependency>
对于jar包的导入,这里做一个说明:像spring-core、spring-context、spring-beans这几个基础的jar包,一般都是不需要显示的导入的。因为只要你使用到了它的功能包比如spring-web、spring-aop等都会自动帮你导入进来。
另外说明一点:导入spring-context就自动导入了aop包。而我们最常导入的spring-webmv包,它其实包含了非常多的jar,都不建议再重复导入了。放过pom.xml,让它更清爽点吧
创建一个切面:
@Aspect public class HelloAspect { // 只拦截service层的所有方法 @Pointcut("execution(* com.fsx.service.*.*(..)) ") public void point() { } @Before("point()") public void before() { System.out.println("this is from HelloAspect#before..."); } @After("point()") public void after() { System.out.println("this is from HelloAspect#after..."); } @AfterReturning("point()") public void afterReturning() { System.out.println("this is from HelloAspect#afterReturning..."); } @AfterThrowing("point()") public void afterThrowing() { System.out.println("this is from HelloAspect#afterThrowing..."); } // 此处需要注意:若写了@Around方法,那么最后只会执行@Around和@AfterReturning 其它的都不会执行 //@Around("point()") //public void around() { // System.out.println("this is from HelloAspect#around..."); //} }
这里需要说明几点
- Spring自己没有定义关于切面相关的注解,而是使用来自org.aspectj这个Jar包里面的注解的(但是没有用它的技术解析,这点需要明白)
- org.aspectj.lang.annotation还有很多其余的注解,但是这里列出Spring只支持的注解如下:
- @Aspect、@Before、@After、@AfterReturning、@AfterThrowing、@Around 其余的注解Spring都是不予解析的(由AspectJ内部技术去解析)
把切面加入容器 和 开启AspectJ得支持(二者缺一不可)
@Component @EnableAspectJAutoProxy @Aspect public class HelloAspect { ... }
本文为了简便,就直接采用@Component以及直接把@EnableAspectJAutoProxy写在切面类上面了,但是大多数情况我们都用@Bean的方式注册切面,且@EnablXXX写在对应的Config类上,方便管理(我是推荐这么做的,但是本文仅仅是为了方便而已)
至于为什么我可以这么乱写也都能生效,这个在前面博文中都有重点分析过,不明白的可以出门左拐~~~
执行结果如下:
this is from HelloAspect#before... this is my method~~ this is from HelloAspect#after... this is from HelloAspect#afterReturning...
从执行结果来看,我们的切面已经生效了。并且我们也能看得到各通知
的执行时机
附:
@Override public Object hello() { System.out.println("this is my method~~"); // 此处特意把this输出来对比,请务必注意差别 System.out.println(this.getClass()); //class com.fsx.service.HelloServiceImpl System.out.println(beanFactory.getBean(HelloService.class).getClass()); //class com.sun.proxy.$Proxy32 是JDK的动态代理了 return "service hello"; }
此处做一步输出,我们发现this代表还是本对象。而Spring容器内的HelloServiceImpl已经是一个JDK得动态代理对象了(HelloServiceImpl实现了接口)
@After和@AfterReturning的区别如下:
try{ try{ //@Before method.invoke(..); }finally{ //@After } //@AfterReturning }catch(){ //@AfterThrowing }
- 它俩得执行时机不同,@After先执行,@AfterReturning后执行
- @AfterReturning它能拿到目标方法执行完的返回值,但是@After不行
- @After它在finnaly里面,所以它不管怎么样都会执行(哪怕目标方法抛出异常),但是@AfterReturning如国目标方法没有正常return(比如抛出异常了),它是不会执行的
AspectJ 框架它定义的通知类型有 6 种:
- 前置通知 @Before 相当于 BeforeAdvice
- 后置通知 @AfterReturning 相当于 AfterReturningAdvice
- 环绕通知 @Around 相当于 MethodInterceptor
- 抛出通知 @AfterThrowing 相当于 ThrowAdvice
- 最终通知 @After 不管是否异常,该通知都会执行
- 引介增强:org.springframework.aop.IntroductionInterceptor,表示在目标类中添加一些新的方法和属性。
引介增强是一种比较特殊的增强类型,它不是在目标方法周围织入增强,而是为目标类创建新的方法和属性,所以引介增强的连接点是类级别的,而非方法级别的。通过引介增强,可以为目标类创建实现某接口的代理。
引介增强的配置与一般的配置有较大的区别:首先,需要指定引介增强所实现的接口;其次,由于只能通过为目标类创建子类的方式生成引介增强的代理,所以必须将proxyTargetClass设置为true。
从上面可以看出,@AspectJ的方式相比较于传统的方式, 多了一个最终通知
切点类型:Spring提供了六种类型切点
- 静态方法切点:org.springframework.aop.support.StaticMethodMatcherPointcut是静态方法切点的抽象基类,默认情况下它匹配所有的类。(NameMatchMethodPointcut提供简单字符串匹配方法签名,AbstractRegexpMethodPointcut使用正则表达式匹配方法签名。) 它不考虑方法入参个数、类型匹配
- 动态方法切点:org.springframework.aop.support.DynamicMethodMatcherPointcut是动态方法的抽象基类,默认情况下它匹配所有的类 它会考虑方法入参个数、类型匹配
- 注解切点:org.springframework.aop.support.annotation.AnnotationMatchingPointcut实现类表示注解切点。使用AnnotationMatchingPointcut支持在Bean中直接通过JDK 5.0注解标签定义切点
- 表达式切点:org.springframework.aop.support.ExpressionPointcut接口主要是为了支持AspectJ切点表达式语法而定义的接口。 这个是最强大的,Spring支持11种切点表达式
- 流程切点:org.springframework.aop.support.ControlFlowPointcut实现类表示控制流程切点。ControlFlowPointcut是一种特殊的切点,它根据程序执行堆栈的信息查看目标方法是否由某一个方法直接或间接调用,以此判断是否为匹配的连接点。
- 复合切点:org.springframework.aop.support.ComposablePointcut实现类是为创建多个切点而提供的方便操作类。它所有的方法都返回ComposablePointcut类。
切面类型
切面的分类:
- Advisor:代表一般切面,它仅包含一个Advice。这个切面太宽泛,一般不会直接使用。
- PointcutAdvisor:代表具有切点的切面,它包含Advice和Pointcut两个类。
- IntroductionAdvisor:代表引介切面。引介切面是对应引介增强的特殊的切面,它应用于类层面上。
PointcutAdvisor主要有6个具体的实现类:
- DefaultPointcutAdvisor:最常用的切面类型
- NameMatchMethodPointcutAdvisor:通过该类可以定义按方法名定义切点的切面
- RegexpMethodPointcutAdvisor:对于按正则表达式匹配方法名进行切点定义的切面,可以通过扩展该实现类进行操作。
- StaticMethodMatcherPointcutAdvisor:静态方法匹配器切点定义的切面,默认情况下,匹配所有的目标类。
- AspectJExpressionPointcutAdvisor:用于AspectJ切点表达式定义切点的切面
- AspectJPointcutAdvisor:用于AspectJ语法定义的切面。