1、AOP:Aspect Oriented Programming (面向切面编程)。是一种编程思想,而不是具体的技术,实现AOP的操作才是具体的技术。
2、联系:
3、如何理解面向切面编程?----> 将不同方法的同一个位置抽象成一个切面对象,对该切面对象进行编程就是 AOP
举个例子:我有多个业务,多个业务里面都有相同的代码(比如都会有输出 Logger、Logger1 的操作)
① 实现了代码的复用;
② 解耦合:之前业务代码参杂了Logger、Logger1、其余自身的代码,相当于三部分合在一起,而现在业务代码就非常简洁。非业务代码(指的是抽象出来的Logger、Logger1)集中
动态理解面向切面编程的过程?
AOP 是对面向对象编程的一个补充。在运行时,动态地将代码 切入到 类的指定方法、指定位置上的编程思想
4、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;
}
}
问题:上述代码中,日志信息和业务逻辑(其余代码) 的耦合性很高,不利于系统的维护。
解决:使用 AOP 可以进行优化,如何来实现 AOP?----> 使用动态代理的方式来实现(也就是:给业务找一个代理,需要提出来的操作 的工作交给代理来做,这样的话业务就只需要关注自身的代码即可)
4、实现 AOP 。动态代理的话,需要实现 InvocationHandler 接口,接口都是提供功能的,这个接口就提供了生成动态代理类的功能。
如何理解代理?----> 在程序中,代理是一个对象,对象的创建需要用到类,那这个类(代理类)是怎么产生的?其实是动态产生的(动态产生:也就是当程序运行的时候产生的类,而不是像之前一样,写好了类再运行)。
注意:实现 InvocationHandler 接口的实现类,不是代理类,只是为了创建代理类而需要的类,既然要让它有创建代理类的功能,那这个功能怎么给它?----> 实现 InvocationHandler 接口
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);
}
}
Proxy.newProxyInstance(obj.getClass().getClassLoader(),obj.getClass().getInterfaces(),this);
-------------------------------------------------------------------------
参数说明:
参数1:参数1就是为了创建代理类的,那是怎么创建的?由于是在运行时创建的,所以需要向虚拟机中添加这个类,
怎么添加?用类加载器:
obj.getClass()获取到委托对象的运行时类,
obj.getClass().getClassLoader()通过运行时类获得类加载器
参数2:这个代理类有什么特点呢?----> 委托类所有功能,代理类必须要有,所以就是需要第二个参数:
obj.getClass().getInterfaces() 获取委托对象的运行时类获取它所有的功能(也就是所有的接口)
参数3:this:表示通过当前类(MyInvocationHander)来创建代理类、进一步创建代理对象
1、以上是通过动态代理实现 AOP 的过程,比较复杂,不好理解。Spring 框架对 AOP 进行了封装,使用 Spring 框架可以用面向对象的思想来实现 AOP。
2、Spring 框架中不需要实现 InvocationHandler 接口,只需要创建一个切面对象,将所有的非业务代码在切面对象中完成即可,Spring 框架底层会自动根据 切面类(生成切面对象的类)以及目标类(就是委托类)生成一个代理对象(也就是说底层还是上面的形式)
3、找到非业务代码的位置(因为,要想做成切面对象,必须先找到每个业务中非业务代码的位置),然后开始实现。
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);
}
}
至此,本教程结束。更多精彩内容尽在我的主页中,谢谢铁子们的赞和收藏 !!!
实际开发中如何使用 AOP,spring boot 整合 aop,链接引入:Spring Boot 整合 AOP