Spring AOP保姆级超详细解析(上)

语言: CN / TW / HK

theme: channing-cyan highlight: a11y-dark


持续创作,加速成长!这是我参与「掘金日新计划 · 10 月更文挑战」的第9天,点击查看活动详情

前言

经过我们之前的学习我们对IoC有了一定的了解,并已经学会了IoC的基本使用。接下来,我们将要学习Spring另外一个核心机制————AOP。

AOP

为什么要学习AOP?

AOP全称是Aspect Oriented Programming,意思是面向切面编程

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

AOP的优点: - 使系统更加容易扩展。 - 更好的提高代码的复用性。 - 降低代码模块之间的耦合度。 - 使业务代码更加简洁纯粹,不参杂其他的非业务代码的影响。 - 使非业务代码更加集中,与业务代码区分开来,不分散,便于统一管理。

案例分析

我们先写一个不使用AOP的案例

1.首先创建一个Maven工程。

(不懂Maven的小伙伴移步到我这个专栏的第一篇文章

2.在pom.xml文件中引入Spring的AOP依赖。 ```js org.springframework spring-aop 5.2.10.RELEASE

<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-aspects</artifactId>
    <version>5.0.11.RELEASE</version>
</dependency>

3.定义一个实现两个数加减的接口。js package com;

public interface Cal { public int add(int num1,int num2); public int sub(int num1,int num2); } ``` 4.实现方法。

```js package com.impl;

import com.Cal;

public class CalImpl implements Cal { public int add(int num1, int num2) { System.out.println("执行加法,参数是("+num1+","+num2+")"); int result = num1 + num2; System.out.println("计算结果是:"+result); return result; }

public int sub(int num1, int num2) {
    System.out.println("执行减法,参数是("+num1+","+num2+")");
    int result = num1 - num2;
    System.out.println("计算结果是:"+result);
    return result;
}

} 5.调用方法。js Cal cal = new CalImpl(); cal.add(1,2); cal.sub(20,10); ``` 打印结果:

js 执行加法,参数是(1,2) 计算结果是:3 执行减法,参数是(20,10) 计算结果是:10

这样是我们不使用AOP的一个案例,这样我们的业务代码和打印日志的代码就会混杂在一起,并且两个方法有重复的部分,这时候我们就可以使用AOP将其打印日志的代码提取出来统一管理。这就是面向切面编程的一个思想。

如何实现AOP?

使用动态代理来实现AOP,日志信息的打印可以交给代理去做,由代理统一管理,提高代码的可扩展性维护性

代理就类似于我们现实生活中的中介。

什么是动态代理呢?动态代理就是在我们程序运行过程中,动态创建的一个代理类。

我们接下来用动态代理类来实现AOP。

1.创建MyInvocationHandler类。 ```js package com;

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

public class MyInvocationHandler implements InvocationHandler { //接受委托对象 private Object object = null;

//返回代理对象
public Object bind(Object object){
    this.object = object;
    return Proxy.newProxyInstance(object.getClass().getClassLoader(),object.getClass().getInterfaces(),this);
}

public Object invoke(Object prox, Method method,Object[] args)throws Throwable{
    System.out.println(method.getName()+"方法的参数是"+ Arrays.toString(args));
    Object result = method.invoke(this.object,args);
    System.out.println(method.getName()+"方法的结果是"+result);
    return result;
}

} ``MyInvocationHandler并不是我们上面所说的动态代理类,这个类是用来创建我们动态代理类的一个类。这里可能会有点绕。我们这里通过实现InvocationHandler`这个接口来完成动态代理的功能。

Spring使用Proxy.newProxyInstance类加载器来实现动态代理类的创建。使用object.getClass().getInterfaces()获取委托对象的全部方法。

invoke方法介绍

在代理实例上处理方法调用并返回结果, 当在与之关联的代理实例上调用方法时,将在调用处理程序中调用此方法。我们的日志打印信息就写在invoke方法之中。

参数 - proxy:在其上调用方法的代理实例,也就是动态代理类。 - method: 对应于在代理实例上调用的接口方法的 Method 实例。 Method 对象的声明类将是在其中声明方法的接口,该接口可以是代理类赖以继承方法的代理接口的超接口。 - args:包含传入代理实例上方法调用的参数值的对象数组,如果接口方法不使用参数,则为 null。基本类型的参数被包装在适当基本包装器类(如 java.lang.Integer 或 java.lang.Boolean)的实例中。

2.通过操作代理对象来调用。 js Cal cal = new CalImpl(); MyInvocationHandler myInvocationHandler = new MyInvocationHandler(); Cal cal1 = (Cal) myInvocationHandler.bind(cal); cal1.add(1,2); cal1.sub(20,10);

通过myInvocationHandler中的bind方法,我们传一个委托对象进去,会返回一个代理对象。从而实现调用代理对象的方法。

3.将业务代码中之前的日志打印信息删除。

打印结果:

js add方法的参数是[2, 5] add方法的结果是7 sub方法的参数是[7, 4] sub方法的结果是3 这样我们就使用AOP实现了相同功能,并且将业务代码和非业务代码分开,模块之间的耦合性降低了,代码也更加的简洁。

总结

我们面向切面编程是通过反射机制来实现的,我们学习Spring的IoC和AOP更多的是学习一种编程思想,理解了这种思想,技术自然就水到渠成。这就是通过动态代理的方式来实现AOP的过程了。下一篇文章讲解下通过面向对象的方式来实现AOP。