前置文章:Spring AOP-基础使用
本文对AOP注解的一些使用细节做了补充。
零、本文纲要
- 一、快速入门
- 二、了解AOP注解
- @EnableAspectJAutoProxy
- @Aspect
- @Pointcut
- 用于配置通知的注解
4.1 补充:获取方法参数、返回值、异常的写法
4.2 补充:同一个切面中相同通知类型的执行顺序 - 用于扩展目标类的注解
一、快速入门
1. 相关依赖
spring-context(包含aop基础的依赖)
aspectjweaver(用于解析切入点表达式)
2. 编写配置类
@Configuration
@ComponentScan("com.stone")
@EnableAspectJAutoProxy //开启注解格式AOP功能
public class SpringConfig{
}
3. 编写切面类
@Component
@Aspect
public class LogUtil{
@Before("execution(* com.stone.service.impl.*.*(..))")
public void log(){
sout("...");
}
}
4. 编写接口&实现类
此处省略
二、了解AOP注解
1. @EnableAspectJAutoProxy
- ① 作用:开启Spring注解AOP的支持
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Import({AspectJAutoProxyRegistrar.class})
public @interface EnableAspectJAutoProxy {
boolean proxyTargetClass() default false; //false表示使用JDK代理,true表示使用CGLIB代理
boolean exposeProxy() default false; //是否能够通过AopContext对象获取当前Proxy代理对象
}
@EnableAspectJAutoProxy(exposeProxy = true)则可以通过AopContext.currentProxy()获取当前对象的代理对象
注意:CGLIB代理不能代理被final修饰的类,因为生成的是其子类
- ② @EnableAspectJAutoProxy中的AspectJAutoProxyRegistrar
@Override
public void registerBeanDefinitions(
AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
AopConfigUtils.registerAspectJAnnotationAutoProxyCreatorIfNecessary(registry); //注册AnnotationAwareAspectJAutoProxyCreator
AnnotationAttributes enableAspectJAutoProxy =
AnnotationConfigUtils.attributesFor(importingClassMetadata, EnableAspectJAutoProxy.class);
if (enableAspectJAutoProxy != null) {
if (enableAspectJAutoProxy.getBoolean("proxyTargetClass")) {
AopConfigUtils.forceAutoProxyCreatorToUseClassProxying(registry);
}
if (enableAspectJAutoProxy.getBoolean("exposeProxy")) {
AopConfigUtils.forceAutoProxyCreatorToExposeProxy(registry);
}
}
}
Ⅰ 其内部会注册:AnnotationAwareAspectJAutoProxyCreator
会往registry内部注册一个Bean,如下:
registry.registerBeanDefinition(AUTO_PROXY_CREATOR_BEAN_NAME, beanDefinition);
AUTO_PROXY_CREATOR_BEAN_NAME = "org.springframework.aop.config.internalAutoProxyCreator";
Ⅱ 该方法执行时期
public AnnotationConfigApplicationContext(Class>... componentClasses) {
this(); //注册IOC容器
register(componentClasses); //把注解类配置到IOC容器
refresh(); //内部执行到invokeBeanFactoryPostProcessors(beanFactory);时触发上述方法
}
2. @Aspect
① 作用:用于配置切面,表明当前类是一个测试类
② 多个切面相同通知类型的执行顺序
Ⅰ 默认为多个切面的字典顺序
Ⅱ 使用@Order注解控制
数值越小,优先级越高
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE, ElementType.METHOD, ElementType.FIELD})
@Documented
public @interface Order {
int value() default Ordered.LOWEST_PRECEDENCE; //Integer.MAX_VALUE
}
3. @Pointcut
① 作用:抽取切入点表达式,非必须
② 使用案例
@Pointcut("execution(* com.stone.service.impl.*.*(..))")
private void pt(){}
@Before("pt()")
public void beforeMethod(){...}
@After("pt()")
public void afterMethod(){...}
4. 用于配置通知的注解
- ① @Before、@After、@AfterReturning、@Afterthrowing
Ⅰ @Before
被此注解修饰的方法为前置通知,前置通知的执行时间点是在切入点方法执行之前
Ⅱ @After
用于指定最终通知
Ⅲ @AfterReturning
用于配置后置通知,后置通知的执行是在切入点方法正常执行之后执行
注意:
5.2.6及之前,先执行@After注解修饰的方法
5.2.7及之后,后执行@After注解修饰的方法
Ⅳ @AfterThrowing
用于配置异常通知,用此注解修饰的方法执行时机是在切入点方法执行产生异常之后执行
注意:
5.2.6及之前,先执行@After注解修饰的方法
5.2.7及之后,后执行@After注解修饰的方法
- ② @Around
模拟5.2.6版本及之前的执行顺序
@Around("execution(* com.stone.service.impl.*.*(..))")
public Object aroundMethod(ProceedingJoinPoint pjp) throws Throwable { //注意:此处方法返回值记得写Object
Object result = null;
try {
try {
System.out.println("前置通知:Before advice running...");
//切入点方法执行
result = pjp.proceed();
} finally {
System.out.println("最终通知:After advice running");
}
System.out.println("后置通知:AfterReturning advice running");
return result;
} catch (Throwable e) {
System.out.println("异常通知:AfterThrowing advice running");
throw new RuntimeException(e);
}
}
注意:@Around环绕通知修饰的方法返回值如果写void,而待增强的切入点是有返回值的,此时会返回null
ProceedingJoinPoint
public interface ProceedingJoinPoint extends JoinPoint {
void set$AroundClosure(AroundClosure var1);
default void stack$AroundClosure(AroundClosure arc) {
throw new UnsupportedOperationException();
}
Object proceed() throws Throwable;
Object proceed(Object[] var1) throws Throwable;
}
4.1 补充:获取方法参数、返回值、异常的写法
@Before(value = "execution(* com.stone.service.impl.*.*(..))" && args(user, id))
public void beforeMethod(User user, String id){...}
@After(value = "execution(* com.stone.service.impl.*.*(..))" && args(user, id))
public void afterMethod(User user, String id){...}
@AfterReturning(value = "execution(* com.stone.service.impl.*.*(..))", returning = "obj")
public void afterReturningMethod(Object obj){...}
@AfterThrowing(value = "execution(* com.stone.service.impl.*.*(..))", throwing = "e")
public void afterThrowingMethod(Throwable e){...}
4.2 补充:同一个切面中相同通知类型的执行顺序
- ① 同一个切面中,相同通知类型的执行顺序是ASCII码顺序,谁值小就先执行
- ② 同一个切面中,相同通知类型执行不能通过@Order注解控制
5. 用于扩展目标类的注解
- ① @DeclareParents
Ⅰ 作用:用于给被增强的类提供新的方法。(实现新的接口)
Ⅱ 使用
在切面类内部添加如下配置:
//value属性:用于指定目标类型的表达式,当在全限定类名后面跟上+时,表示当前类及其子类
//defaultImpl属性:指定提供方法或者字段的默认实现类
@DeclareParents(value = "com.stone.service.UserService+", defaultImpl = ValidateServiceImpl.class)
private ValidateService validateService;
此处省略ValidateService接口和ValidateServiceImpl实现类的代码。
触发方式一:
需要在业务代码中使用方法时,对UserService对象进行强转,变为ValidateService对象再使用其方法。
触发方式二:
@Before(value = "pointcut1() && args(user) && this(validateService)")
public void checkUser(User user, ValidateService validateService){...}
- ② @EnableLoadTimeWeaving(不常用)
Ⅰ 作用:用于切换不同场景下实现增强。
三、结尾
以上即为Spring AOP-注解介绍的全部内容,感谢阅读。