# 《Spring核心技术》第22章:AOP切面型注解实战

作者:冰河
星球:http://m6z.cn/6aeFbs (opens new window)
博客:https://binghe.gitcode.host (opens new window)
文章汇总:https://binghe.gitcode.host/md/all/all.html (opens new window)
源码地址:https://github.com/binghe001/spring-annotation-book/tree/master/spring-annotation-chapter-22 (opens new window)

沉淀,成长,突破,帮助他人,成就自我。

大家好,我是冰河~~


  • 本章难度:★★★☆☆

  • 本章重点:进一步学习并掌握AOP切面型注解的实战案例,从源码级别彻底掌握切面型注解。


本章目录如下所示:

  • 学习指引
  • 注解说明
    • @EnableAspectJAutoProxy注解
    • @Aspect注解
    • @Pointcut注解
    • @Before注解
    • @After注解
    • @AfterReturning注解
    • @AfterThrowing注解
    • @Around
  • 使用案例
  • 总结
  • 思考
  • VIP服务

# 一、学习指引

Spring中的AOP切面型注解,你真的彻底了解过吗?

AOP的全称是Aspect Oriented Programming,表示面向切面编程。AOP使用了设计模式中的代理模式,使用AOP可以实现业务逻辑的各个部分分离,降低业务逻辑的耦合性,提高程序的可重用性。在实际项目开发过程中,如果想在业务逻辑中不修改代码的前提下,增加输出的日志信息,此时就可以使用AOP切面实现。

# 二、注解说明

关于Spring中的AOP注解的一点点说明~~

Spring中提供的切面型注解主要有:@EnableAspectJAutoProxy、@Aspect、@Pointcut、@Before、@After、@AfterReturning、@AfterThrowing和@Around。本章,就简单介绍下Spring中提供的切面型注解。

# 2.1 @EnableAspectJAutoProxy注解

@EnableAspectJAutoProxy注解表示开启Spring对基于注解的AOP的支持,如果基于Spring的注解开发程序时,需要使用AOP功能,就需要使用@EnableAspectJAutoProxy注解来开启AOP功能。

@EnableAspectJAutoProxy注解的源码详见:org.springframework.context.annotation.EnableAspectJAutoProxy。

/**
 * @author Chris Beams
 * @author Juergen Hoeller
 * @since 3.1
 * @see org.aspectj.lang.annotation.Aspect
 */
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Import(AspectJAutoProxyRegistrar.class)
public @interface EnableAspectJAutoProxy {
	boolean proxyTargetClass() default false;
	/**
	 * @since 4.3.1
	 */
	boolean exposeProxy() default false;

}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18

从源码可以看出,@EnableAspectJAutoProxy注解是从Spring3.1版本开始提供的注解,只能标注到类上,并且使用@Import注解导入了AspectJAutoProxyRegistrar类。在@EnableAspectJAutoProxy注解中只提供了proxyTargetClass和exposeProxy两个属性。

  • proxyTargetClass:表示是否基于CGLib进行代理。true:表示基于CGLib进行代理。false:表示基于JDK进行代理。默认值为false。
  • exposeProxy:从Spring4.3.1版本开始提供的属性,表示是否对外暴露代理对象。true:对外暴露代理对象,可以通过AopContext进行访问。false:不对外暴露代理对象。默认值为false。

# 2.2 @Aspect注解

@Aspect注解可以将当前类声明为切面类。如果基于Spring注解开发程序时,需要将某个类声明为切面类,就可以使用@Aspect注解。

@Aspect注解的源码详见:org.aspectj.lang.annotation.Aspect。

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface Aspect {
	String value() default "";
}
1
2
3
4
5

从源码可以看出,@Aspect注解可以标注到类上,并且在@Aspect注解中提供了一个Spring类型的value属性。

  • value:指定预处理的切入点表达式。默认的切面类是单例的,如果切面类是一个多例类,则可以使用value属性指定预处理的切入点表达式。除了支持切入点表达式外,也支持使用@Pointcut修饰的方法的全名称。

# 2.3 @Pointcut注解

@Pointcut注解可以指定切入点表达式,在使用Spring注解开发AOP程序时,如果需要执行多个通知,并且使用AOP增强的功能确定的情况下,就可以使用@Pointcut注解将切入点表达式通用化。

@Pointcut注解的源码详见:org.aspectj.lang.annotation.Pointcut。

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface Pointcut {
    String value() default "";
    String argNames() default "";
}
1
2
3
4
5
6

从源码可以看出,@Pointcut注解标注到方法上。并且在@Pointcut注解中提供了两个String类型的属性value和argNames。

  • value:表示指定的切入点表达式。
  • argNames:表示指定的切入点表达式的参数,参数可以是execution中的,也可以是args中的,并且不使用这个属性也可以获得切入点的方法参数。

# 2.4 @Before注解

