一篇文章搞懂spring aop,什么是AOP,什么是面向切面编程,动态代理实现AOP,Spring boot 实现Aop,spring 实现aop,附代码图文演示(保姆级教程)

文章目录

    • 一、什么是AOP
    • 二、如何使用AOP
    • 三、动态代理方式实现AOP
      • 3.1 详细过程分析:注意看注释(很重要)
      • 3.2 动态代理对象生成说明
    • 四、Spring框架实现AOP
      • 4.1 代码演示分析:注释很重要
      • 4.2 实现过程中常见的注解
      • 4.3 AOP中经常遇见的几个概念
      • 4.4 Spring Boot 整合 AOP

一、什么是AOP

1、AOP:Aspect Oriented Programming (面向切面编程)。是一种编程思想,而不是具体的技术,实现AOP的操作才是具体的技术。

2、联系:

  • 所熟悉的面向对象编程(OOP),是将程序中所有参与模块都抽象成对象,然后通过对象之间的相互调用关系来完成需求。
  • AOP 是对 OOP 的一个补充,是在另外一个维度上抽象出对象,具体是指:程序运行时动态地将非业务代码切入到业务代码中,从而实现代码的解耦合,将非业务代码抽象成一个对象,对该对象进行编程这就是面向切面编程思想。

3、如何理解面向切面编程?----> 将不同方法同一个位置抽象成一个切面对象,对该切面对象进行编程就是 AOP

举个例子:我有多个业务,多个业务里面都有相同的代码(比如都会有输出 Logger、Logger1 的操作)

一篇文章搞懂spring aop,什么是AOP,什么是面向切面编程,动态代理实现AOP,Spring boot 实现Aop,spring 实现aop,附代码图文演示(保姆级教程)_第1张图片

① 实现了代码的复用;

② 解耦合:之前业务代码参杂了Logger、Logger1、其余自身的代码,相当于三部分合在一起,而现在业务代码就非常简洁。非业务代码(指的是抽象出来的Logger、Logger1)集中

动态理解面向切面编程的过程?

AOP 是对面向对象编程的一个补充。在运行时,动态地将代码 切入到 类的指定方法、指定位置上的编程思想

一篇文章搞懂spring aop,什么是AOP,什么是面向切面编程,动态代理实现AOP,Spring boot 实现Aop,spring 实现aop,附代码图文演示(保姆级教程)_第2张图片

4、AOP的优点

  • 降低模块之间的耦合度。
  • 使系统更容易扩展、更好的代码复用。
  • 非业务代码更加集中,不分散,便于统一管理 (AOP一般处理的都是一些非业务代码)
  • 业务代码更加简洁存粹,不参杂其他代码的影响。

二、如何使用AOP

这里通过代码实现上述 Logger的过程

1、同理,新建 maven 工程,在依赖文件 pom.xml 中添加依赖。

<dependency>
	<groupId>org.springframeworkgroupId>
    <artifactId>spring-aopartifactId>
    <version>5.3.10version>
dependency>
<dependency>
	<groupId>org.springframeworkgroupId>
    <artifactId>spring-aspectsartifactId>
    <version>5.3.9version>
dependency>

2、环境搭起来之后,就开始实现 AOP。比如这里创建一个计算器接口,定义 4 个方法当作上述的业务。

public interface Cal {
    //加、减、乘、除
    public int add(int num1,int num2);
    public int sub(int num1,int num2);
    public int mul(int num1,int num2);
    public int div(int num1,int num2);
}

3、创建上述接口的实现类,重写的这 4 个方法就相当于是上述提到的几个业务。现在需要在相应的位置加 Logger(比如:我需要在每个方法的起始位置输出一些信息、在方法操作完后再输出一些信息)

public class CalImp implements Cal {
    //这几个方法就相当于是业务
    public int add(int num1, int num2) {
        System.out.println("add方法的参数是[" + num1 + "," + num2 + "]"); //起始位置输出一些信息
        int result = num1 + num2;
        System.out.println("add方法的结果是" + result); //操作完后输出一些信息
        return result;
    }

    public int sub(int num1, int num2) {
        System.out.println("sub方法的参数是[" + num1 + "," + num2 + "]");
        int result = num1 - num2;
        System.out.println("sub方法的结果是" + result);
        return result;
    }

