前言:AOP简介
- 在软件业,AOP为Aspect Oriented Programming的缩写,意为:面向切面编程。
- 通过预编译方式和运行期动态代理实现程序功能的统一维护的一种技术。
- AOP是OOP的延续,是软件开发中的一个热点,也是Spring框架中的一个重要内容,是函数式编程的一种衍生范型。
- 利用AOP可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率。
开发中存在的一个问题
在现实开发中,我们常常会需要对一个功能进行增强的操作。如上图一般,我们需要对dao中的save方法增加一个权限校验功能,最简单的办法就是直接在源码上进行修改,但是这样显然是不符合设计原则的。所以,我们会想到第二种方式,让两个类继承一个公共父类,这样的设计比第一种好多了但是还是需要修改源码。那么有没有一种不修改源代码就能够实现增加功能的方式呢?答案是有的,我们可以想到代理模式便可以实现功能的扩展。
SpringAOP就是利用了动态代理的方式实现了动态增加功能的方式,让我们可以不破坏原有的类,生成一个代理类,在原来类的基础上进行增强,可以随时添加、取消的功能。
一、JDK动态代理
要学习SpringAOP则需要了解一下动态代理,建议参考这篇文章:https://blog.csdn.net/jiankunking/article/details/52143504(jdk动态代理),或者这篇文章:https://www.cnblogs.com/zuidongfeng/p/8735241.html
这边通过上面dao中save方法需要增加权限校验的例子演示一下jdk动态代理的用法:
主要有分为以下步骤:
- 创建一个实现接口InvocationHandler的类,它必须实现invoke方法(可以使用匿名内部类形式)
- 创建被代理的类以及接口
- 通过Proxy的静态方法
newProxyInstance(ClassLoaderloader, Class[] interfaces, InvocationHandler h)创建一个代理 - 通过代理调用方法
需要注意的一点是,jdk动态代理代理的是接口,所以我们需要代理的对象必须要实现一个接口!
1.创建需要代理的类与接口:
UserDao:
public interface UserDao {
public void save();
}
UserDaoImpl:
public class UserDaoImpl implements UserDao {
@Override
public void save() {
System.out.println("user----save");
}
}
2.创建代理类
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
public class UserDaoJDKProxy {
//需要代理的对象
private UserDao userDao;
public UserDaoJDKProxy(UserDao userDao) {
this.userDao = userDao;
}
//产生代理的方法
public UserDao createProxy() {
//创建代理对象
UserDao userDaoProxy = (UserDao) Proxy.newProxyInstance(userDao.getClass().getClassLoader(),
userDao.getClass().getInterfaces(),
new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("所有方法执行前的操作---------");
if("save".equals(method.getName())){
System.out.println("save方法执行前的单独操作------");
}
Object invoke = method.invoke(userDao, args);
System.out.println("所有方法执行后的操作---------");
return invoke;
}
});
return userDaoProxy;
}
}
3.创建测试类
import org.junit.Test;
public class UserTest {
@Test
public void test() {
UserDao userDao = new UserDaoImpl();
UserDaoJDKProxy userDaoJDKProxy = new UserDaoJDKProxy(userDao);
UserDao proxy = userDaoJDKProxy.createProxy();
proxy.save();
}
}
运行结果:
所有方法执行前的操作---------
save方法执行前的单独操作------
user----save
所有方法执行后的操作---------
上面的代码中我们使用的是匿名内部类的形式实现的InvocationHandler接口,我们也可以像uml中一样进行单独实现。
二、Cglib动态代理
cglib动态代理不同于jdk自带的动态代理,cglib动态代理可以不需要接口就能够实现。所以,Spring在帮我们代理时,如果代理的类有实现接口那么Spring会选择jdk动态代理,否则会选用cglib动态代理
ciglib动态代理采用的继承一个子类的形式来实现代理的,我们需要为要代理的对象继承一个子类,然后在代理对象调用父类方法时会自动回调子类的方法。具体步骤如下:
需要代理的类:
public class GoodsDao {
public void save() {
System.out.println("goodsDao-----save");
}
}
代理类:
import org.springframework.cglib.proxy.Enhancer;
import org.springframework.cglib.proxy.MethodInterceptor;
import org.springframework.cglib.proxy.MethodProxy;
import java.lang.reflect.Method;
public class GoodsDaoCglibProxy implements MethodInterceptor {
@Override
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
System.out.println("方法调用前-----------");
Object proxy = methodProxy.invokeSuper(o, objects);
System.out.println("方法调用后-----------");
return proxy;
}
public GoodsDao createProxy(GoodsDao goodsDao) {
//1.创建cglib核心类
Enhancer enhancer = new Enhancer();
//2.设置父类,cglib采用的是继承方式实现的代理,需要为要代理的类设置一个子类
enhancer.setSuperclass(goodsDao.getClass());
//3.设置回调,当调用父类的方法时会自动调用子类的回调方法
enhancer.setCallback(this);
//4.创建代理对象
GoodsDao proxy = (GoodsDao) enhancer.create();
return proxy;
}
}
第三步我们需要设置回调方法,传入一个MethodInterceptor对象,MethodInterceptor接口中有一个intercept需要我们实现,该方法便是回调方法,当我们代理对象调用方法时都会进入这个intercept方法,在该方法内我们调用了父类(被代理类)的方法,从而实现了功能增强。
测试方法:
@Test
public void testCglib() {
GoodsDao goodsDao = new GoodsDao();
GoodsDaoCglibProxy goodsDaoCglibProxy = new GoodsDaoCglibProxy();
GoodsDao proxy = goodsDaoCglibProxy.createProxy(goodsDao);
proxy.save();
}
运行结果:
方法调用前-----------
goodsDao-----save
方法调用后-----------
三、AOP相关术语
public class UserDao {
public void delete();
public void save();
public void update();
}
假如我们有这么一个UserDao类,并对它进行了代理
Joinpoint:连接点
连接点表示可以被拦截到的方法,也就是说假如我们对这个UserDao进行了代理,那么按理来说这个类中的所有方法我们都可以进行增强,所以该类中的方法都可以叫做连接点
Poincut:切入点
切入点表示真正被增强方法,比如说上例中我们只对save()进行了增强那么save就是切入点
Advice:通知
通知表示增强的内容,我们一般把增强的内容封装成一个方法,这个方法我们就叫做通知。
Introduction:引介
引介表示的是类层面的增强,比如说给类增加一些新的属性、方法等。开发当中一般都是给方法增强
Target:被增加的对象
Weaving:织入
将通知(增强方法)应用的目标对象的一个过程。
Proxy:代理对象
返回给我们的代理对象
Aspect:切面
多个通知和多个切入点的集合