Spring AOP与AspectJ
概念
AOP的全称为Aspect-Oriented Programming,即面向切面编程。
想象你是汉堡店的厨师,每一份汉堡都有好几层,这每一层都可以视作一个切面。现在有一位顾客想要品尝到不同风味肉馅的汉堡,如果按照传统的方式,你需要做多个汉堡,每个汉堡只有肉馅是不一样的,但是你每做一个汉堡都要重新制作面包。而聪明的厨师只需做一个汉堡,仅将肉饼那一层分成不同口味的几个区域,这样你就不需要再重复制作面包了。
对于程序员也是一样的,有多少个接口就要写或复制多少代码那一定是无法忍受的,我们只想关心不同的那部分。
尽管想通俗来讲,但是还是要去熟悉专业的概念:
Aspect
:切面,类似于Java类声明,里面会有Pointcut
和Advice
Joint point
:连接点,在程序执行过程中某个阶段点Pointcut
:切入点,切面与程序流的交叉点,往往此处需要处理Advice
:通知或增强,在切入点处所要执行的代码。可以理解为切面类中的方法。Target object
:目标对象,指所有被通知的对象。Proxy
:代理,将通知应用到目标对象后,被动态创建的对象。Weaving
:织入,将切面代码插到目标对象上,从而生成代理对象的过程。
别担心,我们之后会通过代码来慢慢理解。
AOP的实现
AOP的实现主要分为静态代理和动态代理,在本教程中静态代理我们用AspectJ
,而动态代理用Spring AOP
。
静态代理在编译期就确定了代理类,而动态代理需要靠反射机制动态生成代理类。
Spring AOP动态代理有两种实现方式:一种是JDK动态代理,这种方式需要接口;另一种是CGLib动态代理,这种方式不依赖接口。
有了以上的知识,我们开始写代码,首先新创建一个Maven项目top.cairbin.test2
,如果你不会请回去看之前的章节。
然后在pom.xml
的<dependencies></dependencies>
之间添加依赖包
我们去实现一个IUser
接口,要求接口内有两个方法void addUser()
和void deleteUser()
接下来定义一个实现该接口的User
类
我们定义一个切面类,该类中的两个方法void check()
和void log()
分别模拟权限检查和日志记录功能。
切面类如下所示
JDK动态代理
接下来创建代理类JdkProxy
,这个类实现了JDK动态代理的InvocationHandler
接口,并实现代理方法。
然后尝试在App.java
的main
方法中使用它们
CGLib动态代理
我们不妨尝试使用CGLib来玩一下
创建一个新的类,名称为CglibProxy
,并实现接口MethodInterceptor
以及相应的方法
尝试调用下
我们仔细观察CGLib的这几段代码,在CglibProxy
类中我们并没有用到IUser
这个接口,而是返回Object,然后外面也就是调用者那里强制转换为IUser
类型!
不妨再“懒”一些,我们借助Spring的依赖注入,从Spring的容器中直接返回增强后的实现了IUser
接口的对象试一试。
首先在resources/AppCtx.xml
中编写Bean的配置(这里有坑,如果行不通回前面的文章看看)
修改下MyAspect
类,并实现接口MethodInterceptor
App
类中的main
方法调用如下
AspectJ静态代理
使用AspectJ静态代理,我们重新设计下MyAspect
切面类
对于切入点注解@Pointcut("execution(* top.cairbin.test2.*.*(..))")
表示对top.cairbin.test2
这个包下的所有类的所有方法生效。
我们再来看看Spring中的Advice
的几种类型:
org.springframework.aop.MethodBeforeAdvice
,前置通知,目标方法执行前实施,可用于权限管理。org.springframework.aop.AfterReturningAdvice
,后置通知,在目标方法执行后实施,用于关闭文件流、上传文件、删除临时文件等。org.aopalliance.intercept.MethodInterceptor
,环绕通知,在目标方法实施前后,一般用于日志或事务管理。org.springframework.aop.ThrowsAdvice
,异常抛出通知,在抛出异常后实施。org.springframework.aop.IntroductionInterceptor
,引介通知,在目标类中添加新方法和属性,可以应用于修改老版本程序。
我们还要实现自动扫描和依赖注入,看看我们的User
类
自然也少不了AppCtx.xml
的配置
在main
方法中测试下,为了清楚,我这里仅调用了addUser
一个方法
我们发现当环绕通知与前置通知和后置通知同时使用的时候,优先级如下:
- 环绕通知开始
- 前置通知
- 方法执行
- 后置通知
- 环绕通知结束
想必到了这里,你对AspectJ的使用有了一定的了解,但是对于相应的注解还是不太清楚,请仔细阅读下方图片中的表格,结合一开始的术语体会下: