👉 spring AOP 💎
1 何为 Spring–AOP
AOP(Aspect-oriented-programming),面向切面编程。它与 OOP(面向对象编程)相辅相成。AOP中的基本单元是 Aspect (切面) |
术语
Aspect(切面)
aspect 由 point cut 和 advice 组成,它既包含了横切逻辑的定义,也包括了连接点的定义。 AOP的工作重心在于如何将增强织入目标对象的连接点上,这里包含两个工作: |
advice(增强)
由 aspect 添加到特定的 join point (即满足 point cut 规则的 join point )的一段代码。许多AOP框架,包括Spring AOP,会将advice模拟为一个拦截器(interceptor),并且在 join point 上维护多个 advice ,进行层层拦截。 |
join point(连接点)
程序运行中的一些时间点,在Spring AOP 中 join point 总是方法的执行点,即只有方法的连接点。 |
point cut (切入点)
在 Spring 中,所有的方法都可以认为是 join point ,但是我们并不希望在所有的方法上都添加 Advice ,而 point cut 的作用就是提供一组规则来匹配 join point ,给满足规则的 join point 添加 Advice。 |
introduction (引入)
在不修改代码的前提下,引入可以在运行期为类动态的添加额外的方法。 |
weaving (织入)
将切面应用到目标对象并导致代理对象创建的过程。 |
Advice 的类型
- Before Advice(前置通知) : 在join point前被执行的advice.虽然before advice是在join point前被执行,但是它并不能够阻止join point的执行,除非发生了异常(即我们在before advice代码中,不能人为地决定是否继续执行join point中的代码)
- After Return Advice(后置返回通知) : 在一个join point正常返回后执行的advice
- After Throwing Advice(异常通知) : 当一个join point 抛出异常后执行的advice
- After(Final) Advice(最终通知) : 无论一个join point是正常退出还是发生了异常,都会被执行的advice
- Around Advice(环绕通知) : 在join point前和joint point退出后都执行的advice.这个是最常用的advice
彻底理解 aspect 、join point 、point cut、advice
看完了上面的理论部分知识,我相信还是会有不少朋友感觉到AOP的概念还是很模糊,对AOP中的各种概念理解的还不是很透彻.其实这很正常,因为AOP中的概念是在是太多了,我当时也是花了老大劲才梳理清楚的.下面我以一个简单的例子来比喻一下AOP中 aspect , join point, point cut 与 advice 之间的关系。 |
让我们来假设一下,从前有一个叫爪哇的小县城,在一个月黑风高的晚上,这个县城中发生了命案。作案的凶手十分狡猾,现场没有留下什么有价值的线索。不过万幸的是,刚从隔壁回来的老王恰好在这时候无意中发现了凶手行凶的过程,但是由于天色已晚,加上凶手蒙着面,老王并没有看清凶手的面目,只知道凶手是个
男性,身高约七尺五寸
。爪哇县的县令根据老王的描述,对守门的士兵下命令说:凡是发现有身高七尺五寸的男性,都要抓过来审问
。士兵当然不敢违背县令的命令,只好把进出城的所有符合条件的人都抓了起来。来让我们看一下上面的一个小故事和AOP到底有什么对应关系.首先我们知道,在Spring AOP中 join point 指代的是所有方法的执行点,而point cut是一个描述信息,它修饰的是join point,通过point cut,我们就可以确定哪些 join point 可以被织入Advice。对应到我们在上面举的例子,我们可以做一个简单的类比,join point 就相当于爪哇的小县城里的百姓
,point cut就相当于老王所做的指控,即凶手是个男性,身高约七尺五寸
,而advice则是施加在符合老王所描述的嫌兼疑人的动作:抓过来审问
。为什么可以这样类比呢?
- join point --> 爪哇的小县城里的百姓:因为根据定义,
join point 是所有可能被织入advice的候选的点
,在Spring AOP中,则可以认为所有方法执行点都是 join point。而在我们上面的例子中,命案发生在小县城中,按理说在此县城中的所有人都有可能是嫌疑人。- point cut --> 男性,身高约七尺五寸:我们知道,
所有的方法(joint point)都可以织入 advice
,但是我们并不希望在所有方法上都织入advice,而pointcut 的作用就是提供—组规则来匹配 join point
,给满足规则的 join point 添加 advice
.同理,对于县令来说,他再昏庸,也知道不能把县城中的所有百姓都抓起来审问,而是根据凶手是个男性,身高约七尺五寸,把符合条件的人抓起来。在这里凶手是个男性,身高约七尺五寸就是一个修饰谓语,它限定了凶手的范围,满足此修饰规则的百姓都是嫌疑人,都需要抓起来审问.- advice --> 抓过来审问,
advice是一个动作,即一段 Java 代码,这段Java代码是作用于 point cut 所限定的那些 join point 上的
。同理,对比到我们的例子中,抓过来审问这个动作就是对作用于那些满足男性,身高约七尺五寸的爪哇的小县城里的百姓。- aspect -->
aspect是 point cut与advice的组合
,因此在这里我们就可以类比:"根据老王的线索,凡是发现有身高七尺五寸的男性,都要抓过来审问”这一整个动作可以被认为是一个aspect
。
2 使用@AspectJ支持
@AspectJ可以用 XML 的方式或以 注解 的方式来使用,无论哪种方式使用都必须要有 AspectJweaver 的依赖
使用Java Configuration 方式使用@AspectJ
@Configuration
@EnableAspectJAutoProxy
public class AppConfig{
}
使用XML方式使用@AspectJ
<aop:aspectJ-autoproxy />
定义切面(aspect)
但使用注解@Aspect 标注一个Bean后,那么spring框架就会自动收集这些bean ,并添加到Spring AOP 中,例如:
@Component
@Aspect
public class MyTest{
}
注意:仅仅使用了@Aspect注解,并不能将一个java对象转换为Bean ,因此还需要使用类似@Component之类的注解
声明切入点(point cut)
一个point cut 的声明由两部分组成:
- 一个方法签名,包括方法名和相关参数
- 一个point cut 表达式,用来指定那些方法是我们感兴趣的。(即因此可以织入advice)
在@AspectJ 风格AOP中,我们使用一个方法来描述point cut,即
@Pointcut("execution(* com.yao.service.UserService.*(..))") //切入点表达式
private void dataAccessOperation(){
}
这个方法必须无返回值
切点标志符(designator)
AspectJ5的切点表达式由标志符(designator)和操作参数组成.如"execution( greetTo(…))”的切点表达式,execution就是标志符,而圆括号里的greetTo(…)就是操作参数。
execution
匹配join point的执行,例如“execution(* hello(…)”表示匹配所有目标类中的hello()方法.这个是最基本的 pointcut标志符.
within
匹配特定包下的所有join point,例如within(com.xys.*)表示 com.xys包中的所有连接点,即包中的所有类的所有方法.而within(com.xys.service.*Service)表示在com.xys.service包中所有以Service结尾的类的所有的连接点.
this与target
this 的作用是匹配一个bean,这个bean(Spring AOP proxy)是一个给定类型的实例(instanceof).而target匹配的是一个目标对象(target object,即需要织入advice的原始的类),此对象是—个给定类型的实例(instance of).
bean
匹配bean名字为指定值的bean下的所有方法,
bean(*Service) //匹配名字后缀为Service的bean下的所有方法
bean(myService) //匹配名字是myService的bean的所有方法
args
匹配参数满足要求的方法
@Pointcat("within(com.yao.demo2.*)")
public void pointcut(){
}
@Before(value = "pointcut() && args(name)")
public void doSomething(String name){
logger.info("-----page: {}-----",name);
}
@Service
public class NormalService {
private Logger logger = LoggerFactory.getLogger(getlass());
public void someMethod( {
logger.info("---NormalService: someMethod invoked---");
public string test(String name) {
logger.info("---NormalService: test invoked---");
return“服务一切正常";
}
当NormalService.test执行时,则advice doSomething就会执行, test方法的参数name就会传递到doSomething中.
常用例子:
@Before(value = "pointcut() && args(name)")
public void doSomething(String name){
logger.info("-----page: {}-----",name);
}
@Before(value = "pointcut() && args(name,..)")
public void doSomething(String name){
logger.info("-----page: {}-----",name);
}
@Before(value = "pointcut() && args(*,name,..)")
public void doSomething(String name){
logger.info("-----page: {}-----",name);
}
常见的切入点表达式
匹配方法签名
//匹配指定包下所有方法
execution(* com.yao.service.*(..))
//匹配指定当前包中指定类的方法
execution(* UserService.*(..))
//匹配指定包中所有public方法
execution(public com.yao.service.*(..))
//匹配指定包中的所有public 方法,并且返回值是int类型的方法
execution(public int com.yao.service.*(..))
//匹配指定包中的所有public方法,并且第一个参数是String,返回值是int资型的方法
execution(public com.yao.service.*(String name,..))
声明Advice(增强)
advice是和一个point uct 表达式关联在一起的,并且会在匹配的join point 的方法执行的前、后、周围运行。point cut表达式可以是简单的一个point cut名字引用,或是完整的表达式
Before advice
@Component
@Aspect
public class BeforeAspectTest{
@Pointcut("execution(* com.yao.service.*(..))")
public void dataAccessOperation(){}
}
@Component
@Aspect
public class AdviceDefine{
//定义advice
@Before("com.yao.aspect.PointcutDefine.dataAccessOperation")
public void doBeforeAccessCheck(JoinPoint joinPoint){
System.out.println("****Before advise,method: " +joinPoint.getSignature().toString()+"******");
}
}
这里,@Before引用了一个pointcut,即"com.xys.aspect.PointcutDefine.dataAccessOperation()”是一个pointcut的名字