03-基于注解的方式实现AOP

使用Spring的AOP

AspectJ是Eclipse组织的一个支持AOP的框架,独立于Spring框架之外的一个框架,Spring对AOP的实现包括种方式

  • 第一种方式(常用): Spring框架结合AspectJ框架基于的注解方式实现的AOP
  • 第二种方式(常用): Spring框架结合AspectJ框架基于XML方式实现的AOP
  • 第三种方式(了解): Spring框架基于XML配置方式自己实现的AOP

基于AspectJ的AOP注解开发

第一步: 引入AspectJ框架需要的spring-aspects依赖

<!--spring context依赖-->
<dependency>
  <groupId>org.springframework</groupId>
  <artifactId>spring-context</artifactId>
  <version>6.0.0-M2</version>
</dependency>
<!--spring aop依赖,引入context依赖后会自动引入-->
<dependency>
  <groupId>org.springframework</groupId>
  <artifactId>spring-aop</artifactId>
  <version>6.0.0-M2</version>
</dependency>
<!--spring aspects依赖-->
<dependency>
  <groupId>org.springframework</groupId>
  <artifactId>spring-aspects</artifactId>
  <version>6.0.0-M2</version>
</dependency>

第二步: 在Spring配置文件中添加context(扫描注解)aop的命名空间及其对应的约束文件

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xmlns:aop="http://www.springframework.org/schema/aop"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
                           http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd
                           http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd">

</beans>

第三步: 定义目标类OrderService,如果目标类没有实现接口底层一定会使用CGLIB的动态代理

// 目标类
public class OrderService {
    // 目标方法
    public void generate(){
        System.out.println("订单已生成!");
    }
}

第四步: 定义切面类MyAspect,同时使用@Aspect注解标识当前类为切面类

// 切面 = 通知 + 切点
@Aspect
public class MyAspect {
}

第五步: 将目标类和切面类使用注解注册为组件,将它们纳入Spring容器的管理

@Service
public class OrderService {
    // 目标方法
    public void generate(){
        System.out.println("订单已生成!");
    }
}

@Component
@Aspect
public class MyAspect {
}

第六步: 在spring配置文件中使用component-scan标签开启组件扫描

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xmlns:aop="http://www.springframework.org/schema/aop"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
                           http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd
                           http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd">
    <!--开启组件扫描-->
    <context:component-scan base-package="com.powernode.spring6.service"/>
</beans>

