Spring4.x 笔记(14):Spring 声明式 Aop

声明式 Aop 实现

  1. Spring编程式AOP分别使用 Pointcut 和 Advice 接口描述切点和增强,并使用 Advisor 整合两者成一个切面。
  2. 在声明式AOP中,直接使用AspectJ注解(Schema标签)来描述切点、增强。如@Aspect注解标识一个切面,@Before 表示一个前置增强,execution函数切点匹配定位。

基于AspectJ注解实现

  1. 定义业务目标类
public interface AopService {
    void add(String name);
}

public class AopServiceImpl implements AopService {
    @Override
    public void add(String name) {
        System.out.println("** 操作【新增】的业务** " + name);
    }
}

  1. 定义切面类
  • 通过注解 @Aspect 将普通类 AopAnnotationAspect 标识为一个切面 Advisor
  • @Before 定义切点和增强类型,本身Before注解为前置Advice;value 属性为切点表达式,定义切点匹配
  • before() 方法为增强 Advice 的横切逻辑实现
@Aspect
public class AopAnnotationAspect {

    @Before(value = "execution( * add(..))")
    public void before() {
        System.out.println("How are you");
    }
}
  1. 编码测试
  • 在Spring AOP 中提供了ProxyFactory实现织入基于接口描述的切面,在织入基于AspectJ的切面时可以使用 AspectJProxyFactory
    AopService target = new AopServiceImpl();

    AspectJProxyFactory factory = new AspectJProxyFactory();
    factory.setTarget(target); # 设置目标对象
    factory.addAspect(AopAnnotationAspect.class); # 添加切面类
    AopService proxy = factory.getProxy(); # 生成代理对象

    proxy.add("Sam");
  1. Sechema 配置实现切面,新建配置文件 schema-aop.xml
  • AnnotationAwareAspectJAutoProxyCreator 是基于 Bean中的AspectJ注解自动代理的创建器,为包含AspectJ注解的Bean自动创建代理实例,是前面介绍的三种代理创建器的其中一个。
  • Spring 在自动代理创建的基础中提供了新的标签 <aop:aspectj-autoproxy/> 简化这一功能,其内部也是使用 AnnotationAwareAspectJAutoProxyCreator 实现。
    # 目标类
    <bean id="aopServiceTarget" class="com.learning.spring.aop.aspectj.AopServiceImpl"/>
    # 切面
    <bean id="aopAnnotationAspect" class="com.learning.spring.aop.aspectj.annotation.base.AopAnnotationAspect"/>
    # 自动代理创建器
    <bean class="org.springframework.aop.aspectj.annotation.AnnotationAwareAspectJAutoProxyCreator"/>
    
    <!--自动代理标签,内部使用 AnnotationAwareAspectJAutoProxyCreator-->
    <!--<aop:aspectj-autoproxy/>-->  
    测试
    ApplicationContext context = new ClassPathXmlApplicationContext("classpath:annotation/base/annotation-aop.xml");
    AopService waiter = context.getBean("aopServiceTarget", AopService.class);
    waiter.add("Sam");
  1. JavaConfig 配置,新建配置类 AspectConfig.java
  • 对应 <aop:aspectj-autoproxy/> 标签实现,spring 也提供了 @EnableAspectJAutoProxy 注解实现开启声明式AOP的功能,其内部同样使用 AnnotationAwareAspectJAutoProxyCreator 类。
  • Enable* 功能性注解在 Spring 中有着非常重要的作用,后面会有单独的笔记介绍。
    # 定义目标
    @Bean
    public AopService aopServiceTarget() {
        return new AopServiceImpl();
    }

    # 切面类
    @Bean
    public AopAnnotationAspect preGreetingAspect() {
        return new AopAnnotationAspect();
    }

    # 自动代理创建器
    @Bean
    public AnnotationAwareAspectJAutoProxyCreator creator() {
        return new AnnotationAwareAspectJAutoProxyCreator();
    }
    # 使用注解代理 取代自动代理创建器Bean
    @EnableAspectJAutoProxy
    public class AspectConfig {
        ......
    }
    # 测试
    ApplicationContext context = new AnnotationConfigApplicationContext(AspectConfig.class);
    AopService waiter = context.getBean("aopServiceTarget", AopService.class);
    waiter.add("Sam");

基于Schema标签实现

针对于注解实现的AspectJ注解@Aspect@Before,spring提供了与之对应的Schema标签,用于实现基于xml文件配置的AOP功能

  1. 目标类不变,修改上例中的切面类,去掉注解。
