首页 > 其他分享 >4_面向切面AOP

4_面向切面AOP

时间:2024-03-06 13:35:07浏览次数:11  
标签:int 切面 System 面向 AOP println out public result

面向切面AOP

1. 场景模拟

声明计算器接口Calculator,包含加减乘除的抽象方法

public interface Calculator {
    int add(int i, int j);
    int sub(int i, int j);
    int mul(int i, int j);
    int div(int i, int j);
}

创建接口实现类:CalculatorImpl

public class CalculatorImpl implements Calculator {
    @Override
    public int add(int i, int j) {
        int result = i + j;
        System.out.println("方法内部 result =" + result);
        return result;
    }

    @Override
    public int sub(int i, int j) {
        int result = i - j;
        System.out.println("方法内部 result =" + result);
        return result;
    }

    @Override
    public int mul(int i, int j) {
        int result = i * j;
        System.out.println("方法内部 result =" + result);
        return result;
    }

    @Override
    public int div(int i, int j) {
        int result = i / j;
        System.out.println("方法内部 result =" + result);
        return result;
    }
}

创建带日志功能的实现类CalculatorLogImpl

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("[日志] add方法结束了,结果是 " + result);
        return result;
    }

    @Override
    public int sub(int i, int j) {
        System.out.println("[日志] add方法开始了,参数是 " + i + "," + j);
        int result = i - j;
        System.out.println("[日志] add方法结束了,结果是 " + result);
        return result;
    }

    @Override
    public int mul(int i, int j) {
        System.out.println("[日志] add方法开始了,参数是 " + i + "," + j);
        int result = i * j;
        System.out.println("[日志] add方法结束了,结果是 " + result);
        return result;
    }

    @Override
    public int div(int i, int j) {
        System.out.println("[日志] add方法开始了,参数是 " + i + "," + j);
        int result = i / j;
        System.out.println("[日志] add方法结束了,结果是 " + result);
        return result;
    }
}

提出问题

针对带日志功能的实现,我们发现有如下缺陷:

  • 对核心业务有干扰,导致程序员在开发核心功能时分散了注意力
  • 附加功能分散在各个业务功能的方法中,不利于维护统一

解决思路:解耦合,让核心代码与日志分离

目的就是为了减少非业务代码对业务代码的干扰!有利于统一维护。

2. 代理模式

开始->调用代理对象->调用目标方法->目标方法把结果返回代理对象->代理对象把返回值返回给最初的调用者。

生活中的代理:

  • 广告商找明星拍广告需要经过经纪人
  • 房产中介是买卖双方的代理
  • 合作伙伴找大老板合作要约见面时间需要经过秘书

2.1 静态代理

静态代理能够实现代码的解耦,但是由于代码写死了,完全不具备灵活性,因此这并不是一个很好的方案。

创建静态代理

public class CalculatorStaticProxy implements Calculator{
    //被代理的目标对象要穿递过来
    private Calculator calculator;
    public CalculatorStaticProxy(Calculator calculator){
        this.calculator = calculator;
    }

    @Override
    public int add(int i, int j) {
        //输出日志
        System.out.println("[日志] add方法开始了,参数是 " + i + "," + j);
        //调用核心业务
        int result = calculator.add(i,j);
        //输出日志
        System.out.println("[日志] add方法结束了,结果是 " + result);
        return result;
    }

    @Override
    public int sub(int i, int j) {
        return 0;
    }

    @Override
    public int mul(int i, int j) {
        return 0;
    }

    @Override
    public int div(int i, int j) {
        return 0;
    }
}

提出进一步的需求:将日志功能集中到一个代理类中,将来有任何日志需求,都通过一个代理类来实现。这就是需要使用动态代理技术。

2.2 动态代理

调用流程:调用代理类->操作前->调用核心代码->调用后->结束

生产代理工厂ProxyFactory:

package com.lily;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.Arrays;

public class ProxyFactory {
    //创建一个目标对象
    private Object target;

    public ProxyFactory(Object target) {
        this.target = target;
    }

    //创建一个方法,返回一个代理对象
    public Object getProxy() {
        /**
         * Proxy.newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler handler);
         *
         * ClassLoader loader:加载动态生成代理类的类加载器
         * interfaces:目标对象实现的所有接口class类型的数组
         * handler:设置代理对象它实现目标对象方法的过程
         */
        ClassLoader classLoader = this.target.getClass().getClassLoader();
        Class<?>[] interfaces = target.getClass().getInterfaces();
        var invocationHandler = new InvocationHandler() {
            @Override
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                /**
                 * proxy:代理对象
                 * method:代理对象要实现的方法
                 * args:method方法当中的参数
                 */
                //调用方法前输出
                System.out.println("[动态代理][日志]" + method.getName() + ", 参数:" + Arrays.toString(args));
                Object result = method.invoke(target, args);
                //调用方法后输出
                System.out.println("[动态代理][日志]" + method.getName() + ", 结果:" + result);
                return result;
            }
        };
        return Proxy.newProxyInstance(classLoader, interfaces, invocationHandler);
    }
}

创建测试类测试:

public class Main {
    public static void main(String[] args) {
        //创建代理对象
        ProxyFactory proxyFactory = new ProxyFactory(new CalculatorImpl());
        //调用方法
        Calculator proxy = (Calculator)proxyFactory.getProxy();
        proxy.add(1,2);
    }
}

结果如下:

[动态代理][日志]add, 参数:[1, 2]
方法内部 result =3
[动态代理][日志]add, 结果:3

Process finished with exit code 0

3 AOP概念以及相关术语

AOP(Aspect Oriented Programming)是一种设计思想,面向横切面编程。它以通过预编译的方法和运行期动态代理方式实现,在不修改源代码的情况下,给程序动态统一添加额外功能的一种技术。利用AOP可以对各个部分进行隔离,进一步降低耦合度。

每一个横切关注点要做的事情都需要写一个方法来实现,这样的方法称之为通知方法。

  • 前置通知:在被代理的目标方法执行
  • 返回通知:在被代理的目标方法成功结束后执行
  • 异常通知:在被代理的目标方法异常结束后执行
  • 后置通知:在被代理的目标方法最终结束执行
  • 环绕通知:使用tay...catch...finally结构围绕整个被代理的目标方法,包括上面四种通知对应的位置。

4. 基于注解的AOP

AOP底层使用的就是动态代理,其中分为两类:

  • JDK动态代理->适用于有接口的情况->生成接口实现类的代理对象。
  • cglib动态代理->适用于没有接口的情况->通过继承被代理的目标类,生成子类代理对象。

AspectJ->是AOP框架,Spring只是借用了AspectJ当中的注解实现了AOP功能。

引入如下依赖:

<!-- https://mvnrepository.com/artifact/org.springframework/spring-aop -->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-aop</artifactId>
            <version>6.0.2</version>
        </dependency>

        <!-- https://mvnrepository.com/artifact/org.springframework/spring-aspects -->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-aspects</artifactId>
            <version>6.0.2</version>
        </dependency>

创建切面类LogAspect并配置

package com.lily;

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;

@Component
@Aspect //  表示这是一个切面类
public class LogAspect {
    //设置切入点和通知类型
    /**
     * 通知类型:前置@Before(value="切入点表达式")
     * 返回@AfterReturning  异常@AfterThrowing  后置@After  环绕@Around
     */
    /**
     * 切入点表达式:
     * execution(方法权限修饰符 方法返回值 方法所在类的全类名(实现类).方法名(参数列表) )
     */
    @Before(value = "execution(public int com.lily.CalculatorImpl.add(..))")
    public void beforeMethod(JoinPoint joinPoint) {
        System.out.println("前置通知,参数名称:" + joinPoint.getArgs().toString());
    }

    @After(value = "execution(public int com.lily.CalculatorImpl.*(..))")
    public void afterMethod(JoinPoint joinPoint) {
        System.out.println("后置通知,方法名称:" + joinPoint.getSignature().getName());
    }


    @AfterReturning(value = "execution(public int com.lily.CalculatorImpl.*(..))", returning = "result")
    public void afterReturing(JoinPoint joinPoint, Object result) {
        System.out.println("返回通知,结果为:" + result);
    }

    @AfterThrowing(value = "execution(* com.lily.CalculatorImpl.*(..))", throwing = "exceptionMessage")
    public void afterThrowing(JoinPoint joinPoint, Throwable exceptionMessage) {
        System.out.println("异常通知,异常信息" + exceptionMessage);
    }

    @Around(value = "execution(* com.lily.CalculatorImpl.*(..))")
    public void around(JoinPoint joinPoint) {
        System.out.println("环绕信息");
    }
}

开启启AspectJ自动代理,为目标对象生成代理:

@ComponentScan("com.lily")
@EnableAspectJAutoProxy //开启AspectJ自动代理,为目标对象生成代理
public class Main {
    public static void main(String[] args) {

    }
}

package com.lily;

import org.springframework.stereotype.Component;

@Component
public class CalculatorImpl implements Calculator {
    @Override
    public int add(int i, int j) {
        int result = i + j;
        System.out.println("方法内部 result =" + result);
        return result;
    }

    @Override
    public int sub(int i, int j) {
        int result = i - j;
        System.out.println("方法内部 result =" + result);
        return result;
    }

    @Override
    public int mul(int i, int j) {
        int result = i * j;
        System.out.println("方法内部 result =" + result);
        return result;
    }

    @Override
    public int div(int i, int j) {
        int result = i / j;
        System.out.println("方法内部 result =" + result);
        return result;
    }
}

重用切入点表达式

由于程序中出现了大量重复的表达式,因此我们可以重用切入点表达式:

  • 声明

    @Pointcut(value = "execution(* com.lily.CalculatorImpl.*(..))")
        public void pointCut(){}
    
  • 使用

    //如果在同一个切面(同一个文件下):
    @After(value = "pointCut()")
    public void afterMethod(JoinPoint joinPoint) {
        System.out.println("后置通知,方法名称:" + joinPoint.getSignature().getName());
    }
    
    //如果不在同一个切面(同一个文件下):
    @After(value = "com.lily.LogAspectpointCut()")
    public void afterMethod(JoinPoint joinPoint) {
        System.out.println("后置通知,方法名称:" + joinPoint.getSignature().getName());
    }
    