第七步: 在切面类中使用@Before注解将方法(编写的增强代码)标识为前置通知,同时在@Before注解中使用切点表达式指定前置通知作用的切点(核心业务方法)`

// 切面 = 通知 + 切点
@Aspect
@Component
public class MyAspect {
    // 切点表达式,作用于OrderService的所有方法
    @Before("execution(* com.powernode.spring6.service.OrderService.*(..))")
    public void advice(){
        System.out.println("我是一个通知");
    }
}

第八步: 在spring配置文件中使用aspectj-autoproxy注解开启自动代理

  • Spring在扫描类的时候,会查看类上是否有@Aspect注解,如果有则给这个切面类生成代理对象,代理的目标对象的目标方法由切点表达式决定
注解属性描述
proxy-target-class=“true”true表示强制使用cglib动态代理
proxy-target-class=“false”(默认)fasle表示使用jdk动态代理,但如果目标类没有实现接口也会自动选择cglib生成代理类
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xmlns:aop="http://www.springframework.org/schema/aop"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
                           http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd
                           http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd">
    <!--开启组件扫描-->
    <context:component-scan base-package="com.powernode.spring6.service"/>
    <!--开启自动代理-->
    <aop:aspectj-autoproxy proxy-target-class="true"/>
</beans>

第九步: 测试在目标对象的目标方法之前执行的前置通知

public class AOPTest {
    @Test
    public void testAOP(){
        ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring-aspectj-aop-annotation.xml");
        OrderService orderService = applicationContext.getBean("orderService", OrderService.class);
        orderService.generate();
    }
}

通知类型及其执行顺序

除了环绕通知,其他通知方法都可以接收一个JoinPoint(连接点)类型的参数,在Spring容器调用这个通知方法的时候会自动传过来连接点参数

方法名功能
getSignature()获取目标方法的签名
getDeclaringTypeName根据签名获取目标方法所在类的全限定类名
getName根据签名获取目标方法的方法名
@Before("execution(* com.powernode.spring6.service.OrderService.*(..))")
public void beforeAdvice(JoinPoint joinPoint){
    // joinPoint.getSignature()表示获取目标方法的签名(修饰符 返回值类型 方法名),通过方法的签名可以获取到一个方法的具体信息如方法名
	System.out.println("目标方法的方法名:" + joinPoint.getSignature().getName());
}
注解名作用
@Before(前置通知)声明一个在目标方法执行之前的通知
@AfterReturning(后置通知)声明一个在目标方法执行之后的通知
@Around(环绕通知)声明一个在目标方法执行前后都要执行的通知
环绕通知由于也是一个方法,所以方法中间需要调用目标方法将通知前后的代码分隔开
Spring调用通知方法的时候会自动传过来一个ProceedingJoinPoint(连接点)类型的参数,调用连接点的proceed() 方法会执行目标方法
环绕通知是最大的通知,在前置通知之前,在后置通知和最终通知之后
@AfterThrowing(异常通知)声明一个在发生异常之后执行的通知
@After(最终通知)声明一个在finally语句块中执行的通知(一定会执行的通知),这个finally语句块不用我们声明

第一步: 定义目标类及目标方法(没有抛出异常)

// 目标类
@Component
public class OrderService {
    // 目标方法
    public void generate(){
        System.out.println("订单已生成!");
    }
}

第二步: 定义切面类MyAspect,使用注解添加对应类型的通知

@Component
@Aspect
public class MyAspect {
    // 环绕通知
    @Around("execution(* com.powernode.spring6.service.OrderService.*(..))")
    public void aroundAdvice(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
        System.out.println("环绕通知开始");
        // 执行目标方法
        proceedingJoinPoint.proceed();
        System.out.println("环绕通知结束");
    }
	
    // 前置通知
    @Before("execution(* com.powernode.spring6.service.OrderService.*(..))")
    public void beforeAdvice(){
        System.out.println("前置通知");
    }
	// 后置通知
    @AfterReturning("execution(* com.powernode.spring6.service.OrderService.*(..))")
    public void afterReturningAdvice(){
        System.out.println("后置通知");
    }
	// 异常通知
    @AfterThrowing("execution(* com.powernode.spring6.service.OrderService.*(..))")
    public void afterThrowingAdvice(){
        System.out.println("异常通知");
    }
	// 最终通知
    @After("execution(* com.powernode.spring6.service.OrderService.*(..))")
    public void afterAdvice(){
        System.out.println("最终通知");
    }
}

调用目标对象的目标方法,测试通知的执行顺序

public class AOPTest {
    @Test
    public void testAOP(){
        ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring-aspectj-aop-annotation.xml");
        OrderService orderService = applicationContext.getBean("orderService", OrderService.class);
        orderService.generate();
    }
}
环绕通知开始
前置通知
订单已生成!
后置通知
最终通知
环绕通知结束

当目标类的目标方法发生异常时会执行异常通知然后程序结束,因为最终通知会出现在finally语句块中所以一定也会执行,但后置通知和环绕通知就不会执行了

// 目标类
@Component
public class OrderService {
    // 目标方法
    public void generate(){
        System.out.println("订单已生成!");
        if (1 == 1) {
            throw new RuntimeException("模拟异常发生");
        }
    }
}
环绕通知开始
前置通知
订单已生成!
异常通知
最终通知

切面的先后顺序

业务流程当中不一定只有一个切面,可能有的切面控制事务,有的记录日志,有的进行安全控制,此时就需要控制它们的执行顺序了

添加一个新的切面类YourAspect,使用@Order注解的value属性(指定一个整数型的数字,数字越小优先级越高)控制两个切面类的优先级

@Aspect
@Component
@Order(1) //设置优先级
public class YourAspect {
    @Around("execution(* com.powernode.spring6.service.OrderService.*(..))")
    public void aroundAdvice(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
        System.out.println("YourAspect环绕通知开始");
        // 执行目标方法
        proceedingJoinPoint.proceed();
        System.out.println("YourAspect环绕通知结束");
    }

    @Before("execution(* com.powernode.spring6.service.OrderService.*(..))")
    public void beforeAdvice(){
        System.out.println("YourAspect前置通知");
    }

    @AfterReturning("execution(* com.powernode.spring6.service.OrderService.*(..))")
    public void afterReturningAdvice(){
        System.out.println("YourAspect后置通知");
    }

    @AfterThrowing("execution(* com.powernode.spring6.service.OrderService.*(..))")
    public void afterThrowingAdvice(){
        System.out.println("YourAspect异常通知");
    }

    @After("execution(* com.powernode.spring6.service.OrderService.*(..))")
    public void afterAdvice(){
        System.out.println("YourAspect最终通知");
    }
}

// 设置切面类MyAspect的优先级
@Component
@Aspect
@Order(2) //设置优先级
public class MyAspect {

    @Around("execution(* com.powernode.spring6.service.OrderService.*(..))")
    public void aroundAdvice(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
        System.out.println("环绕通知开始");
        // 执行目标方法
        proceedingJoinPoint.proceed();
        System.out.println("环绕通知结束");
    }

    @Before("execution(* com.powernode.spring6.service.OrderService.*(..))")
    public void beforeAdvice(){
        System.out.println("前置通知");
    }

    @AfterReturning("execution(* com.powernode.spring6.service.OrderService.*(..))")
    public void afterReturningAdvice(){
        System.out.println("后置通知");
    }

    @AfterThrowing("execution(* com.powernode.spring6.service.OrderService.*(..))")
    public void afterThrowingAdvice(){
        System.out.println("异常通知");
    }

    @After("execution(* com.powernode.spring6.service.OrderService.*(..))")
    public void afterAdvice(){
        System.out.println("最终通知");
    }
}

测试切面类优先级不同时的执行顺序,可以先得到优先级低的通知和目标方法的执行结果,然后将它们的最终结果看成一个整体,最终再嵌套优先级高的通知

# YourAspect的优先级高
YourAspect环绕通知开始
YourAspect前置通知环绕通知开始
前置通知
订单已生成!
后置通知
最终通知
环绕通知结束
YourAspect后置通知
YourAspect最终通知
YourAspect环绕通知结束
# MyAspect的优先级高
环绕通知开始
前置通知
YourAspect环绕通知开始
YourAspect前置
通知订单已生成!
YourAspect后置通知
YourAspect最终通知
YourAspect环绕通知结束
后置通知
最终通知
环绕通知结束

抽取可重用的切点表达式

在切面类中的每个通知方法前都需要指定一个切点表达式,对于相同的切点表达式我们完全可以抽取出来,保证切点表达式得到复用同时改一处即改所有

  • 第一步: 随便声明一个没有实现且返回void的空方法,这个方法只是起到一个能够让@Pointcut注解有位置声明的作用,此时方法名就是通用的切点表达式
  • 第二步: 在通知方法的注解中使用方法名()引入切点表达式,同类中可以直接使用方法名引用,跨类使用时需要在通用切点表达式前补全方法的全限定类名
// 切面类
@Component
@Aspect
@Order(2)
public class MyAspect {
    // 通用的切点表达式
    @Pointcut("execution(* com.powernode.spring6.service.OrderService.*(..))")
    public void pointcut(){}
	
    // 在本类中使用通用切点表达式
    @Around("pointcut()")
    public void aroundAdvice(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
        System.out.println("环绕通知开始");
        // 执行目标方法
        proceedingJoinPoint.proceed();
        System.out.println("环绕通知结束");
    }

    @Before("pointcut()")
    public void beforeAdvice(){
        System.out.println("前置通知");
    }

    @AfterReturning("pointcut()")
    public void afterReturningAdvice(){
        System.out.println("后置通知");
    }

    @AfterThrowing("pointcut()")
    public void afterThrowingAdvice(){
        System.out.println("异常通知");
    }

    @After("pointcut()")
    public void afterAdvice(){
        System.out.println("最终通知");
    }

}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值