public class AopAnnotationAspect {
    public void before() {
        System.out.println("How are you");
    }
}
  1. 使用Schema标签在配置文件中定义切面的配置。
  • Bean 的定义注解@Bean变为<bean>标签
  • 使用 <aop:config><aop:aspect><aop:before> 等标签实现功能,见名知意,不赘述。
    <!--定义目标-->
    <bean id="aopServiceTarget" class="com.learning.spring.aop.aspectj.AopServiceImpl"/>

    <!--定义切面的Bean-->
    <bean id="aopSchemaAspect" class="com.learning.spring.aop.aspectj.schema.base.AopSchemaAspect"/>

    <!--定义AOP配置-->
    <aop:config proxy-target-class="true">
        # 指定切面
        <aop:aspect ref="aopSchemaAspect"> 
            # 指定advice横切逻辑方法,编写切点函数
            <aop:before method="before" pointcut="execution( * add(..))"/> 
        </aop:aspect>
    </aop:config>
    # 测试
    ApplicationContext context = new ClassPathXmlApplicationContext("classpath:schema/base/schema-aop.xml");
    AopService aopService = context.getBean("aopServiceTarget", AopService.class);
    aopService.add("Sam");

基于注解的 Advice

Spring Aop 有大量基于接口的Advice类型实现,AspectJ 也有各种不同Advice的注解,其位于org.aspectj.lang.annotation 包中。

注解Advice详解说明

前置- @Before

前置Advice相当于BeforeAdvice。该注解有两个成员:

  1. value:定义切点
  2. argNames:由于无法通过Java 反射获取方法入参名,所以如果在java 编译时未启用调试信息,或者需要在运行期解析切点,就必须通过这个成员指定注解所标注增强方法的参数名(名字完全相同),多个参数名用逗号分隔
    @Before("execution(* add(..))")
    public void before() {
        System.out.println("Hello   @Before");
    }
后置- @AfterReturning

后置Advice相当于AfterReturningAdvice。该注解有4个成员:

  1. value:定义切点
  2. pointcut:切点,value 同义,会覆盖value的值
  3. returning:将目标对象方法的返回值绑定给增强的方法。使用于返回值绑定。
  4. argNames:如 @Before 的属性所述
    @AfterReturning(value = "execution(* add(..))", returning = "result")
    public void afterReturning(String result) {
        System.out.println("Hello   @AfterReturning" + result);
    }
环绕- @Around

环绕Advice相当于MethodInterceptor。该注解有2个成员:

  1. value:定义切点
  2. argNames:如 @Before 的属性所述
    @Around("execution(* add(..))")
    public void around(ProceedingJoinPoint invocation) throws Throwable {
        System.out.println("Hello   @Around  before");
        invocation.proceed();
        System.out.println("Hello   @Around  after");
    }
异常抛出- @AfterThrowing

异常抛出Advice相当于ThrowsAdvice。该注解有4个成员:

  1. value:定义切点
  2. pointcut:切点,value 同义,会覆盖value的值
  3. throwing:将抛出的异常绑定到增强方法中,用于绑定异常。
  4. argNames:如 @Before 的属性所述
    @AfterThrowing(value = "execution(* add(..))", throwing = "ex")
    public void afterThrowing(Exception ex) {
        System.out.println("Hello   @AfterThrowing" + ex);
    }
Final- @After

After finally advice,不管是抛出异常还是正常退出,该增强都会被执行。可以看成是AfterReturning和AfterThrowing的混合物,一般用来释放资源。该注解有2个成员:

  1. value:定义切点
  2. argNames:如 @Before 的属性所述
    @After("execution(* add(..))")
    public void after() {
        System.out.println("Hello   @After finally advice");
    }
引介- @DeclareParents
  1. 引介 Advice相当于IntroductionInterceptor。该注解有2个成员:
  • value: 定义切点,表示在哪个目标类上添加引介
  • defaultImpl:默认接口的实现类
  1. 定义引介的新接口以及实现类
public interface Monitorable {
    void delete(String name);
}

public class MonitorableImpl implements Monitorable {
    @Override
    public void delete(String name) {
        System.out.println("** 操作删除的业务** " + name);
    }
}
  1. 定义引介Advice(切面类 AnnotationAdviceAspect)
    @DeclareParents(value = "com.learning.spring.aop.aspectj.AopServiceImpl", defaultImpl = MonitorableImpl.class)
    public Monitorable monitorable;
  1. JavaConfig 配置实现