    public int mul(int num1, int num2) {
        System.out.println("mul方法的参数是[" + num1 + "," + num2 + "]");
        int result = num1 * num2;
        System.out.println("mul方法的结果是" + result);
        return result;
    }

    public int div(int num1, int num2) {
        System.out.println("div方法的参数是[" + num1 + "," + num2 + "]");
        int result = num1 / num2;
        System.out.println("div方法的结果是" + result);
        return result;
    }
}

一篇文章搞懂spring aop,什么是AOP,什么是面向切面编程,动态代理实现AOP,Spring boot 实现Aop,spring 实现aop,附代码图文演示(保姆级教程)_第3张图片
一篇文章搞懂spring aop,什么是AOP,什么是面向切面编程,动态代理实现AOP,Spring boot 实现Aop,spring 实现aop,附代码图文演示(保姆级教程)_第4张图片

问题:上述代码中,日志信息和业务逻辑(其余代码) 的耦合性很高,不利于系统的维护。

解决:使用 AOP 可以进行优化,如何来实现 AOP?----> 使用动态代理的方式来实现(也就是:给业务找一个代理,需要提出来的操作 的工作交给代理来做,这样的话业务就只需要关注自身的代码即可)

三、动态代理方式实现AOP

4、实现 AOP 。动态代理的话,需要实现 InvocationHandler 接口,接口都是提供功能的,这个接口就提供了生成动态代理类的功能。

如何理解代理?----> 在程序中,代理是一个对象,对象的创建需要用到类,那这个类(代理类)是怎么产生的?其实是动态产生的(动态产生:也就是当程序运行的时候产生的类,而不是像之前一样,写好了类再运行)。

注意:实现 InvocationHandler 接口的实现类,不是代理类,只是为了创建代理类而需要的类,既然要让它有创建代理类的功能,那这个功能怎么给它?----> 实现 InvocationHandler 接口

一篇文章搞懂spring aop,什么是AOP,什么是面向切面编程,动态代理实现AOP,Spring boot 实现Aop,spring 实现aop,附代码图文演示(保姆级教程)_第5张图片


一篇文章搞懂spring aop,什么是AOP,什么是面向切面编程,动态代理实现AOP,Spring boot 实现Aop,spring 实现aop,附代码图文演示(保姆级教程)_第6张图片

3.1 详细过程分析:注意看注释(很重要)

public interface Cal {
    //加、减、乘、除
    public int add(int num1,int num2);
    public int sub(int num1,int num2);
    public int mul(int num1,int num2);
    public int div(int num1,int num2);
}
//这里就不需要日志信息(像之前的输出信息),代码比较纯净
public class CalImp implements Cal {
    public int add(int num1, int num2) {
        int result = num1 + num2;
        return result;
    }

    public int sub(int num1, int num2) {
        int result = num1 - num2;
        return result;
    }

    public int mul(int num1, int num2) {
        int result = num1 * num2;
        return result;
    }

    public int div(int num1, int num2) {
        int result = num1 / num2;
        return result;
    }
}
import java.lang.reflect.Proxy;  //不要导错了

public class MyInvocationHander implements InvocationHandler {
    /*
    定义变量 来接收委托的对象(比如:我找中介租房,我就是委托的对象,中介就是代理对象)
    这里就是CalImp为委托对象,由于有不同类型的委托对象,所以搞成Object。
    */
    private Object object = null;  

    /*
    1、在这个类中写一个生成动态代理类的方法,因为这个类是生成动态代理类的类,
       由于动态代理类是运行时产生的,所以看不到动态代理类,只能生成动态代理对象
    2、说明:
        由于代理对象各种各样,就好比有各种各样的中介,所以用Object
        参数:就是委托对象,好比:你想得到中介,就必须你要去找
     */
    public  Object bind(Object obj){  //返回代理对象的方法bind
        this.object = obj; //赋值

        /*
        1、创建动态代理对象需要用到动态代理类,为什么没看到:实际上是在newProxyInstance参数里

        参数说明:
        参数1:参数1就是为了创建代理类的,那是怎么创建的?由于是在运行时创建的,所以需要向虚拟机中添加这个类,
        怎么添加?用类加载器:
        obj.getClass()获取到委托对象的运行时类,
        obj.getClass().getClassLoader()通过运行时类获得类加载器

        参数2:这个代理类有什么特点呢?---->委托类所有功能(需求),代理类必须要有,所以需要第二个参数:
        obj.getClass().getInterfaces() 获取委托对象的运行时类获取它所有的功能(也就是所有的接口)

        参数3:this:表示通过当前类(MyInvocationHander)来创建代理类、进一步创建代理对象
         */
        return Proxy.newProxyInstance(obj.getClass().getClassLoader(),obj.getClass().getInterfaces(),this);
    }