切面的优先级:

  • 外面的优先级高于里面
  • @Order(较小的数):优先级高;@Order(较大的数):优先级低

5. 基于XML的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:content="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">


    <!-- 开启组件扫描 -->
    <content:component-scan base-package="com.lily"></content:component-scan>
    <!--配置AOP五种通知-->
    <aop:config>
        <!-- 配置切面类 -->
        <aop:aspect ref="logAspect">
            <!-- 配置切入点 -->
            <aop:pointcut id="pointCut" expression="execution(* com.lily.CalculatorImpl.*(..))"/>
            <!-- 配置5种通知类型 -->
            <aop:before method="beforeMethod" pointcut-ref="pointCut"></aop:before>
            <aop:after method="afterMethod" pointcut-ref="pointCut"></aop:after>
            <aop:after-returning method="afterReturing" returning="result" pointcut-ref="pointCut"></aop:after-returning>
            <aop:after-throwing method="afterThrowing" throwing="exceptionMessage" pointcut-ref="pointCut"></aop:after-throwing>
            <aop:before method="around" pointcut-ref="pointCut"></aop:before>
        </aop:aspect>
    </aop:config>
</beans>

标签:int,切面,System,面向,AOP,println,out,public,result
From: https://www.cnblogs.com/lilyflower/p/18056317

相关文章

  • Java面向对象
    面向过程&面向对象面向过程:步骤简单清晰,第一步->第二步->....面向过程适合处理一些较为简单的问题面向对象:分类的思维模式面向对象适合处理复杂的问题对于描述复杂的事物,使用面向对象的思维去宏观上把握、整体上分析,使用面向过程的思维去处理围观操作什么是面向......
  • 9.Python3 面向对象
    Python3面向对象1.类定义Python中定义一个类使用class关键字实现,其基本语法格式如下:classClassName:<statement-1>...<statement-N>类实例化后,可以使用其属性,创建一个类之后,可以通过类名访问其属性。无论是属性还是方法,对于类来说,它们都不是......
  • 面试官:说说SpringAOP实现原理?
    AOP(Aspect-OrientedProgramming,面向切面编程)是一种编程技术,它允许开发者在不改变现有代码的情况下,增加新的功能或行为,这些功能或行为被称为“切面”。AOP可以通过预编译方式和运行期动态代理的方式来实现,它的主要目的是降低业务逻辑的耦合性,提高程序的可重用性和开发效率。AOP......
  • C# 面向对象
    .NETFramework是一个平台,它类似于Jave的虚拟机,.NET程序是运行在.NETFramwork之上的。.NET框架结构核心包括:.NET基础类库(BaseClassLibrary)和通用语言开发环境(CommonLanguageRuntime).NET基础库由“命名空间(Namespace)”和“类(Class)”组成。4.C#语言的特点:语法简洁(C#源自......
  • JAVA面向对象基础:封装,实体JavaBean
     封装: 封装设计对象规范:合理隐藏,合理暴露 即类中使用的public与private关键字合理使用(只暴露对对象数据操作的方法,不暴露数据,故在对象中用private来封装数据,用public来封装方法)将成员变量保护起来,将get与set方法暴露出来供相关操作。将需要外界访问的成员方法用public,不......
  • JAVA面向对象基础:this关键字
    this;this就是一个变量,可以用在方法中,来拿到拿到当前对象   this的应用场景:this主要用来解决变量名称冲突问题的。   this真正解析: ......
  • Nestjs系列 Nestjs中的AOP架构
    什么是AOPSpringboot中就存在AOP切面编程。在Nest中也同样提供了该能力。通常,一个请求过来,可能会经过Controller(控制器)、Service(服务)、DataBase(数据库访问)的逻辑。在这个流程中,若想要添加一些通用的逻辑,比如日志记录、权限控制、异常处理等作为一个通用的逻辑。AOP的......
  • JAVA面向对象-第二弹
    Java中,所有的类,都默认直接或者间接继承object封装◆该露的露,该藏的藏◆我们程序设计要追求“高内聚,低耦合”。高内聚就是类的内部数据操作细节自己完成,不允许外部干涉;低耦合:仅暴露少量的方法给外部使用。封装(数据的隐藏)◆通常,应禁止直接访问一个对象中数据的实际表示,而应......
  • .Net Core(六) 面向切面编程
    简介面向切面编程(AOP,Aspect-OrientedProgramming)是一种编程范式,旨在增强现有的面向对象编程(OOP,Object-OrientedProgramming)范式。AOP通过在程序执行过程中动态地将横切关注点(cross-cuttingconcerns)从它们所影响的对象中分离出来,并将其模块化,以便重用和管理。在传统的面向对......
  • JAVA面向对象基础:入门,搞懂对象
     packagecom.itheima.duyixiang;importjava.util.ArrayList;importjava.util.List;publicclassTest{publicstaticvoidmain(String[]args){Students1=newStudent();s1.name="凯文";s1.yuwen=22;s1.shuxu......