Spring框架(三)——AOP--实现方式

Spring对AOP的实现包括以下3种方式:

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

在本篇文章中,我们主要讲解前两种方式。


  1. 基于AspectJ的AOP注解式开发
    1. 定义目标类以及目标方法
      1. // 目标类
        public class OrderService {
            // 目标方法
            public void generate(){
                System.out.println("订单已生成!");
            }
        }
    2. 定义切面类
      1. // 切面类
        @Aspect
        public class MyAspect {
        }

        注解@Aspect会告诉Spring该类是一个注解类

    3. 目标类和切面类都纳入spring bean管理
      1. 在目标类OrderService上添加**@Component**注解。
        在切面类MyAspect类上添加**@Component**注解。
    4. 在spring配置文件中添加组建扫描
      1. <!--开启组件扫描-->
            <context:component-scan base-package="com.xxx.spring6.service"/>

        这里是单单扫描com.xxx.spring6.service中的所有类,如果想扩大,我们完全可以扫描com.xxx下的所有包

    5. 在切面类中添加通知
      1. // 切面类
        @Aspect
        @Component
        public class MyAspect {
            // 这就是需要增强的代码(通知)
            public void advice(){
                System.out.println("我是一个通知");
            }
        }
    6. 在通知上添加切点表达式
      1. // 切面类
        @Aspect
        @Component
        public class MyAspect {
            
            // 切点表达式
            @Before("execution(* com.xxx.spring6.service.OrderService.*(..))")
            // 这就是需要增强的代码(通知)
            public void advice(){
                System.out.println("我是一个通知");
            }
        }

        其中,注解@Before表示前置通知(具体的通知类型在上一篇有讲过AOP基础部分
        ,下面也会有方法中的通知注解)

    7. 在spring配置文件中启用自动代理
      •  <!--开启组件扫描-->
            <context:component-scan base-package="com.xxx.spring6.service"/>
            <!--开启自动代理-->
            <aop:aspectj-autoproxy proxy-target-class="true"/>

        <aop:aspectj-autoproxy  proxy-target-class="true"/> 开启自动代理之后,凡是带有@Aspect注解的bean都会生成代理对象。
        proxy-target-class="true" 表示采用cglib动态代理。
        proxy-target-class="false" 表示采用jdk动态代理。默认值是false。即使写成false,当没有接口的时候,也会自动选择cglib生成代理类(AOP的底层就是动态代理,关于动态代理的内容可查看代理机制)        

  • 通知类型
    • 前置通知:@Before 目标方法执行之前的通知
    • 后置通知:@AfterReturning 目标方法执行之后的通知
    • 环绕通知:@Around 目标方法之前添加通知,同时目标方法执行之后添加通知。
    • 异常通知:@AfterThrowing 发生异常之后执行的通知
    • 最终通知:@After 放在finally语句块中的通知
  • 接下来,编写程序来测试这几个通知的执行顺序
    • // 切面类
      @Component
      @Aspect
      public class MyAspect {
      
          @Around("execution(* com.xxx.spring6.service.OrderService.*(..))")
          public void aroundAdvice(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
              System.out.println("环绕通知开始");
              // 执行目标方法。
              proceedingJoinPoint.proceed();
              System.out.println("环绕通知结束");
          }
      
          @Before("execution(* com.xxx.spring6.service.OrderService.*(..))")
          public void beforeAdvice(){
              System.out.println("前置通知");
          }
      
          @AfterReturning("execution(* com.xxx.spring6.service.OrderService.*(..))")
          public void afterReturningAdvice(){
              System.out.println("后置通知");
          }
      
          @AfterThrowing("execution(* com.xxx.spring6.service.OrderService.*(..))")
          public void afterThrowingAdvice(){
              System.out.println("异常通知");
          }
      
          @After("execution(* com.xxx.spring6.service.OrderService.*(..))")
          public void afterAdvice(){
              System.out.println("最终通知");
          }
      
      }
        读者可自行测试,其中异常通知需要产生异常才能触发,当发生异常之后,最终通知也会执行,因为最终通知@After会出现在finally语句块中。出现异常之后,**后置通知**和**环绕通知的结束部分**不会执行
  • 切面的先后顺序
    • 我们知道,业务流程当中不一定只有一个切面,可能有的切面控制事务,有的记录日志,有的进行安全控制,如果多个切面的话,顺序如何控制:可以使用@Order注解来标识切面类,为@Order注解的value指定一个整数型的数字,数字越小,优先级越高
  • 优化使用切点表达式​​​​​​​
    • 上面的切点表达式重复写了多次,没有得到复用,同时如果要修改切点表达式,需要修改多处,难维护
    • 我们可以将切点表达式单独的定义出来,在需要的位置引入即可
      • // 切面类
        @Component
        @Aspect
        @Order(2)
        public class MyAspect {
            
            @Pointcut("execution(* com.xxx.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("最终通知");
            }
        
        }

        使用@Pointcut注解来定义独立的切点表达式。
        注意这个@Pointcut注解标注的方法随意,只是起到一个能够让@Pointcut注解编写的位置

  1. 基于XML配置方式的AOP
    1. 编写目标类
      1. // 目标类
        public class VipService {
            public void add(){
                System.out.println("保存vip信息。");
            }
        }
    2. 编写切面类,并且编写通知
      1. // 负责计时的切面类
        public class TimerAspect {
            
            public void time(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
                long begin = System.currentTimeMillis();
                //执行目标
                proceedingJoinPoint.proceed();
                long end = System.currentTimeMillis();
                System.out.println("耗时"+(end - begin)+"毫秒");
            }
        }
    3. 编写spring配置文件
      1.     <!--纳入spring bean管理-->
            <bean id="vipService" class="com.xxx.spring6.service.VipService"/>
            <bean id="timerAspect" class="com.xxx.spring6.service.TimerAspect"/>
        
            <!--aop配置-->
            <aop:config>
                <!--切点表达式-->
                <aop:pointcut id="p" expression="execution(* com.xxx.spring6.service.VipService.*(..))"/>
                <!--切面-->
                <aop:aspect ref="timerAspect">
                    <!--切面=通知 + 切点-->
                    <aop:around method="time" pointcut-ref="p"/>
                </aop:aspect>
            </aop:config>
        </beans>
  • 15
    点赞
  • 20
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Spring AOPSpring框架中的一个重要模块,它提供了面向切面编程(AOP)的支持。AOP是一种编程思想,它可以在不改变原有代码的情况下,通过在程序运行时动态地将代码“织入”到现有代码中,从而实现对原有代码的增强。 Spring AOP提供了基于注解的AOP实现,使得开发者可以通过注解的方式来定义切面、切点和通知等相关内容,从而简化了AOP的使用。 下面是一个基于注解的AOP实现的例子: 1. 定义切面类 ```java @Aspect @Component public class LogAspect { @Pointcut("@annotation(Log)") public void logPointcut() {} @Before("logPointcut()") public void beforeLog(JoinPoint joinPoint) { // 前置通知 System.out.println("执行方法:" + joinPoint.getSignature().getName()); } @AfterReturning("logPointcut()") public void afterLog(JoinPoint joinPoint) { // 后置通知 System.out.println("方法执行完成:" + joinPoint.getSignature().getName()); } @AfterThrowing(pointcut = "logPointcut()", throwing = "ex") public void afterThrowingLog(JoinPoint joinPoint, Exception ex) { // 异常通知 System.out.println("方法执行异常:" + joinPoint.getSignature().getName() + ",异常信息:" + ex.getMessage()); } } ``` 2. 定义业务逻辑类 ```java @Service public class UserService { @Log public void addUser(User user) { // 添加用户 System.out.println("添加用户:" + user.getName()); } @Log public void deleteUser(String userId) { // 删除用户 System.out.println("删除用户:" + userId); throw new RuntimeException("删除用户异常"); } } ``` 3. 在配置文件中开启AOP ```xml <aop:aspectj-autoproxy/> <context:component-scan base-package="com.example"/> ``` 在这个例子中,我们定义了一个切面类LogAspect,其中通过@Aspect注解定义了一个切面,通过@Pointcut注解定义了一个切点,通过@Before、@AfterReturning和@AfterThrowing注解分别定义了前置通知、后置通知和异常通知。 在业务逻辑类中,我们通过@Log注解标注了需要增强的方法。 最后,在配置文件中,我们通过<aop:aspectj-autoproxy/>开启了AOP功能,并通过<context:component-scan>扫描了指定包下的所有组件。 这样,当我们调用UserService中的方法时,就会触发LogAspect中定义的通知,从而实现对原有代码的增强。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值