    /*
    1、invoke方法是什么意思,就是用来写那些日志信息的(也就是需要代理的东西)
    实际上就是在这个方法中:剥离那些耦合模块(比如:自身代码和日志信息)

    参数说明:
    1、Object proxy:代理对象
    2、Method method:日志信息操作、自身业务代码 所在的方法(需要解耦合的方法)(也就是你找中介看房,你要告诉中介你需要的房子信息)
    3、Object[] args:方法中所对应的参数
     */
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {

        //getName()取方法名字
        System.out.println(method.getName()+"方法的参数是:"+ Arrays.toString(args)); 		
        
        //执行其余的业务代码(自身的业务代码)
        Object result = method.invoke(this.object,args);
        /*
        1、也就是反射中的invoke,参数:方法的调用者(为什么是委托对象,因为方法在委托对象里面)
        2、为什么可以用method调,反射中不是还要获取成员方法(getMethod)嘛?说明这个参数method,就是反射中getMethod方法的返回值
        */

        System.out.println(method.getName()+"的结果是"+result);
        return result;
    }
}
public class Test_Proxy {
    public static void main(String[] args) {
        Cal cal = new CalImp(); //创建委托对象
        
        //创建生成动态代理类的类的实例,因为需要用的里面的bind方法,方法怎么调,只能通过对象
        MyInvocationHander myInvocationHander = new MyInvocationHander();
        //把委托对象传进去,bind方法返一个动态代理对象给我
        Cal proxy = (Cal) myInvocationHander.bind(cal);
        
        //为什么代理对象有这些方法,因为在newProxyInstance时就给代理类了
        //怎么执行下述的方法:调一次方法,就走一次invoke
        proxy.add(1,1); 
        proxy.sub(2,1);
        proxy.mul(2,3);
        proxy.div(6,2);

    }
}

3.2 动态代理对象生成说明

Proxy.newProxyInstance(obj.getClass().getClassLoader(),obj.getClass().getInterfaces(),this);
-------------------------------------------------------------------------
参数说明:
参数1:参数1就是为了创建代理类的,那是怎么创建的?由于是在运行时创建的,所以需要向虚拟机中添加这个类,
	怎么添加?用类加载器:
    obj.getClass()获取到委托对象的运行时类,
    obj.getClass().getClassLoader()通过运行时类获得类加载器
    
参数2:这个代理类有什么特点呢?----> 委托类所有功能,代理类必须要有,所以就是需要第二个参数:
    obj.getClass().getInterfaces() 获取委托对象的运行时类获取它所有的功能(也就是所有的接口)
    
参数3this:表示通过当前类(MyInvocationHander)来创建代理类、进一步创建代理对象

四、Spring框架实现AOP

1、以上是通过动态代理实现 AOP 的过程,比较复杂,不好理解。Spring 框架对 AOP 进行了封装,使用 Spring 框架可以用面向对象的思想来实现 AOP。

2、Spring 框架中不需要实现 InvocationHandler 接口,只需要创建一个切面对象,将所有的非业务代码在切面对象中完成即可,Spring 框架底层会自动根据 切面类(生成切面对象的类)以及目标类(就是委托类)生成一个代理对象(也就是说底层还是上面的形式)

3、找到非业务代码的位置(因为,要想做成切面对象,必须先找到每个业务中非业务代码的位置),然后开始实现。

一篇文章搞懂spring aop,什么是AOP,什么是面向切面编程,动态代理实现AOP,Spring boot 实现Aop,spring 实现aop,附代码图文演示(保姆级教程)_第7张图片

4.1 代码演示分析:注释很重要

public interface Cal {
    //加、减、乘、除
    public int add(int num1,int num2);
    public int sub(int num1,int num2);
    public int mul(int num1,int num2);
    public int div(int num1,int num2);
}
@Component
public class CalImp implements Cal {
    //这几个方法就相当于是业务
	public int add(int num1, int num2) {
        int result = num1 + num2;
        return result;
    }