@Configuration
@EnableAspectJAutoProxy
public class AnnotationAdviceConfig {
    @Bean
    public AopService aopServiceTarget() {
        return new AopServiceImpl();
    }

    @Bean
    public AnnotationAdviceAspect annotationAdvice() {
        return new AnnotationAdviceAspect();
    }
}
    ApplicationContext context = new AnnotationConfigApplicationContext(AnnotationAdviceConfig.class);
    AopService aopService = context.getBean("aopServiceTarget", AopService.class);

    // 可以强制类型转换,然后调用引介接口方法
    Monitorable monitorable = (Monitorable) aopService;
    monitorable.delete("Rabbi");

Advice 的顺序

  1. 如果有异常,那么 @AfterReturning 不会触发
注意默认的执行顺序

Hello   @Around  before
Hello   @Before
** 操作新增的业务** Sam
Hello   @Around  after
Hello   @After finally advice
Hello   @AfterReturning

切点表达式函数详解

  1. AspectJ 使用注解和切点表达式函数描述切面。切点函数由关键字和操作参数组成,如@Before("execution(* add(..))") 中,
  • execution为关键字,代表目标类执行某一方法;
  • * add(..)为操作参数,描述目标类方法的匹配模式串。
  1. Spring 支持9个AspectJ 切点表达式函数,主要有
  • 方法切点函数:@annotation、execution
  • 方法入参切点函数:args、@args
  • 目标类切点函数:within、@target、@within、target
  • 代理类切点函数:this
  1. 有些函数的入参支持通配符
  • *:匹配任意字符,匹配上下文中的一个
  • ..:匹配任意字符,可以匹配上下文中的多个元素,表示类时必须和*联合使用,而在表示入参时则单独使用
  • +:表示按类型匹配指定类的所有类,必须跟在类名后面(包括类本身与子类)
  1. 操作参数还支持逻辑运算
  • &&:与操作符,切点交集,等效于Spring 的and运算符。
  • ||:或操作符,切点并集,等效于Spring 的or运算符。
  • :非操作符,切点反集运算,等效于Spring 的not运算符。

常用函数以及用法

  1. 定义目标业务类以及相关
public class User {
    private String name;

    public User(String name) {
        this.name = name;
    }
}

public interface AopService {

    void add(String name);

    void update(String name);

    void select(User user);

    void exception(String name);
    
    String execute();
    
    void scan(String name);
}

public class AopServiceImpl implements AopService {

    @Override
    public void add(String name) {
        System.out.println("** 操作【新增】的业务** " + name);
    }

    @Override
    public void update(String name) {
        System.out.println("** 操作【修改】的业务** " + name);
    }

    @Override
    public void select(User user) {
        System.out.println("** 操作【查询】的业务** " + user);
    }

    @Override
    public void exception(String name) {
        System.out.println("** 操作【异常】的业务** " + name);
        int i = 1 / 0;
    }

    @Override
    public String execute() {
        System.out.println("** 操作【执行】有返回业务** ");
        return "RETURN";
    }

    @Override
    public void scan(String name) {
        System.out.println("** 操作【扫描】的业务** " + name);
    }

    public void self(String name) {
        System.out.println("** 操作【私有】的方法** " + name);
    }
}

  1. 定义JavaConfig配置类PointcutFunctionConfig,以及切面类PointcutFunctionAspect
@Configuration
@EnableAspectJAutoProxy
public class PointcutFunctionConfig {
    @Bean
    public AopService aopServiceTarget() {
        return new AopServiceImpl();
    }
    @Bean
    public PointcutFunctionAspect annotationAdvice() {
        return new PointcutFunctionAspect();
    }
}
@Aspect
public class PointcutFunctionAspect {
    
}
@annotation
  1. @annotation 表示标注了某个注解的所有方法。
  2. 定义自定义注解类,并标注在AopServiceImpl#add()方法上
    @Retention(RetentionPolicy.RUNTIME)
    @Target({ElementType.TYPE, ElementType.METHOD})
    public @interface LogEntity {
    }
    @Override
    @LogEntity
    public void add(String name) {
        System.out.println("** 操作【新增】的业务** " + name);
    }

  1. 在切面类中定义一个切面
  • 测试调用中,切面被正确 织入 add() 中,其他方法没有被织入
    @Before("@annotation(com.learning.spring.aop.aspectj.LogEntity)")
    public void before() {
        System.out.println("Hello @Before  @annotation Function");
    }
execution
  1. execution是最常用的切点函数。语法如下:
