【代理模式AOP】2. @Aspect的代码实战(比较Cglib和动态JDK)
目录
- 1 execution表达式
- (1) 代理类
- (2) 被代理的类
- (3) 测试类
- 2 注解方式
- (1) 代理类
- (2) 自定义注解
- (3) 被代理的类
- (4) 测试类
- 3 具体代理生效的方式
前言:
1)横切关注点,从每个方法中抽取出来的同一类非核心业务
2)切面(Aspect),对横切关注点进行封装的类,每个关注点体现为一个通知方法;通常使用 @Aspect 注解来定义切面。
3)通知(Advice),切面必须要完成的各个具体工作,比如我们的日志切面需要记录接口调用前后的时长,就需要在调用接口前后记录时间,再取差值。通知的方式有五种:
@Before:通知方法会在目标方法调用之前执行
@After:通知方法会在目标方法调用后执行
@AfterReturning:通知方法会在目标方法返回后执行
@AfterThrowing:通知方法会在目标方法抛出异常后执行
@Around:把整个目标方法包裹起来,在被调用前和调用之后分别执行通知方法
4)连接点(JoinPoint),通知应用的时机,比如接口方法被调用时就是日志切面的连接点。
5)切点(Pointcut),通知功能被应用的范围,比如本篇日志切面的应用范围是所有 controller 的接口。通常使用@Pointcut 注解来定义切点表达式。
1 execution表达式
使用execution表达式,直接匹配需要被代理的目标方法
execution表达式写法:
execution(modifiers-pattern? return-type-pattern declaring-type-pattern? name-pattern(parameter-types-pattern) throws-pattern?)
- 匹配所有公共方法
execution(public * *(..))
- 匹配名为 save 的所有方法,无论返回类型和参数类型
execution(* save*(..))
- 匹配特定类中返回类型为 void 的方法
execution(void com.example.MyClass.*(..))
- 匹配返回类型为 String 的任何方法,不论方法名和参数
execution(String *(..))
- 匹配特定包中所有 update 方法
execution(* com.example.service..update*(..))
(1) 代理类
@Aspect
@Component
public class MyBefore {@Before("execution(* com.example.demo.Service.testBefore(..))")public void before(){System.out.println("before...");}
}
(2) 被代理的类
@Component
public class Service {public void testBefore(){System.out.println("this is in the method");}
}
(3) 测试类
@RunWith(SpringRunner.class)
@SpringBootTest
public class ServiceTest {@Autowiredprivate Service service;@Testpublic void testBefore(){service.testBefore();}
}
2 注解方式
声明注解, 在需要被代理的目标方法上增加注解
(1) 代理类
@Aspect
@Component
public class MyAround {@Pointcut(value = "@annotation(com.example.demo.ProxyAnnotation)")public void pointCut(){};@Around(value = "@annotation(around)")public Object around(ProceedingJoinPoint joinPoint, ProxyAnnotation around) throws Throwable {System.out.println(around.methodName());System.out.println("before...");int result = (int) joinPoint.proceed();System.out.println("result = " + result);System.out.println("after...");return result;}
}
@Around(value = "@annotation(around)")
这里的"around"对应方法参数中的参数名around, 用来传递被代理的方法信息, 还可以在自定义的注解中自定义其他的属性pointCut()
上面标注的注解, 说明查找哪个注解被使用来增强. 还可以在增强的方法中显式使用
中的@Aspect @Component public class MyAfterReturning {@Pointcut(value = "@annotation(com.alipay.demo.ProxyAnnotation2)")public void pointCut(){};@AfterReturning("pointCut()")public void afterReturning(){System.out.println("afterReturning...");} }
@AfterReturning("pointCut()")
注意: 因为这里的切面类方法返回类型需要符合(包含)实际被代理的方法类型, 因此最好设置为Object
(2) 自定义注解
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface ProxyAnnotation {String methodName();
}
(3) 被代理的类
@Component
public class Service {@ProxyAnnotation(methodName = "testAround")public int testAround(){System.out.println("this is in the method");return 1;}
}
(4) 测试类
@RunWith(SpringRunner.class)
@SpringBootTest
public class ServiceTest {@Autowiredprivate Service service;@Testpublic void testAround(){service.testAround();}
}
3 具体代理生效的方式
在Spring中, 注解@Aspect具体起效可能是动态JDK(JDK Proxy)和Cglib, 具体是运行哪个, 是看被代理的目标类是否实现了接口决定的:
接口org.springframework.aop.framework.AopProxy
的两个实现类:org.springframework.aop.framework.JdkDynamicAopProxy
和org.springframework.aop.framework.CglibAopProxy
- 动态JDK: 实现了接口的对象。当对一个实例调用一个方法时,Spring会生成一个代理,这个代理实现了该接口。
- Cglib: 没有实现接口的对象。适用于没有实现接口的类或是类的所有方法都被 final 或是 private 修饰的情况。CGLIB会在运行时生成目标类的子类来实现代理。
由于Spring AOP现在已经接入了AspectJ, 如果想要以AspectJ的方式完成代理, 则需要在/META-INF/AOP.xml
中设置:
<aspectj><weaver><include within="your.package.name.*"/></weaver><aspects><aspect name="your.package.name.LoggingAspect"/></aspects>
</aspectj>