    public int sub(int num1, int num2) {
        int result = num1 - num2;
        return result;
    }

    public int mul(int num1, int num2) {
        int result = num1 * num2;
        return result;
    }

    public int div(int num1, int num2) {
        int result = num1 / num2;
        return result;
    }
}

<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 https://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/aop https://www.springframework.org/schema/aop/spring-aop.xsd">

    <context:component-scan base-package="aop_relevant">context:component-scan>
    <aop:aspectj-autoproxy>aop:aspectj-autoproxy>
beans>
@Aspect
@Component
/*
1、把切面抽象成对象,既然有对象,是不是先有类,所以创建切面类
2、这个切面类中,完成的就是非业务代码
    非业务代码有哪些特点呢?----> 需要找到位置
*/
public class SpringAopAspect {
    /*
    1、位置:发现有一处的非业务代码在最开始就执行(也就是在自身的业务执行之前就开始了)
    2、既然这个类就是执行非业务代码的,我又找到这个位置了,那怎么执行?
        2.1 既然要执行操作,肯定要在方法里面执行,所以需要定义方法(一般称为切面方法)
        2.2 要把方法和位置关联,怎么关联?----> 加入注解
     */

    // @Before("execution(public int import aop_relevant.imp.CalImp.add()")
    /* 注解解析:
    1、Before注解:相当于在业务执行之前就开始执行
    2、参数:需要切割的方法
    execution(public int import aop_relevant.imp.CalImp.add())
        2.1 aop_relevant.imp.CalImp. ----> 必须要全包名
        2.2 aop_relevant.imp.CalImp.add() ----> 这样就只关联了一个方法,怎么关联多的方法,用.*
        2.3 参数的通配符也用 * ?----> 错 用..
     */
    @Before("execution(public int aop_relevant.imp.CalImp.*(..))")
    public void before(JoinPoint joinPoint) {
        /*
        1、方法里面是要执行的具体操作,此处也就是:
            System.out.println("add方法的参数是[" + num1 + "," + num2 + "]");
        2、怎么拿到参数?(比如方法名、参数值)
            之前动态代理的方式,是有invoke这个方法,现在怎么办?----> 传参
        3、怎么传参?
            直接在方法里加入 连接点(JoinPoint joinPoint) 即可
            什么叫做连接点?----> 相当于就是需要切割的方法和这个方法的一个连接
         */
        String methodName = joinPoint.getSignature().getName();  //获取方法名
      //这个joinPoint里面不仅仅有方法,还有一些其他东西,joinPoint.getSignature()表示获取里面的方法

        String methodArgs = Arrays.toString(joinPoint.getArgs()); //获取参数
        System.out.println(methodName + "方法的参数是:" + methodArgs);

        /*
        这样写完之后还不行,还需要在类的前面加上注解,为什么?
        1、因为Spring框架会自动根据切面类以及目标类(就是委托类)生成一个代理对象
        2、其实Spring在真正执行时,是需要根据切面类生成切面类对象的
            2.1、所以需要将这个切面类交给IoC容器处理,让IoC容器给我切面类对象
                怎么交给IoC容器?----> 加入注解:@Component (注意:这个注解需要先在pom.xml依赖文件中添加IoC依赖)
        3、此时定义的切面类SpringAopAspect,只是一个普通的类而已。没有什么特殊的,而不像动态代理那样,
        (采用动态代理方式时还实现了接口)所以要想让它成为切面类,就必须必备一些功能,怎么具备?----> 实现接口,这里就不采用实现接口的形式,而是添加注解:@Aspect
         */

        /*
        1、此时:第一个日志信息的切面方法写完了,现在回到业务的那个类(也就是CalImp)
        2、因为Spring框架会自动根据切面类以及目标类(就是委托类)生成一个代理对象,现在切面类已经处理了,现在就该委托类了,所以委托类也要交给IoC容器,即:需要在委托类中加上 @Component 注解
         */

        /*
        1、接着需要在spring.xml配置文件中配置AOP,为什么要配?怎么配?
        为什么?
            因为,你只加了 @Component注解的话,spring框架又不晓得你加没加、加了多少,
            所以需要在配置文件中扫描 @Component注解
         怎么扫描?
            
            base-package="" 其实就是设置扫描的范围,只要在这个范围下面的类,而且又添加了@Component,就扫描
         2、使 @Aspect注解生效
         怎么配?
         
         */

        /*
        1、上面只完成了非业务代码一半,还有
        2、位置:还有一个非业务代码在执行完之后
         */
    }