execution (<修饰符类型>?<返回类型模式><方法名模式>(<参数模式>)<异常模式>?)
  1. 方法签名定义切点
execution(public * *(..)) :匹配所有目标类的 public 方法:
第一个 `*` :返回类型
第二个 `*` :方法名
方法中`..` :任意参数

execution(* *To(..)) :匹配目标类所有以To为后缀的方法
  1. 类定义切点
execution(* com.learning.spring.aop.aspectj.AopService.*(..)) 匹配 AopService 接口的所有方法

execution(* com.learning.spring.aop.aspectj.AopService+.*(..)) 匹配 AopService 接口及其实现类的所有方法。包括实现类的私有的方法。
  1. 类包定义切点
  • .* 包下所有的类
  • ..* 包和子孙包
execution(* com.learning.spring.aop.aspectj.*(..)) 匹配 com.learning.spring.aop.aspectj 包下所有类所有方法

execution(* com.learning.spring.aop.aspectj..*(..)) 匹配 com.learning.spring.aop.aspectj 包下,子孙包下所有类所有方法

execution(* com.learning.spring.aop..*.*Service.find*(..)) 匹配 com.learning.spring.aop.aspectj 任意包下Service后缀的类,前缀为find的方法
  1. 方法入参定义切点
execution(* add(String)) 匹配add(String)方法,且方法参数类型是 String
args
  1. args 函数,接收一个类名,表示目标类方法入参对象是指定类(包含子类)的匹配
  2. 匹配目标类中 select(User user) 方法
    # 表示运行时入参是`User`类型的方法
    @Before("args(com.learning.spring.aop.aspectj.User)")
    public void args() {
        System.out.println("Hello args Function");
    }
    
@args
  1. @args 函数,接收一个注解类的类名。当方法运行时,入参对象标注了指定的注解时,匹配切点。注意入参对象的击继承关系。
  2. 修改实体类User,标注注解LogEntity,则匹配目标类中 select(User user) 方法
    @Before("@args(com.learning.spring.aop.aspectj.LogEntity)")
    public void argsAnnotation() {
        System.out.println("Hello @args Function");
    }
within
  1. within函数:通过类匹配模式串,针对目标类而言,不是运行期对象的类型。指定的最小范围是类。
within(com.learning.spring.aop.aspectj.AopService) 不会匹配任何方法。AopService 是接口,没有实例。
within(com.learning.spring.aop.aspectj.AopServiceImpl) 匹配类方法

within(com.learning.spring.aop.*) 匹配com.learning.spring.aop中的所有类,不包括子类
within(com.learning.spring.aop..*) 匹配com.learning.spring.aop中的所有类,包括子类
@target 和 @within
  1. 除了@annotation、@args,函数 @target、@within也是作用域注解的切点函数。
  2. 函数@target(A):匹配任意标注了注解@A 的目标类
    @Before("@target(com.learning.spring.aop.aspectj.LogEntity)")
    public void targetAnnotation() {
        System.out.println("Hello @target Function");
    }

  1. 函数@within(A):匹配任意标注了注解@A 的目标类以及子孙类。注意如果是接口标注了@A,实现接口的子类不匹配。
    @Before("@within(com.learning.spring.aop.aspectj.LogEntity)")
    public void withinAnnotation() {
        System.out.println("Hello @within Function");
    }
target
  1. 函数 target:针对目标类。通过判断目标类是否按类型匹配指定类来决定连接点是否匹配。
  2. target(C): 表示如果目标类按类型匹配 C,则目标类的所有方法都匹配切点
target(com.learning.spring.aop.aspectj.AopService): AopService 接口其子类的所有方法匹配,包括私有方法(目标类属于AopService类型)
this
  1. 函数 this:针对生成的代理对象的类,考虑目标类生成的代理对象的类型。通过判断代理对象的类是否按类型匹配指定类来决定是否和切点匹配
this(com.learning.spring.aop.aspectj.AopServiceImpl)  不会匹配任何方法。如果不使用CGLib,生成的代理对象属于 AopService 类型。AopService类型 不属于 AopServiceImpl。

进阶用法

切点复合运算
  1. 使用逻辑运算进行复合运算
    # 与运算。也支持 或运算(||)、非与运算(! &&)
    @Before("within(com.learning.spring.aop.aspectj.*)" + "&& execution(* add(..))")
    public void before() {
        System.out.println("Hello @Before 复合运算");
    }
