切面是对方法中具体相同功能的代码段的封装。乍一看这句话可能很抽象不好理解,但是将他带入到我们面向对象的思想中去看,我们就会很好理解这句话的含义。什么是对于方法的抽取?在我们的面向对象的思想中,我们把程序中需要用到的功能抽取成对象,然后将整个的程序变成了由对象组成的程序,面向对象编程有很多的好处,比如一个对象负责一个功能,这个对象可以在这里用,也可以在其他的地方使用,提高了代码复用性,并且降低了程序的耦合性。
程序被分解成了类,而类中的方法负责实现各自的功能,这是面向对象的基本理念。方法还能细分吗?在我们之前编写代码的时候,可能会遇到这样的情况,我们在方法中会编写很多重复的代码,比如我们会在增加用户的方法中写入一段用来记录日志的代码,然后我们在删除用户的方法中又写了相同的记录日志的代码。这样的在方法中出现的重复的代码,我们也可以把他抽取出来,这个从方法中抽取的代码段,就叫做切面。
但是在更多的时候,我们是反向使用的,也就是说,我们的切面中包含一些方法,需要添加到已有的方法中。这个将切面中的方法添加到原有方法的过程,就叫做增强。并且Spring框架还有一个非常棒的地方在于,它可以在不更改源代码的情况下对方法进行增强,就避免了我们因为修改源代码而导致我们的程序莫名其妙就跑不起来的情况发生。
我们可以用图式的方法去演示这种切面的提取:
这是之前我们的面向逻辑编程,所有的功能都写在一个方法里面,也就是main方法里面
这是之后的面向对象编程,我们将功能封装成单独的对象,并且里面调用多个方法去完成这个功能。这个类可以重复使用,也就提高了代码复用率,降低了程序的耦合度。
但是很明显,我们可以看到,在每个方法中,我们都有重复的代码块,也就是【判断权限】和【写入日志】这个地方,而切面就是对这些地方的提取和添加,主要还是添加。
我们将相同的代码都提取出来,那么这份代码就可以使用很多次,这就像是在方法中调用方法一样,只不过这个调用的过程是由Spring帮我们动态的去完成的。
比如此时我们还有一个方法没有做【判断权限】和【写入日志】的过程,但是我们又不想去修改他的源代码,其实更多的时候是无法修改或者修改源代码的代价非常大的时候,我们会选择将切面中的方法增强进我们已有的旧方法中,让旧方法具有新的功能,这是切面的主要作用:
这种方式是切面使用最多的方式,就是将一些增强代码添加到已有的旧方法中,在之后的学习中,我们也是重点学习如何将切面中的方法写入到已有方法,即在不修改源代码的情况下对旧方法进行增强处理。
在正式的开始使用之前,我们需要对面向切面的编程中的一些专业术语做一些基本的介绍:
切面:通常指的就是我们增强方法存在的类,比如我们想在添加用户的时候写入日志,那么写入日志的方法 就是增强方法,但是因为方法不能单独存在,方法需要存在与类中,这个增强方法所在的类就是切面 连接点:连接点就是程序执行过程中某个特殊的点,比如方法的执行或者处理异常 切入点:当连接点满足某个条件的时候,AOP就在连接点的地方插入增强方法,这时候这个连接点就不是连接点了 而被称为切入点。也就是说一个切入点肯定是连接点,但是一个连接点不一定是一个切入点。 通知/增强处理:就是我们增强的方法,比如写入日志的方法 目标对象:被增强的方法,比如添加用户的方法 织入:将切面代码插入到目标对象上,从而生成代理对象的过程 代理:将通知引用到目标对象之后,程序动态创建的通知对象,就称为代理 引介:引介是一张特殊的通知,它可以为目标对象添加一些属性和方法
在之后的学习中,我们可能会非常频繁的提起这些名词,如果某个时候忘记了某个名词的含义可以随时查看 主要需要我们记住的就是切面,连接点,切入点和通知这四个名词,也是我们配置面向切面编程的比较重要的 四个关键点。
Spring AOP在实现的时候,需要先创建一个动态代理对象。所谓的动态代理对象,就是将原有方法所在的类进行加强后,这个加强的过程成为动态代理,经过动态代理之后的类调用的方法就是增强后的方法,根据动态代理的方式不同,可以分为JDK动态代理和CGLib动态代理
JDK动态代理的方式是通过实现InvocationHandler接口,然后实现接口内的invoke()方法的方式 完成动态代理的过程。 基本步骤是我们需要一个被增强的类,这个类里面有我们之前的普通方法。 然后我们新建一个普通的类,这个类里面有我们的增强方法 然后关键的地方来了,我们需要创建一个代理类,这个类也是一个普通类 首先,让代理类去实现我们的InvocationHandler接口,在这个接口中 我们首先需要去实现一个方法,这个方法是invoke()方法,动态代理类的所有方法 都会交给invoke()方法去调用。 动态代理类就是我们将之前我们被增强方法所在的类,经过JDK动态代理之后的类,我们 之后调用的增强方法其实都是经过动态代理对象来调用的。 现在我们先来看动态代理对象如何获取: 我们在动态代理类中创建一个方法,这个方法的方法名可以随意,返回值是Object类型 我的动态代理类是SpringAOP,我的动态代理方法是createProxy,这个方法的参数是我们的动态代理之前 的类,也就是包含了普通方法的类。然后我们需要获取两个东西,一个是类加载器 这个类加载器可以是随便的一个,一般我们会获取本类的类加载器。以及被代理对象 的所有接口,之后我们就开始调用proxy的newProxyInstance()将我们的被代理对象 转换成代理对象,这个方法需要三个参数,分别是我们之前的类加载器,还有被代理对象 的所有接口,以及this也就是本类,之后直接将方法的返回值返回就可以了。
然后我们需要编辑我们的invoke()方法,在这个方法中,我们需要做两件事 第一件事就是创建我们的增强类对象,增强类就是增强方法所在的类, 然后使用methods参数调用invoke()方法,接收方法的返回值并在最后返回这个返回值
这就是我们的动态代理类的所有内容,接下来就是来到测试类中进行测试: 首先,我们需要创建被增强类的接口对象,然后调用动态代理类的创建动态代理类的方法,将我们之前的被代理类接口对象作为参数传递进去 这个方法会返回一个参数,这个参数我们强制转换成被代理类的类型,但是经过这个方法返回之后的结果就已经是包含增强方法的动态代理类对象了。 然后通过这个类调用之前的增加用户和删除用户的方法就是经过动态代理对象增强之后的方法了。
我们首先要创建我们的原有类和原有方法:
接口:
package Semester_4.AOP.JDKDynamicProxy.DAO;
public interface UserDAO {
void addUser();
void deleteUSer();
}
实现类:
package Semester_4.AOP.JDKDynamicProxy.Imp;
import Semester_4.AOP.JDKDynamicProxy.DAO.UserDAO;
public class userDAOImp implements UserDAO {
public void addUser(){
System.out.println("添加了用户");
}
public void deleteUSer(){
System.out.println("删除了用户");
}
}
这个原有的类和方法我们在创建完成之后就不会再去修改它了,因为我们切面最大的作用就是在不修改源代码的情况下对原有的方法进行增强,现在我们就对上面的方法进行增强。
既然要增强,那么首先我们要做的就是要创建增强方法所在的类,也就是我们的切面:
package Semester_4.AOP.JDKDynamicProxy.intensifier;
public class MyAspect {
public void chinkPermissions(){
System.out.println("模拟检查权限的过程……");
}
public void log(){
System.out.println("模拟记录权限的过程……");
}
}
切面中的方法就是我们要加入到原有方法中的方法,这个方法叫做通知,将通知添加到原有方法的过程叫做织入。原有的方法叫做切入点。现在我们切入点也有了,切面也有了,就差配置切入点和切面之间的关系了,这就需要用到我们的创建动态代理类的类了、
创建动态代理的类:
package Semester_4.AOP.JDKDynamicProxy.intensifier;
import Semester_4.AOP.JDKDynamicProxy.DAO.UserDAO;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
public class SpringAOP implements InvocationHandler {
// 引入我们的原始对象
UserDAO ud ;
// 这个方法是创建原始对象的动态代理对象
public Object createProxy(UserDAO ud){
// 创建动态代理对象需要三个参数,一个是被代理对象
this.ud = ud;
// 一个是类加载器,随便一个类加载器就可以,这里一般使用本类的类加载器
ClassLoader loader = SpringAOP.class.getClassLoader();
// 然后是被代理对象实现的接口
Class>[] classes = ud.getClass().getInterfaces();
// 最后通过Proxy类的newProxyInstance方法,依次将类加载器,接口集合,本类放进去然后返回的就是我们动态代理之后的对象
return Proxy.newProxyInstance(loader,classes,this);
}
// 这个重写的方法的作用是将我们切面中的增强方法添加到已有方法中,也就是说在这个方法中,我们就实现了对原有功能进行增强的过程
/**
*
* @param proxy the proxy instance that the method was invoked on
*
* @param method the {@code Method} instance corresponding to
* the interface method invoked on the proxy instance. The declaring
* class of the {@code Method} object will be the interface that
* the method was declared in, which may be a superinterface of the
* proxy interface that the proxy class inherits the method through.
*
* @param args an array of objects containing the values of the
* arguments passed in the method invocation on the proxy instance,
* or {@code null} if interface method takes no arguments.
* Arguments of primitive types are wrapped in instances of the
* appropriate primitive wrapper class, such as
* {@code java.lang.Integer} or {@code java.lang.Boolean}.
* 这一段洋文说的就是在这个方法的参数的作用,我们简单的理解一下就是第一个参数proxy指的就是动态代理之后的类
* method用来调用旧的方法,保证原有方法的功能可以正常实现
* agrs就是原有方法中需要的参数
* @return
* @throws Throwable
*/
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// 首先我们需要创建切面类
MyAspect ms = new MyAspect();
// 然后调用切面中的方法.对原有方法做一个前置增强
ms.chinkPermissions();
// 之后我们调用原有的方法,保证原有功能的正常实现
// 这里调用原有方法使用的是method的invoke方法,参数就是我们的原有类和方法参数
Object invoke = method.invoke(ud, args);
// 最后调用切面中的方法,对原有方法做一个后置增强
ms.log();
// 最后我们会返回一个类,这个类就是我们经过动态代理之后的对象
// 这个对象最重要的作用就是可以调用我们增强之后的方法
return invoke;
}
}
在创建完我们的动态代理对象创建类之后,我们就可以来到测试类中对我们做过的增强进行测试:
package Semester_4.AOP.JDKDynamicProxy;
import Semester_4.AOP.JDKDynamicProxy.DAO.UserDAO;
import Semester_4.AOP.JDKDynamicProxy.Imp.userDAOImp;
import Semester_4.AOP.JDKDynamicProxy.intensifier.SpringAOP;
public class text {
public static void main(String[] args) {
// 首先我们创建原有的类的对象
UserDAO ud = new userDAOImp();
// 然后我们创建SpringAOP类的对象,这个类里面包含我们创建动态代理对象的方法,以及我们的增强原有方法的方法
SpringAOP sa = new SpringAOP();
// 然后我们将原有的对象传入到创建代理对象的方法,这样返回的就是经过我们动态代理之后的对象
UserDAO userDAO = (UserDAO) sa.createProxy(ud);
// 之后我们用动态代理之后的方法去正常的调用方法即可
userDAO.addUser();
userDAO.deleteUSer();
}
}
之后我们运行测试类,来看运行结果
我们将运行结果和之前的原有类中的方法去对比一下:
很明显的可以看到,我们的方法中并没有输出【检查权限】和【记录日志】的语句,这两个输出就来自于我们切面的增强,这就是我们一直说的,切面中的方法是对原有方法的增强处理。
在这一章节中,我们需要注意的就是创建动态代理对象的过程,也就是实现了InvocationHandler接口的类,主要的配置就在这个类中。
还有一个地方不知道大家有没有注意,我们在测试类中创建了UserDAO对象,这个UserDAO是接口的对象,也就是说,我们通过多态的方式创建的是接口的对象,并不是接口实现类的对象,真正被增强的方法是接口中的方法,这个是一定要注意的。
既然这种方式只能增强通过实现接口而来的方法,那么如果的我们的方法并不是通过实现接口而来的,而是通过继承其他类而来的,我们就不能获取到接口,这种时候我们就需要用到另一种CGLib方式来获取动态代理对象。