    //业务自身代码执行之后,所以是After
    @After("execution(public int aop_relevant.imp.CalImp.*(..))")
    public void after(JoinPoint joinPoint) {
        String methodName = joinPoint.getSignature().getName();
        System.out.println(methodName + "方法执行完毕。");
        /*
        1、这里就运行的话,会输出:
            add方法的参数是:[1,1]    ----> 这是@Before输出的
            add方法执行完毕    ---->  这是@After输出的
           所以可以看到执行顺序
         2、而这边要求的不是执行完毕,而是要拿到计算结果,那这个结果怎么办?
         结果在哪计算出来的?----> 业务中的自身代码执行完毕,并return之后出现的(也就是在CalImp中return的)
         所以需要再写一个注解来拿到结果
         */
    }

    @AfterReturning(value = "execution(public int aop_relevant.imp.CalImp.*(..))", returning = "res")
    /*注解解析:
    1、AfterReturning注解:指的是业务自身代码执行之后、并return的一个时机,也就是那边业务一return,我就可以拿到结果
        那结果在哪呢?我怎么接收它的return呢?
            还是像上面一样:execution(public int aop_relevant.imp.CalImp.*(..))吗?
            注:上面@Before、@After中的 execution.... 语句,实际上省略了注解中的属性名value(因为当里面只有value属性时,value可省略)
            完整写法为:value="execution(public int aop_relevant.imp.CalImp.*(..))"
        而这里需要两个属性,所以需要写出value,为什么需要两个?----> 因为需要接收结果,那这个属性名是什么?----> returning
     */
    public void afterReturning(JoinPoint joinPoint,Object res){
     //Object res参数:为了接收传过来的res,注意这个参数名必须和上面注解中的returning属性的属性值一样
        String methodName = joinPoint.getSignature().getName();
        System.out.println(methodName + "方法的结果为:" + res);
    }

    /*
    1、至此,Spring框架实现AOP就结束了
    2、这里扩充一个注解 @AfterThrowing
     */
    @AfterThrowing(value = "execution(public int aop_relevant.imp.CalImp.*(..))",throwing = "error")
    /*
    1、这个注解是抛出异常的意思,也就是当业务方法抛出异常时,开始执行这个
    2、利用throwing属性接收异常
     */
    public void afterThrowing(JoinPoint joinPoint,Object error){
        String methodName = joinPoint.getSignature().getName();
        System.out.println(methodName + "方法抛出异常:" + error);
    }
}
public class Test_SpringAop {
    public static void main(String[] args) {
        //加载配置文件
        ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring.xml");

        /*
        1、这个getBean的参数怎么写?----> 就是委托对象的类名(首字母小写)
            我的配置文件中没有配bean啊?为什么会有calImp?
            原因:同理,采用了注解 @Component的方式交给了IoC容器,而注解 @Component 默认的id就是类名首字母小写
            也可以自己更改:@Component("自定义id名")
         */
        Cal proxy = (Cal)applicationContext.getBean("calImp");
        proxy.add(1,1);
        proxy.sub(2,1);
        proxy.mul(2,3);
        proxy.div(6,2);
    }
}

4.2 实现过程中常见的注解

一篇文章搞懂spring aop,什么是AOP,什么是面向切面编程,动态代理实现AOP,Spring boot 实现Aop,spring 实现aop,附代码图文演示(保姆级教程)_第8张图片

4.3 AOP中经常遇见的几个概念

  • 切面对象: 切面类的实例化对象
  • 通知: 切面类中的具体代码,即非业务代码
  • 目标: 被横切的对象(目标类,也就是委托类)
  • 代理: 切面对象、通知、目标混合之后的内容,即我们用 JDK 动态代理机制创建的对象。
  • 连接点: 需要被横切的位置,即通知(非业务代码)要插入业务代码的具体位置。

至此,本教程结束。更多精彩内容尽在我的主页中,谢谢铁子们的赞和收藏 !!!

4.4 Spring Boot 整合 AOP

实际开发中如何使用 AOP,spring boot 整合 aop,链接引入:Spring Boot 整合 AOP

你可能感兴趣的:(#,Java,EE,Java,spring,spring,boot,java)