命名切点
  1. 命名切点的最大作用是定义的切点可以重用
  2. 使用@Pointcut注解和切面类方法定义切点,然后在函数入参处直接传入这个切面类方法
    # 定义切点,然后可以重用
    @Pointcut("execution(* update(..))")
    public void pointcut() {
    }

    @AfterReturning("pointcut()")
    public void after() {
        System.out.println("Hello @AfterReturning  @Pointcut 命名切点");
    }
访问连接点信息:JoinPoint
  1. AspectJ使用org.aspectj.lang.JoinPoint接口表示目标类连接点对象。
  2. 如果是环绕Advice,则使用org.aspectj.lang.ProceedingJoinPoint表示连接点对象。
  • joinPoint.proceed();:通过反射执行目标对象的连接点处方法
  • joinPoint.proceed(args);:执行目标对象的连接点处方法,使用新的参数
    @Before("execution(* add(..))")
    public void joinPoint(JoinPoint joinPoint) {

        // 获取连接点方法运行时的入参列表
        Object[] args = joinPoint.getArgs();


        // 获取连接点的方法签名对象
        Signature signature = joinPoint.getSignature();

        // 获取连接点所在的目标对象
        Object target = joinPoint.getTarget();

        // 获取代理对象本身
        Object aThis = joinPoint.getThis();
    }

    @Around("execution(* add(..))")
    public void joinPoint(ProceedingJoinPoint joinPoint) throws Throwable {

        // 获取连接点方法运行时的入参列表
        Object[] args = joinPoint.getArgs();


        // 获取连接点的方法签名对象
        Signature signature = joinPoint.getSignature();

        // 获取连接点所在的目标对象
        Object target = joinPoint.getTarget();

        // 获取代理对象本身
        Object aThis = joinPoint.getThis();

        // 通过反射执行目标对象的连接点处方法
        joinPoint.proceed();

        // 执行目标对象的连接点处方法,使用新的参数
        args[0] = "Eason";
        joinPoint.proceed(args);
    }
绑定连接点方法入参
  1. args、this、target、@args、@winthin、@target、@annotation这7个函数不仅可以指定类名外,还可以指定参数名,将目标对象连接点的方法入参绑定到Advice增强的方法中。其实也可以使用连接点信息类JoinPoint获取

  2. 比如args用于绑定连接点方法的入参;@annotation用于绑定连接点方法的类注解对象;@args用于绑定连接点方法入参的注解

  3. 下面的例子:匹配 AopService 类中方法入参为User的方法。args(参数名):

  • 连接点匹配规则:连接点方法的入参是User类型
  • 连接点方法入参和增强方法入参绑定
    @Before("target(com.learning.spring.aop.aspectj.AopService)" + "&& args(user)")
    public void args(User user) {
        System.out.println("Hello @Before args 绑定连接点方法入参");
        System.out.println(user);
    }
绑定类注解对象
  1. @winthin、@target函数可以将目标类的注解对象绑定到Advice增强方法中
  2. 例子:使用@winthin函数绑定注解
    @Before("@within(logEntity)")
    public void annotation(LogEntity logEntity) {
        System.out.println("Hello @within annotation args 绑定类注解对象");
        System.out.println(logEntity);
    }
绑定返回值
  1. 在后置Advice增强中,可以通过returning属性绑定连接点方法的返回值
    @AfterReturning(value = "execution(* execute(..))", returning = "result")
    public void afterReturningValue(String result) {
        System.out.println("Hello @AfterReturning returning value 绑定返回值");
        System.out.println(result);
    }
绑定异常
  1. 连接点抛出异常必须使用@AfterThrowing 注解的throwing属性进行绑定。
    @AfterThrowing(value = "execution(* exception(..))", throwing = "ex")
    public void afterThrowing(Exception ex) {
        System.out.println("Hello @AfterReturning returning value 绑定异常");

        System.out.println(ex);
    }

总结

  1. Spring 声明式AOP,是基于Aspect提供的注解实现的,其重点是表达式语言和切面函数,切换函数非常灵活且强大,建议经常看看

  2. 在切点函数表达式中,大多数切点函数都可以绑定连接点方法的入参,以便Advice增强方法访问连接点信息,也可以直接使用JoinPoint对象访问连接点信息。

  3. 声明式AOP 支持基于JavaConfig和Schema标签配置两种方式使用切点表达式和增强定义。

  4. Spring AOP 提供了使用接口、@Aspect注解、<aop:aspect>标签、<aop:advisor>标签4种方式定义切面,但是其底层实现确是相同的,如有需要,其支持各种方式混用。

参考

  1. Spring AOP 官方文档
  2. 文章所涉及代码源码 spring-aop
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值