Spring AOP
Published on: | Views: 81本文90%来自于https://docs.spring.io/spring/docs/5.1.9.RELEASE/spring-framework-reference/core.html#aop
AOP概念
AOP(Aspect-oriented Programming)是对OOP的一个补充,通过切面将原来分散在多个对象中的功能集中到一起进行模块化。 AOP相关概念: - Aspect(切面):跨越多个类的关注点的模块化,在Sprint AOP中是一个普通的类。 - Join point(连接点):程序执行的点,比如方法执行或者异常处理, 在Spring AOP中仅为方法执行。 - Pointcut(切点):符合某种条件的连接点。 - Advice(增强): 切面中针对连接点采取的行为。 - Introduction(引入): 给目标附加额外的方法或者属性。 - Target object(目标对象): 被多个切面增强后的对象,也叫advised object 。 - AOP proxy(AOP代理):由AOP框架创建的,实现切面功能的对象。 - Weaving(织入):联合多个切面和其他应用类型或对象,创建一个advised object的过程。
Spring AOP
Spring AOP的目标是提供AOP功能与IOC功能紧密集成,可以说它是为了增强IOC, 以实现某些通用的企业功能,比如事务。
Spring AOP中advice类型: - before advice: 在调用方法前增强 - after returning advice:在返回结果之后增强 - after throwing advice:在抛出异常之后增强 - after (finally) advice:在返回结果或者抛出异常之后增强 - around advice:完全掌握目标方法的执行,基至可以选择调用或者不调用目标方法
Spring AOP proxy
Spring AOP使用AspectJ切点语言,但是底层是通过代理(JDK或者CGLib)实现运行时织入。 Spring AOP有两种实现切面的方式: - 基于模式(XML) - 基于AspectJ注解
Spring AOP @AspectJ方式使用
引入依赖
compile('org.springframework.boot:spring-boot-starter-aop:版本)
启用@AspectJ支持
配置类上加@EnableAspectJAutoProxy
定义一个Aspect
@Component //注意要加这个,不然切面不会生效
@Aspect
public class TestAspect {
}
定义切点
@Component //注意要加这个,不然切面不会生效
@Aspect
public class TestAspect {
@Pointcut("execution(* com.example.demo.TestService.add(..))")
private void executionTestServiceAdd(){}
@Pointcut("within(com.example..*)")
private void withinComExample(){}
@Pointcut("@within(TestAnnotation)")
private void withinTestAnnotation(){}
@Pointcut("this(com.example.demo.TestService)")
private void thisTestService(){}
@Pointcut("target(com.example.demo.TestService)")
private void targetTestService(){}
@Pointcut("args(..)")
private void argsAny(){}
}
切点由标志符(参数)形式组成, Spring AOP中可用标志符如下: - execution: 匹配方法执行连接点。 - within: 要求连接点在指定类型中(Spring AOP中指执行方法所在的类)。 - @within:要求连接点所在的类型拥有指定的注解。 - this: 要求增强后的对象是指定类型。 - target: 要求增强后的对象是指定类型。 - @target: 要求增强后的对象拥有指定的注解。 - args: 要求参数为指定类型。 - @args: 要求参数为拥有指定的注解。 - @annotation: 要求被增强的方法拥有指定注解。 - bean(idOrNameOfBean):要求匹配bean名称或者ID。 切点可以有多个标志符,用 “&&”,"||" 组合,或者用"!"表示取反。 切点也可以使用其他切点进行组合:
@Pointcut(value = "executionTestServiceAdd() && argsAny()")
private void combinePointcut() {}
最常用的标志符是execution,它的完整定义是: execution(modifiers-pattern? ret-type-pattern declaring-type-pattern?name-pattern(param-pattern) throws-pattern?) |pattern|是否必选|含义 |--|--|--| |ret-type-pattern|是|返回类型,可以用*号作为通配符 |name-pattern|是|方法名,可以用*号作为通配符 |param-pattern|是|方法参数,()表示无参数,(..)表示任意参数, (*)表示一个参数任意类型,(*,String)表示匹配两个参数 |modifiers-pattern|否|方法修饰符,public, protected等 |declaring-type-pattern|否|类型名,例如[com.xxx.yy.] [com.xx.*.] [com.*.*.] [com..*.] |throws-pattern|否|抛出异常类型
定义切点的增强
@Before("executionTestServiceAdd()")
public void beforeAdd() {
log.info("before add");
}
@After("executionTestServiceAdd()")
public void afterAdd() {
log.info("after add");
}
@AfterReturning(value = "executionTestServiceAdd()", returning = "retVal")
public void afterReturning(Object retVal) {
log.info("after returning {}", retVal);
}
@Around("executionTestServiceAdd()")
public Object aroundAdd(ProceedingJoinPoint joinPoint) throws Throwable {
log.info("around add ---1---");
Object retVal = joinPoint.proceed();
log.info("around add ---2---");
return retVal;
}
参数
有两种方法获取参数,一是使用JointPoint, 二是直接传递参数。
使用JointPoint
所有的advice方法, 都可以将JoinPoint作为第一个参数(@Around是ProceedingJoinPoint),这个参数提供了一些有用的功能: - getArgs(): Returns the method arguments. - getThis(): Returns the proxy object. - getTarget(): Returns the target object. - getSignature(): Returns a description of the method that is being advised. - toString(): Prints a useful description of the method being advised.
使用直接传递
在args标志符中,传入参数的名称(不是参数类型)的时候, Spring AOP会将对应的参数传递给增强函数:
@Before(value = "executionTestServiceAdd() && args(a,b)")
public void testAdd2(int a,int b) {
log.info("before add a={}, b={}",a, b);
}
或者这样写:
@Pointcut("args(a,b)")
private void argsPointcut(int a, int b){}
@Before(value = "executionTestServiceAdd() && argsPointcut(a,b)")
public void testAdd2(int a,int b) {
log.info("before add a={}, b={}",a, b);
}
除了args可以这样用外, 像this、target、@within、@target、@annotation都可以这样用,例如
@Before("executionTestServiceAdd() && @target(testAnnotation)")
public void testParams(TestAnnotation testAnnotation){
log.info("annotation value={}",testAnnotation.value());
}
模板参数方法
public interface Sample<T> {
void sampleGenericMethod(T param);
void sampleGenericCollectionMethod(Collection<T> param);
}
可以拦截自己需要的类型
@Before("execution(* ..Sample+.sampleGenericMethod(*)) && args(param)")
public void beforeSampleMethod(MyType param) {
// Advice implementation
}
@Before("execution(* ..Sample+.sampleGenericCollectionMethod(*)) && args(param)")
public void beforeSampleMethod(Collection<MyType> param) {
// Advice implementation
}
参数名称
确定参数命名步骤: - If the parameter names have been explicitly specified by the user, the specified parameter names are used(use argNames). - 如果没有argsNames, Spring AOP looks at the debug information for the class and tries to determine the parameter names from the local variable table. - 如果没有调试信息,Spring AOP tries to deduce the pairing of binding variables to parameters. - If all of the above strategies fail, an IllegalArgumentException is thrown.
Introductions(引入)
@DeclareParents(value = "com.example.demo.TestServiceImpl", defaultImpl = TestIntroductionImpl.class)
public static TestIntroduction xxx;
@Before("executionTestServiceAdd() && this(testIntroduction)")
public void beforeAdd(TestIntroduction testIntroduction) {
testIntroduction.sayHello();
}
问题
今天有个切面死活不生效,试了各种办法, 后面发现这个切面都不出现在beans列表里面, 最后找到原因是在创建的时候(intelij)选择了aspect文件,这种文件无法被扫描到,即使把aspect修改成class, 文件后缀名还是.aj, 吭了我几个小时。