@Before注解指定方法为前置通知,指定为前置通知的方法在切入点方法之前执行。在实际开发项目的过程中,如果需要在切入点方法之前执行一些业务逻辑处理,就可以使用@Before注解指定前置通知的方法,让前置通知的方法在切入点方法之前执行。如果在前置通知的方法中需要获取切入点方法中的参数进行处理时,就需要配合使用切入点表达式参数。

@Before注解的源码详见:org.aspectj.lang.annotation.Before。

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface Before {
    String value();
    String argNames() default "";
}
1
2
3
4
5
6

从源码可以看出,@Before注解只能标注到方法上,并且在@Before注解中提供了两个Spring类型的属性,分别是value和argNames。

  • value:表示指定切入点表达式,可以是表达式,也可以是表达式的引用。
  • argNames:表示指定切入点表达式参数的名称。要求和切入点表达式中的参数名称一致。如果不指定切入点表达式参数的名称,一般情况下也可以获取切入点方法的参数内容。

# 2.5 @After注解

@After注解主要指定最终的通知,如果在实际项目的开发过程中,需要指定最终通知的执行时机,并且需要在切入点方法完成之后执行,无论切入点方法的执行是否抛出异常,都要执行最终的通知方法,就可以使用@After注解标注最终通知的方法。使用@After注解标注的方法也可以用来执行一些清理工作。

@After注解的源码详见:org.aspectj.lang.annotation.After。

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface After {
    String value();
    String argNames() default "";
}
1
2
3
4
5
6

从源码可以看出,@After注解只能标注到方法上,并且在方法中提供了两个String类型的属性,分别是value和argNames。

  • value:表示指定切入点表达式,可以是表达式,也可以是表达式的引用。
  • argNames:表示指定切入点表达式参数的名称。要求和切入点表达式中的参数名称一致。如果不指定切入点表达式参数的名称,一般情况下也可以获取切入点方法的参数内容。

# 2.6 @AfterReturning注解

@AfterReturning注解可以配置后置增强切入点方法。被@AfterReturning注解修饰的方法会在切入点方法正常执行之后执行。其实,在实际项目的开发过程中,比如提交事务、记录访问日志等等功能都可以使用@AfterReturning注解的后置通知实现。

@AfterReturning注解的源码详见:org.aspectj.lang.annotation.AfterReturning。

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface AfterReturning {
    String value() default "";
    String pointcut() default "";
    String returning() default "";
    String argNames() default "";
}
1
2
3
4
5
6
7
8

从源码可以看出,@AfterReturning注解只能标注到方法上,并且在@AfterReturning注解中提供了四个String类型的属性,分别是value、pointcut、returning和argNames。

  • value:表示指定切入点表达式,可以是表达式,也可以是表达式的引用。
  • pointcut:同value。
  • returning:表示指定切入点方法返回值的变量名称,需要注意的是,指定的名称需要和切入点方法返回值名称一致。
  • argNames:表示指定切入点表达式参数的名称。要求和切入点表达式中的参数名称一致。如果不指定切入点表达式参数的名称,一般情况下也可以获取切入点方法的参数内容。

# 2.7 @AfterThrowing注解

@AfterThrowing注解主要是配置异常通知,如果需要在切入点方法执行异常时,执行某些逻辑处理,就可以使用@AfterThrowing注解指定异常通知。

@AfterThrowing注解的源码详见:org.aspectj.lang.annotation.AfterThrowing。

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface AfterThrowing {
    String value() default "";
    String pointcut() default "";
    String throwing() default "";
    String argNames() default "";
}
1
2
3
4
5
6
7
8

从源码可以看出,@AfterThrowing注解只能标注到方法上,并且在@AfterThrowing注解中提供了四个String类型的属性,分别是:value、pointcut、throwing和argNames。

  • value:表示指定切入点表达式,可以是表达式,也可以是表达式的引用。
  • pointcut:同value。
  • throwing:表示指定切入点方法执行过程中抛出异常时的异常对象的变量名称,需要和异常变量名称保持一致。
  • argNames:表示指定切入点表达式参数的名称。要求和切入点表达式中的参数名称一致。如果不指定切入点表达式参数的名称,一般情况下也可以获取切入点方法的参数内容。

# 2.8 @Around注解

@Around注解用于指定环绕通知,如果在实际项目的开发过程中,需要手动控制增强方法执行的时机,就可以使用@Around注解来实现。

@Around注解的源码详见:org.aspectj.lang.annotation.Around。

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface Around {
    String value();
    String argNames() default "";
}
1
2
3
4
5
6

从源码可以看出,@Around注解只能标注到方法上,并且在@Around方法中提供了两个String类型的属性,分别是value和argNames。

  • value:表示指定切入点表达式,可以是表达式,也可以是表达式的引用。
  • argNames:表示指定切入点表达式参数的名称。要求和切入点表达式中的参数名称一致。如果不指定切入点表达式参数的名称,一般情况下也可以获取切入点方法的参数内容。

# 三、使用案例

# 查看完整文章

加入冰河技术 (opens new window)知识星球,解锁完整技术文章与完整代码