• spring5:Aop思想和注解Aop


    一.Aop是什么?

    1.Aop

    • 面向切面编程
    • Oop(面向对象编程)

    2.举例说明

    • 加入日志功能:记录输入输出
    • 即在核心功能头尾加入非核心功能
    • 举例:在核心功能处加入非核心功能
    public class CalculatorLogImpl implements Calculator {
    @Override
    public int add(int i, int j) {
    System.out.println("[日志] add 方法开始了,参数是:" + i + "," + j);
    int result = i + j;
    System.out.println("方法内部 result = " + result);
    System.out.println("[日志] add 方法结束了,结果是:" + result);
    return result;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    3.解决方案:可用代理模式

    ①代理模式简介:

    • 将不属于目标方法核心逻辑的代码从目标中抽取出来(解耦)
    • 创建一个代理类来让代理类间接访问目标方法并可完成非核心代码的功能

    ②静态代理(写死的代码不具备灵活性)

    • 静态代理重点:
      要实现目标对象同样实现的抽象方法
      重写抽象方法并且加入附加功能调用目标对象的该方法
    • 举例
    public class CalculatorStaticProxy implements Calculator {
    // 将被代理的目标对象声明为成员变量
    private Calculator target;
    public CalculatorStaticProxy(Calculator target) {
    this.target = target;
    }
    @Override
    public int add(int i, int j) {
    // 附加功能由代理类中的代理方法来实现
    System.out.println("[日志] add 方法开始了,参数是:" + i + "," + j);
    // 通过目标对象来实现核心业务逻辑
    int addResult = target.add(i, j);
    System.out.println("[日志] add 方法结束了,结果是:" + addResult);
    return addResult;
    }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    ③动态代理(具备灵活性)

    • 动态代理注意点
      获得类加载器加载类(代理类需要被加载)
      获得目标对象实现的接口(代理类需要实现相同接口)
      获得重写抽象方法的方式(方法名,参数)
    • 举例
    public class ProxyFactory {
    private Object target;
    public ProxyFactory(Object target) {
    this.target = target;
    }
    public Object getProxy(){
    /**
    * newProxyInstance():创建一个代理实例
    * 其中有三个参数:
    * 1、classLoader:加载动态生成的代理类的类加载器
    * 2、interfaces:目标对象实现的所有接口的class对象所组成的数组
    * 3、invocationHandler:设置代理对象实现目标对象方法的过程,即代理类中如何重写接
    口中的抽象方法
    */
    ClassLoader classLoader = target.getClass().getClassLoader();
    Class<?>[] interfaces = target.getClass().getInterfaces();
    InvocationHandler invocationHandler = new InvocationHandler() {
    @Override
    public Object invoke(Object proxy, Method method, Object[] args)
    throws Throwable {
    /**
    * proxy:代理对象
    * method:代理对象需要实现的方法,即其中需要重写的方法
    * args:method所对应方法的参数
    */
    Object result = null;
    try {
    System.out.println("[动态代理][日志] "+method.getName()+",参
    数:"+ Arrays.toString(args));
    result = method.invoke(target, args);
    System.out.println("[动态代理][日志] "+method.getName()+",结
    果:"+ result);
    } catch (Exception e) {
    e.printStackTrace();
    System.out.println("[动态代理][日志] "+method.getName()+",异
    常:"+e.getMessage());
    } finally {
    System.out.println("[动态代理][日志] "+method.getName()+",方法
    执行完毕");
    }
    return result;
    }
    };
    return Proxy.newProxyInstance(classLoader, interfaces,
    invocationHandler);
    }
    }
    @Test
    public void testDynamicProxy(){
    ProxyFactory factory = new ProxyFactory(new CalculatorLogImpl());
    Calculator proxy = (Calculator) factory.getProxy();
    proxy.div(1,0);
    //proxy.div(1,1);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 动态代理的两种方式
      jdk动态代理:动态代理实现接口
      cglib动态代理:动态代理继承目标类

    二.注解Aop

    1.概念

    • 横切关注点:对于目标对象来说的非核心业务(日志功能)
    • 通知:横切关注点封装到切面中的方法叫通知
    • 切面:封装通知方法的类
    • 目标:被代理的对象
    • 代理:创建的代理对象
    • 切入点:从代码层面定位到需要连接点的方式

    2.两个过程

    • 抽取:抽取非核心代码即横切关注点到切面中形成通知
    • 套:将通知加入到切入点中。

    3.Aop思想过程

    ①整体框架

    在这里插入图片描述

    ②通知方法的声明

    • 前置通知:使用@Before注解标识,在被代理的目标方法前执行
    • 返回通知:使用@AfterReturning注解标识,在被代理的目标方法成功结束后执行(寿终正寝)
    • 异常通知:使用@AfterThrowing注解标识,在被代理的目标方法异常结束后执行(死于非命)
    • 后置通知:使用@After注解标识,在被代理的目标方法最终结束后执行(盖棺定论)
    • 环绕通知:使用@Around注解标识,使用try…catch…finally结构围绕整个被代理的目标方法,包括上面四种通知对应的所有位置

    ③切入点的参数

    • 返回值+方法名的具体位置
      • public int com.atguigu.aop.annotation.CalculatorImpl.*(…)
        表示返回值int具体位置为.CalculatorImpl下载全部方法参数不限制
      • (* com.atguigu.aop.annotation.CalculatorImpl.*(…)
        表示返回值不限具体位置为.CalculatorImpl下载全部方法参数不限制
    • 切入点表达式的重用
    @Pointcut("execution(* com.atguigu.aop.annotation.*.*(..))")
    public void pointCut(){}
    
    • 1
    • 2
    • 同一切面中使用
    @Before("pointCut()")
    public void beforeMethod(JoinPoint joinPoint){
    String methodName = joinPoint.getSignature().getName();
    String args = Arrays.toString(joinPoint.getArgs());
    System.out.println("Logger-->前置通知,方法名:"+methodName+",参数:"+args);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 不同切面中使用
    @Before("com.atguigu.aop.CommonPointCut.pointCut()")
    public void beforeMethod(JoinPoint joinPoint){
    String methodName = joinPoint.getSignature().getName();
    String args = Arrays.toString(joinPoint.getArgs());
    System.out.println("Logger-->前置通知,方法名:"+methodName+",参数:"+args);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    ④方法的参数

    • 获取方法的签名即方法的名称参数等
    @Before("execution(public int com.atguigu.aop.annotation.CalculatorImpl.*(..))")
    public void beforeMethod(JoinPoint joinPoint){
    //获取连接点的签名信息
    String methodName = joinPoint.getSignature().getName();
    //获取目标方法到的实参信息
    String args = Arrays.toString(joinPoint.getArgs());
    System.out.println("Logger-->前置通知,方法名:"+methodName+",参数:"+args);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 获取方法的返回值
    @AfterReturning(value = "execution(* com.atguigu.aop.annotation.CalculatorImpl.*
    (..))", returning = "result")
    public void afterReturningMethod(JoinPoint joinPoint, Object result){
    String methodName = joinPoint.getSignature().getName();
    System.out.println("Logger-->返回通知,方法名:"+methodName+",结果:"+result);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 获取目标方法的异常
    @AfterThrowing(value = "execution(* com.atguigu.aop.annotation.CalculatorImpl.*
    (..))", throwing = "ex")
    public void afterThrowingMethod(JoinPoint joinPoint, Throwable ex){
    String methodName = joinPoint.getSignature().getName();
    System.out.println("Logger-->异常通知,方法名:"+methodName+",异常:"+ex);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    5.整体举例

    // @Aspect表示这个类是一个切面类
    @Aspect
    // @Component注解保证这个切面类能够放入IOC容器
    @Component
    public class LogAspect {
    @Before("execution(public int com.atguigu.aop.annotation.CalculatorImpl.*
    (..))")
    public void beforeMethod(JoinPoint joinPoint){
    String methodName = joinPoint.getSignature().getName();
    String args = Arrays.toString(joinPoint.getArgs());
    System.out.println("Logger-->前置通知,方法名:"+methodName+",参
    数:"+args);
    }
    @After("execution(* com.atguigu.aop.annotation.CalculatorImpl.*(..))")
    public void afterMethod(JoinPoint joinPoint){
    String methodName = joinPoint.getSignature().getName();
    System.out.println("Logger-->后置通知,方法名:"+methodName);
    }
    @AfterReturning(value = "execution(*
    com.atguigu.aop.annotation.CalculatorImpl.*(..))", returning = "result")
    public void afterReturningMethod(JoinPoint joinPoint, Object result){
    String methodName = joinPoint.getSignature().getName();
    System.out.println("Logger-->返回通知,方法名:"+methodName+",结
    果:"+result);
    }
    @AfterThrowing(value = "execution(*
    com.atguigu.aop.annotation.CalculatorImpl.*(..))", throwing = "ex")
    public void afterThrowingMethod(JoinPoint joinPoint, Throwable ex){
    String methodName = joinPoint.getSignature().getName();
    System.out.println("Logger-->异常通知,方法名:"+methodName+",异常:"+ex);
    }
    @Around("execution(* com.atguigu.aop.annotation.CalculatorImpl.*(..))")
    public Object aroundMethod(ProceedingJoinPoint joinPoint){
    String methodName = joinPoint.getSignature().getName();
    String args = Arrays.toString(joinPoint.getArgs());
    Object result = null;
    try {
    System.out.println("环绕通知-->目标对象方法执行之前");
    //目标对象(连接点)方法的执行
    result = joinPoint.proceed();
    System.out.println("环绕通知-->目标对象方法返回值之后");
    } catch (Throwable throwable) {
    throwable.printStackTrace();
    System.out.println("环绕通知-->目标对象方法出现异常时");
    } finally {
    System.out.println("环绕通知-->目标对象方法执行完毕");
    }
    return result;
    }
    }
    
            ApplicationContext ioc = new ClassPathXmlApplicationContext("Spring-AOP.xml");
            Calculator calculator = ioc.getBean(Calculator.class);
            int add = calculator.add(10, 1);
            System.out.println(add);
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
  • 相关阅读:
    SpringBoot 04 多环境配置和配置文件小技巧
    大学4年做出来这个算不算丢人
    geoserver多种数据源图层发布详解
    JDBC 在性能测试中的应用
    如何在 Ubuntu VPS 实例上安装 Chef 服务器、工作站和客户端
    【译】.NET 8 网络改进(二)
    输运方程的推导
    ch1_系统启动_bootsect.s
    【TCP/IP 网络模型】
    手把手教你做主成分分析
  • 原文地址:https://blog.csdn.net/qq_44724899/article/details/127716068