Spring AOP的动态代理
在业务处理代码中,通常都有日志记录、性能统计、安全控制、事务处理、异常处理等操作。尽管使用OOP可以通过封装或继承的方式达到代码的重用,但仍然存在同样的代码分散到各个方法中。因此,采用OOP处理日志记录等操作,不仅增加了开发者的工作量,而且提高了升级维护的困难。为了解决此类问题,AOP
思想应运而生。AOP采取横向抽取机制,即将分散在各个方法中的重复代码提取出来,然后在程序编译或运行阶段,再将这些抽取出来的代码应用到需要执行的地方。这种横向抽取机制,采用传统的OOP是无法办到的,因为OOP实现的是父子关系的纵向重用。但是AOP不是OOP的替代品,而是OOP的补充,它们相辅相成。
1.切面
切面(Aspect)是指封装横切到系统功能(如事务处理)的类。
2.连接点
连接点(Joinpoint)是指程序运行中的一些时间点,如方法的调用或异常的抛出。
3.切入点
切入点(Pointcut)是指那些需要处理的连接点。在Spring AOP 中,所有的方法执行都是连接点,而切入点是一个描述信息,它修饰的是连接点,通过切入点确定哪些连接点需要被处理。
4.通知(增强处理)
由切面添加到特定的连接点(满足切入点规则)的一段代码,即在定义好的切入点处所要执行的程序代码。可以将其理解为切面开启后,切面的方法。因此,通知是切面的具体实现。
5.引入
引入(Introduction)允许在现有的实现类中添加自定义的方法和属性。
6.目标对象
目标对象(Target Object)是指所有被通知的对象。如果AOP 框架使用运行时代理的方式(动态的AOP)来实现切面,那么通知对象总是一个代理对象。
7.代理
代理(Proxy)是通知应用到目标对象之后,被动态创建的对象。
8.组入
组入(Weaving)是将切面代码插入到目标对象上,从而生成代理对象的过程。根据不同的实现技术,AOP织入有三种方式:编译器织入,需要有特殊的Java编译器;类装载期织入,需要有特殊的类装载器;动态代理织入,在运行期为目标类添加通知生成子类的方式。Spring AOP框架默认采用动态代理织入,而AspectJ(基于Java语言的AOP框架)采用编译器织入和类装载期织入。
目前,Spring AOP中常用JDK
和CGLIB
两种动态代理技术。
JDK动态代理
JDK动态代理是java.lang.reflect.*包提供的方式,它必须借助一个接口才能产生代理对象。因此,对于使用业务接口的类,Spring默认使用JDK动态代理实现AOP。
package dynamic.jdk;
import org.springframework.stereotype.Repository;
@Repository("testDao")
public class TestDaoImpl implements TestDao {
@Override
public void save() {
// TODO Auto-generated method stub
System.out.println("保存");
}
@Override
public void modify() {
// TODO Auto-generated method stub
System.out.println("修改");
}
@Override
public void delete() {
// TODO Auto-generated method stub
System.out.println("删除");
}
}
package aspect;
//切面类,可以定义多个通知,即增强处理的方法
public class MyAspet {
public void check() {
System.out.println("模拟权限控制");
}
public void except() {
System.out.println("模拟异常处理");
}
public void log() {
System.out.println("模拟日志记录");
}
public void monitor() {
System.out.println("性能监测");
}
}
package dynamic.jdk;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import aspect.MyAspet;
public class JDKDynamicProxy implements InvocationHandler{
//声明目标类接口对象(真实对象)
private TestDao testDao;
//创建代理的方法,建立代理对象和真实对象的代理关系,并返回代理对象
public Object createProxy(TestDao testDao) {
this.testDao=testDao;
//1.类加载器
ClassLoader cld=JDKDynamicProxy.class.getClassLoader();
//2.被代理对象实现的所有接口
Class[] clazz=testDao.getClass().getInterfaces();
//3.使用代理类进行增强,返回代理后的对象
return Proxy.newProxyInstance(cld, clazz, this);
}
/**
* 代理的逻辑方法,所有动态代理类的方法调用,都交给该方法处理
* proxy被代理对象
* method将要被执行的方法信息
* args执行方法时重要的参数
* return代理结果
* **/
@Override
public Object invoke(Object proxy,Method method,Object[] args) throws Throwable{
//创建一个切面
MyAspet myAspet=new MyAspet();
//前增强
myAspet.check();
myAspet.except();
//在目标类上调用方法,并传入参数,相当于调用testDao中的方法
Object obj=method.invoke(testDao, args);
//后增强
myAspet.log();
myAspet.monitor();
return obj;
}
}
package dynamic.jdk;
public class JDKDynamicTest {
public static void main(String[] args) {
//创建代理对象
JDKDynamicProxy jdkProxy=new JDKDynamicProxy();
//创建目标对象
TestDao testDao=new TestDaoImpl();
//从代理对象中获取增强后的目标对象,该对象是一个被代理的对象,它会进入代理的逻辑方法invoke里
TestDao testDaoAdvice=(TestDao) jdkProxy.createProxy(testDao);
//执行方法
testDaoAdvice.save();
System.out.println("===========");
testDaoAdvice.modify();
System.out.println("===========");
testDaoAdvice.delete();
}
}
CGLIB动态代理
CGLIB(Code Generation Library)是一个高性能开源的代码生成包,采用非常底层的字节码技术,对指定的目标类生成一个子类,并对子类进行增强。在Spring Core包中已经集成了CGLIB所需要的JAR包,不需要另外导入JAR包。
package dynamic.jdk;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import aspect.MyAspet;
public class JDKDynamicProxy implements InvocationHandler{
//声明目标类接口对象(真实对象)
private TestDao testDao;
//创建代理的方法,建立代理对象和真实对象的代理关系,并返回代理对象
public Object createProxy(TestDao testDao) {
this.testDao=testDao;
//1.类加载器
ClassLoader cld=JDKDynamicProxy.class.getClassLoader();
//2.被代理对象实现的所有接口
Class[] clazz=testDao.getClass().getInterfaces();
//3.使用代理类进行增强,返回代理后的对象
return Proxy.newProxyInstance(cld, clazz, this);
}
/**
* 代理的逻辑方法,所有动态代理类的方法调用,都交给该方法处理
* proxy被代理对象
* method将要被执行的方法信息
* args执行方法时重要的参数
* return代理结果
* **/
@Override
public Object invoke(Object proxy,Method method,Object[] args) throws Throwable{
//创建一个切面
MyAspet myAspet=new MyAspet();
//前增强
myAspet.check();
myAspet.except();
//在目标类上调用方法,并传入参数,相当于调用testDao中的方法
Object obj=method.invoke(testDao, args);
//后增强
myAspet.log();
myAspet.monitor();
return obj;
}
}
package dynamic.jdk;
public class JDKDynamicTest {
public static void main(String[] args) {
//创建代理对象
JDKDynamicProxy jdkProxy=new JDKDynamicProxy();
//创建目标对象
TestDao testDao=new TestDaoImpl();
//从代理对象中获取增强后的目标对象,该对象是一个被代理的对象,它会进入代理的逻辑方法invoke里
TestDao testDaoAdvice=(TestDao) jdkProxy.createProxy(testDao);
//执行方法
testDaoAdvice.save();
System.out.println("===========");
testDaoAdvice.modify();
System.out.println("===========");
testDaoAdvice.delete();
}
}