• java干货 spring aop的理解和使用


    一、AOP 概念

    1.1 aop 思想

    APO (面向切面编程) 是一种编程思想,它通过将通用的横向关注点(日志、事务、权限控制等)与业务逻辑分离,实现解耦,使得代码更易于维护。核心就是将非核心代码抽取出来,在实际运行时将代码切回业务代码。如何切回?使用cglib动态代理jdk动态代理实现。

    1.2 aop 应用场景

    • 日志记录:在系统中记录日志是非常重要的,可以使用aop来实现记录日志的功能,可以在业务方法执行前、执行后或者抛出异常时记录日志
    • 事务处理:在数据库操作中,使用事务可以保证数据的一致性,可以使用aop 来实现处理事务的功能,可以在方法开始前开启事务,在方法执行完毕后提交事务,或者在抛出异常时回滚事务
    • 安全控制:在系统中包含某些需要安全控制的操作,如登录、授权等,可以使用aop 实现安全控制的功能 ,在方法执行前进行权限判断,如果用户没有权限,则抛出异常或者转到错误页面,防止未经授权的访问
    • 异常处理: 系统中可能会出现各种异常,如空指针异常、数据库链接异常等,可以使用aop 来实现处理异常的功能,在方法执行过程中,如果出现异常,则进行异常处理(如记录日志发送邮件等)
    • 性能监控: 在系统中,有时需要对某些方法的性能进行监控,以找到系统的瓶颈进行优化,可以使用aop 来实现性能监控的功能,可以在方法执行前记录时间戳,在方法执行完毕后计算执行时间并输出到日志中
    • 缓存控制:在系统中有些数据需要缓存起来以提高访问速度,可以使用aop 来实现缓存控制的功能,可以在方法执行前查询缓存中是否有数据,如果有直接返回,否则执行方法并将返回值存入缓存

    二、aop 如何使用

    2.1 八个核心名词

    • 横切关注点

      • 从每个方法中抽取出来的同一类非核心业务(如日志是一个业务,事务是一个业务)。在同一个项目,可以使用多个横向关注点,对同一个方法进行不同方面(日志、事务等)的的增强
      • AOP 把软件系统分为两个部分,核心关注点横切关注点,核心业务是核心关注点,与之关系不大的部分是横切关注点。
      • 横切关注点的特点是,它们经常发生在核心关注点的多处,而且各处基本相似,比如权限认证、日志、事务、异常等
    • 通知(也叫增强)

      • 每一个横切关注点上要做的事情都需要写一个方法来实现,这样的方法叫做通知方法增强方法
      • 前置通知:在被代理的目标的方法前执行
      • 返回通知: 在被代理的目标方法成功后执行,可以修改返回值
      • 异常通知: 在被代理的目标方法异常结束后执行
      • 后置通知:在被代理的目标方法最终结束后执行,不可以修改返回值
      • 环绕通知: 使用 try…catch…finally 结构围绕整个被代理的目标方法,包括上面四种通知的所有位置
    • 连接点 (joinpoint)
      这是一个纯逻辑概念,指哪些可能被拦截到的点,也就被 通知增强 的各个点

    • 切入点 (pointcut)
      定位连接点的方式,可以理解为被选中的连接点。是一个表达式,比如execution(* com.spring.service.impl…(…))指定好了切入点,框架才知道在哪里进行增强

    • 切面(aspect)
      切入点和增强的结合,是一个类。在各个切入点进行增强,好比切西瓜,形成一个切面

    • 目标 target
      被代理的目标对象

    • 代理 proxy
      为了对目标对象应用通知而创建的对象,好比中介,目标是房东

    • 织入
      是指把通知应用到目标上,生成代理对象的过程,可以在编译时织入,也可以在运行时织入,spring 采用后者,也就是动态代理

    2.2 代码实现

    • 加入依赖
      spring-context 已经集成了aop ,aop 只为 ioc 容器中的对象创建代理对象
    
                org.springframework
                spring-context
                6.0.6
            
    
            
                org.springframework
                spring-aspects
                6.0.6
            
     
                org.springframework
                spring-test
                6.0.6
            
    
    • 步骤

      • 编写业务类
      • 定义增强类,在类中编写增强方法,获取核心业务方法信息(方法名,参数,访问修饰符,所属的类信息等),编写非核心业务逻辑,如日志输出等
      • 在增强方法加注解,指定切入点(通过切点表达式指明)以及增强的时机(前置,后置等),这样就知道在哪个类的哪个方法上进行怎么样的增强
      • 增强类上加**@Component注解**,注入ioc容器,加**@Aspect注解**,指定为切面
      • 编写配置类加@Configuration注解,指定为配置类,指定扫描包(加@ComponentScan注解),增强类也必须注入ioc 容器,开启aspectj aop支持(加@EnableAspectJAutoProxy注解)
      • 在junit 测试类上加上@SpringJUnitConfig(value = JavaConfig.class),指定配置类,那么spring 将自动为我们创建ioc 容器,而不用我们手动 new ClassPathXmlApplication
    • 补充

      • 配置类相当于一个.xml文件,含有扫描包的路径。
      • @SpringJUnitConfig(value = JavaConfig.class) 相当于 new ClassPathXmlApplication(JavaConfig.class)
      • @SpringJUnitConfig(locations = {“xxx.xml”}) 相当于 new ClassPathXmlApplication(“xxx.xml”)
      • @EnableAspectJAutoProxy 相当于在xml 中配置aop 标签,@ComponentScan注解 相当于 component-sacn标签
    
    
        
        
    
    
    • 编写业务类
      接口
    public interface Calculator {
        public double div(int i, int j);
    }
    

    实现类

    @Component
    public class CalculatorImpl implements Calculator {
        @Override
        public Double div(int i, int j){
            try {
                System.out.println("业务方法--正在计算除法");
                double r = i / j;
                return r;
            }catch (Exception e){
                System.out.println("业务方法--出现异常");
                throw e; // 抛异常
            }finally {
                // 回收资源
                System.out.println("业务方法----finally!");
            }
        }
    
    }
    
    • 编写增强类,也就是切面
    @Component
    @Aspect
    public class LogAdvice {
        @Before(value = "execution(* com.binbin.service.impl.*.*(..))")
        public void atBefore(JoinPoint joinPoint){
            // 获取方法名
            String methodName = joinPoint.getSignature().getName();
            System.out.println("Method Name: " + methodName);
            System.out.println("Before-前置增强!");
        }
    
        @After(value = "execution(* com.binbin.service.impl.*.*(..))")
        public void atAfter(JoinPoint joinPoint){
            System.out.println("After-后置增强!");
        }
    
        @AfterReturning(value = "execution(* com.binbin.service.impl.*.*(..))",returning="res")
        public void atAfterReturning(JoinPoint joinPoint,Object res){
            System.out.println("AfterReturning -后置返回增强! 返回结果 res = " + res.toString());
        }
    
        @AfterThrowing(value = "execution(* com.binbin.service.impl.*.*(..))",throwing = "e")
        public void error(Exception e){
            System.out.println("AfterThrowing-异常处理增强!" + e);
        }
    }
    
    • 编写配置类
    @Configuration
    @ComponentScan(basePackages = {"com.binbin.service","com.binbin.advice"})
    @EnableAspectJAutoProxy // 开启aop 功能
    public class JavaConfig {
    }
    
    • 编写测试类
    @SpringJUnitConfig(value = JavaConfig.class) // 由spring 创建ioc 容器,相当于 new ClassPathXmlApplication("xxx.xml") 或者 new ClassPathXmlApplication(xxx.class)
    public class SpringTest {
        @Autowired
        Calculator calculator;
        @Test
        public void  test() {
            try {
                Double r = calculator.div(1, 1);
            }catch (Exception e){
                System.out.println();
            }
    
        }
    }
    

    调用calculator.div(1, 1):
    依次触发 Before、AfterReturning、After,无异常

    Method Name: div
    Before-前置增强!
    业务方法--正在计算除法
    业务方法----finally!
    AfterReturning -后置返回增强! 返回结果 res = 1.0
    After-后置增强
    

    调用calculator.div(1, 0):
    依次触发 Before、AfterThrowing、After有异常

    Method Name: div
    Before-前置增强!
    业务方法--正在计算除法
    业务方法--出现异常
    业务方法----finally!
    AfterThrowing-异常处理增强!java.lang.ArithmeticException: / by zero
    After-后置增强!
    
    
    • 小结
      执行顺序:前置增强业务核心返回值后置异常增强后置增强
      异常还是会传播方法调用处

    2.3 统一切点管理

    • 将切点表达式全部放到一个类中,并加@Component注解多个execution 可以用 || 分开,方法上加@Pointcut 注解
    @Component
    public class MyPointCut{
        @Pointcut(value = "execution(* com.binbin.service.impl.*.*(..)) || execution(* com.binbin.pointcut.*.*())")
        public void pc1(){}
    
     @Pointcut(value = "execution(* com.binbin.service.impl.*.*(..)) || execution(* com.binbin.pointcut.*.*())")
        public void pc2(){}
    }
    
    • 在切面中使用,通过 类的全限名.方法名() 进行使用,多个可以使用 || 分开
    @Component
    @Aspect
    public class LogAdvice {
        @Before("com.binbin.pointcut.MyPointCut.pc1() || com.binbin.pointcut.MyPointCut.pc1()")
        public void atBefore(JoinPoint joinPoint){
            // 获取方法名
            String methodName = joinPoint.getSignature().getName();
            int modifier = joinPoint.getSignature().getModifiers();
            System.out.println("Modifier: " + Modifier.toString(modifier));
            System.out.println("Method Name: " + methodName);
            System.out.println("Before-前置增强!");
        }
    }    
    

    2.4 环绕通知

    • 相当于 前置通知、后置通知、异常通知的组合实现,可以分开写,也可以使用环绕通知,环绕通知,需要自己调用目标对象的方法
    @Component
    @Aspect
    public class MyAroundAdvice {
    
        @Around(value = "com.binbin.pointcut.MyPointCut.pc1() || com.binbin.pointcut.MyPointCut.pc2()")
        public Object testAround(ProceedingJoinPoint proceedingJoinPoint){
            Object[] args = proceedingJoinPoint.getArgs();// 获取目标方法参数
            Object result = null;
            try {
                System.out.println("开启事务!");
                result = proceedingJoinPoint.proceed(args);
                System.out.println("提交事务");
            }catch(Throwable t){
                System.out.println("回滚事务");
                throw new RuntimeException(); // 抛异常
            }finally {
                System.out.println("回收资源");
            }
            return  result;
        }
    }
    

    2.5 设置切面优先级

    • 一个切点可能有多个切面进行增强,因此可以设置优先级,从而改变各个切面的执行顺序
    • 通过加@Order 注解进行实现,数字越小优先级越高
    @Component
    @Aspect
    @Order(1)
    public class TransitionAdvice {
        @Before("com.binbin.pointcut.MyPointCut.pc1() || com.binbin.pointcut.MyPointCut.pc2()")
        public void atBefore(JoinPoint joinPoint){
            System.out.println("TransitionAdvice-Before-前置增强!");
        }
    
        @After(value = "execution(* com.binbin.service.impl.*.*(..))")
        public void atAfter(JoinPoint joinPoint){
            System.out.println("TransitionAdvice-After-后置增强!");
        }
    
        @AfterReturning(value = "execution(* com.binbin.service.impl.*.*(..))",returning="res")
        public void atAfterReturning(JoinPoint joinPoint,Object res){
            System.out.println("TransitionAdvice-AfterReturning -后置返回增强! 返回结果 res = " + res.toString());
        }
    
        @AfterThrowing(value = "execution(* com.binbin.service.impl.*.*(..))",throwing = "e")
        public void error(Exception e){
            System.out.println("TransitionAdvice-AfterThrowing-异常处理增强!" + e);
        }
    }
    
    

    不抛异常时的运行结果:
    TransitionAdvice 优先级高before 最先执行,但是AfterReturning 和 After 最后执行,这好比嵌套的括号

    TransitionAdvice-Before-前置增强!
    Before-前置增强!
    业务方法--正在计算除法
    业务方法----finally!
    AfterReturning -后置返回增强! 返回结果 res = 1.0
    After-后置增强!
    TransitionAdvice-AfterReturning -后置返回增强! 返回结果 res = 1.0
    TransitionAdvice-After-后置增强!
    

    抛异常时的运行结果:
    TransitionAdvice 优先级高before 最先执行,但是AfterThrowing 和 After 最后执行,这好比嵌套的括号

    TransitionAdvice-Before-前置增强!
    Before-前置增强!
    业务方法--正在计算除法
    业务方法--出现异常
    业务方法----finally!
    AfterThrowing-异常处理增强!java.lang.ArithmeticException: / by zero
    After-后置增强!
    TransitionAdvice-AfterThrowing-异常处理增强!java.lang.ArithmeticException: / by zero
    TransitionAdvice-After-后置增强!
    
  • 相关阅读:
    自然语言处理(NLP)—— Rasa中config.yml
    element ui框架(登录窗口)
    第九天!玩转langchain!回调处理器!一篇学会日志+监控+流式传输!9/10
    Linux计划任务
    模救护车发声
    CSDN程序设计精品课程——Java程序设计(Java语言概述·Java语言基础·Java基本数据类型)
    微信小程序 地图map组件 SDK 并 实现导航
    什么是容器?容器有什么作用?
    VPP二层接口,不是翻墙
    使用cpolar发布群晖NAS上的网页(2)
  • 原文地址:https://blog.csdn.net/qq_51305563/article/details/139861076