AOP(Aspect oriented programing)面向切面编程,通过预编译方式和运行期动态代理实现程序功能的统一维护的一种技术。AOP是OOP的延续,是软件开发中的一个热点,也是Spring框架中的一个重要内容,是函数式编程的一种衍生范型。利用AOP可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率。
AOP的相关术语
Joinpoint(连接点): 类中可以被增强的方法(Joinpoint还未被增强)
Pointcut(切入点):指我们要对哪些Joinpoint进行拦截的定义(Joinpoint已经被增强)
Advice(通知/增强):指拦截到Joinpoint之后所要做的事情就是通知.通知分为前置通知,后置通知,异常通知,最终通知,环绕通知(切面要完成的功能)
Aspect(切面): 是切入点和通知(引介)的结合
Introduction(引介):引介是一种特殊的通知在不修改类代码的前提下, Introduction可以在运行期为类动态地添加一些方法或Field.
Target(目标对象):代理的目标对象(要增强的类)
Weaving(织入):是把advice 应用到target的过程,分成三个阶段:
(1)编译时:当一个类文件被编译时进行织入,这需要特殊的编译器才可以做的到,例如AspectJ的织入编译器
(2)类加载时:使用特殊的ClassLoader在目标类被加载到程序之前增强类的字节代码
(3)运行时:切面在运行的某个时刻被织入,SpringAOP就是以这种方式织入切面的,原理应该是使用了JDK的动态代理技术
Proxy(代理):一个类被AOP织入增强后,就产生一个结果代理类
Spring切面定义了5种通知
1、before(前置通知):在某个方法执行之前,先执行通知中定义的方法。
应用场景: 权限控制 (权限不足,抛出异常)、 记录方法调用信息日志
2、after-returning(后置通知):在某个方法执行,等待到方法执行完毕得到返回值,才执行通知中定义的方法。
应用场景:与业务相关的,如ATM取款机取款后,自动下发短信
3、around(环绕通知):在某个方法执行前,先执行通知中定义的方法,并且,在这个方法开始执行之后,再次执行通知中定义的方法。
应用场景:日志、缓存、权限、性能监控、事务管理
4、after-throwing(异常通知):在某个方法执行时抛出异常,执行通知中定义的方法。
应用场景:处理异常(一般不可预知),记录日志
5、after(最终通知):不管目标方法是否发生异常,最终通知都会执行(类似于finally代码功能)。
应用场景:释放资源(关闭文件、 关闭数据库连接、 网络连接、 释放内存对象 )
Spring中AOP的有两种实现方式,那么是怎么具体实现的呢?
1、JDK动态代理
创建dao层接口实现类
public class StudentDaoImpl implements StudentDao {
@Override
public void read() {
System.out.println("只读书,却不写读书笔记");
}
}
定义dao层接口
public interface StudentDao {
public void read();
}
创建JdkProxy类,用于对StudentDaoImpl中的read方法进行增强
public class JDKProxy {
// 定义一个需要被增强方法所属类的对象
private StudentDao studentDao;
// 定义有参构造,用于接收需要被增强的类对象
public JDKProxy(StudentDao customerDao) {
this.studentDao = studentDao;
}
/**
* 利用jdk动态代理生成对象的方法 newProxyInstance的三个参数:
* loader:代理类的类加载器(和目标象使用相同的类加载器)
* interfaces:代理类要实现的接口列表(和目标对象具有相同的行为,实现相同的接口)
* handler:接口InvocationHandler实现类对象,当调用目标方法read时,实际上时在调用内部类中的invoke方法,实现增强
*/
public StudentDao create() {
StudentDao proxy = (StudentDao) Proxy.newProxyInstance(studentDao.getClass().getClassLoader(),
studentDao.getClass().getInterfaces(), new InvocationHandler() {
/**
* 当你调用目标方法时,其实是调用该方法 invoke中的三个参数:
* proxy:代理对象
* method:目标方法
* args:目标方法的形参
*/
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// 通过反射调用原有的read方法
Object invoke = method.invoke(studentDao, args);
// 执行增强的代码
System.out.println("在读书之余,写读书笔记");
return invoke;
}
});
return proxy;
}
}
注意:JDK动态代理只能对实现了接口的类进行代理
2、Cglib动态代理
在实际开发中,可能需要对没有实现接口的类增强,用JDK动态代理的方式就没法实现。采用Cglib动态代理可以对没有实现接口的类产生代理,实际上是生成了目标类的子类来增强。
创建StudentDao类,没有实现任何接口
public class StudentDao {
public void read() {
System.out.println("只读书,却不写读书笔记");
}
}
创建CglibProxy,为StudentDao来生成增强对象
public class CglibProxy implements MethodInterceptor {
private StudentDao1 studentDao1;
public CglibProxy(StudentDao1 studentDao1) {
this.studentDao1 = studentDao1;
}
public StudentDao1 create() {
// 创建核cglib核心类对象
Enhancer enhancer = new Enhancer();
// 设置父类对象
enhancer.setSuperclass(studentDao1.getClass());
// 创建回调
enhancer.setCallback(this);
// 创建代理对象
StudentDao1 proxy = (StudentDao1) enhancer.create();
return proxy;
}
/**
* 当你调用目标方法时,实质上是调用该方法 intercept四个参数:
* proxy:代理对象
* method:目标方法
* args:目标方法的形参
* methodProxy:代理方法
*/
@Override
public Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
// 调用目标(父类)方法
Object result = methodProxy.invokeSuper(proxy, args);
// 添加增强的代码
System.out.println("在读书之余,写读书笔记");
// 返回目标方法的返回值
return result;
}
}
测试类
public class ProxyTest {
// JDK动态代理
@Test
public void test1() {
StudentDao studentDao = new StudentDaoImpl();
JDKProxy jdkProxy = new JDKProxy(studentDao);
StudentDao create = (StudentDao) jdkProxy.create();
create.read();
}
// Cglib动态代理
@Test
public void test2(){
//目标
StudentDao1 studentDao1 = new StudentDao1();
CglibProxy cglibProxy = new CglibProxy(studentDao1);
StudentDao1 proxy = cglibProxy.create();
proxy.read();
}
}
测试结果: