Spring AOP使用了两种代理机制,一种是基于JDK的动态代理,另一种是基于CGLib的动态代理。
首先描述一下环境,实体是一个User类,有UserService、UserDao和UserDaoImpl。而我们要纵向添加的功能就是日志分析功能。在每次执行一个方法之前,提示,分析方法时间,提示结束。
具体构建代码如下:
UserService:
package springTest.service;
import springTest.dao.UserDao;
public class UserService {
private UserDao userDao;
public UserDao getUserDao() {
return userDao;
}
public void setUserDao(UserDao userDao) {
this.userDao = userDao;
}
}
UserDao:
package springTest.dao;
import springTest.model.User;
public interface UserDao {
public void removeUser(int Id) ;
}
UserDaoImpl:
package springTest.dao.impl;
import springTest.dao.UserDao;
import springTest.model.User;
public class UserDaoImpl implements UserDao{
@Override
public void removeUser(int Id) {
System.out.println("根据ID删除User记录:" + Id);
try {
Thread.currentThread().sleep(20);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
}
为了实现逻辑切入,还会建立一些类,他们的关系如下:
MethodPerformace类,他负责记录方法开始时间,计算方法运行时间。
PerformanceMonitor类,他是一个线程池,负责给每个方法给配一个MethodPerformace对象来计算。
PerformaceHandler类,他负责把逻辑代码和业务织如到一起。之前两个类就是逻辑代码。
TestJDKProxy类,他负责创建代理,并被调用。
代码如下:
MethodPerformace类:
package ProxyTest;
public class MethodPerformace {
private long begin;
private long end;
private String serviceMethod;
public MethodPerformace(String serviceMethod){
this.serviceMethod = serviceMethod;
this.begin = System.currentTimeMillis();//记录方法调用开始时的系统时间
}
public void printPerformace(){
//以下两行程序得到方法调用后的系统时间,并计算出方法执行花费时间
end = System.currentTimeMillis();
long elapse = end - begin;
//报告业务方法执行时间
System.out.println(serviceMethod+"花费"+elapse+"毫秒。");
}
}
PerformanceMonitor类:
package ProxyTest;
/**
*
* Performance 表演
* Monitor 监视
*
* PerformanceMonitor就是一个线程池,每一个线程接受一个方法,并统计该方法的执行时间。
*
*/
public class PerformanceMonitor {
//线程池
private static ThreadLocal<MethodPerformace> performaceRecord =new ThreadLocal<MethodPerformace>();
//分配线程并计算方法时间
public static void begin(String method) {
System.out.println("begin monitor...");
MethodPerformace mp = new MethodPerformace(method);
performaceRecord.set(mp);
}
//得到线程并打印信息
public static void end() {
System.out.println("end monitor...");
MethodPerformace mp = performaceRecord.get();
mp.printPerformace();
}
}
JDK动态代理
PerformaceHandler类:
package ProxyTest;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
/**
* 第一点, 横切代码 安置在PerformaceHandler中
* 第二点, 每一个横切代码要加载到一个业务类的方法里,用一个线程来实现。PerformanceMonitor是一个线程池。
*
* <span style="background-color: rgb(255, 255, 102);"> 重点启迪:所有方法 </span>
* 两个“所有”涵义
* invoke(Object proxy, Method method, Object[] args)
* 1.可以看到这里的方法Method method,是所有的目标类中的方法。也就是说只能让所有方法都加逻辑或不加。
* 2.所有方法,都是Method method代表,也就是说,再反转之前,你可以逻辑的去控制,例如m.getname()+"方法被执行",记入LOG。
* 这是一种利用方式。我用beforeMethod()举例一下。
* 3.注意写的方式,外面写方法,invoke里调用。
*/
public class PerformaceHandler implements InvocationHandler {
private Object target;
public PerformaceHandler(Object target){ //target为目标的业务类
this.target = target;
}
public void beforeMethod(Method m){
System.out.println("方法:"+m.getName()+"开始执行");
}
@Override
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable {
//这里就是在调用 横切代码,也就是自己添加的方法
beforeMethod( method);
PerformanceMonitor.begin(target.getClass().getName()+"."+ method.getName());
//通过反射方法调用被代理类的方法
Object obj = method.invoke(target, args);
PerformanceMonitor.end();
return obj;
}
}
TestJDKProxy类:
package ProxyTest;
import java.lang.reflect.Proxy;
import springTest.dao.UserDao;
import springTest.dao.impl.UserDaoImpl;
import springTest.service.UserService;
/**
* 问题一:组织完代码为何需要代理?
*
* 中要生成代理对象,这个代理对象就是混合加入切片的对象。
* 虽然把代码组织在一起了,但只有通过代理对象才可以调用。
* 调用这个对象,就可以实现功能。
*
* 问题二:创建代理类的步骤:
*
* 第一步,获取目标业务类。
* 第二步,将目标业务类和横切代码通过PerformaceHandler组织到一起。
* 第三步,创建代理类,(代理类是组织代码的实现者)
*
* 问题三:JDK创建代理的限制:
* 使用JDK创建代理有一个限制,即它只能为接口创建代理。所以target对象一定是一个接口。
* 对于没有通过接口定义业务方法的类,如何动态创建代理实例就要用CGLib动态代理。CGLib采用非常底层的字节码技术,
* 可以为一个类创建子类,并在子类中采用方法拦截的技术拦截所有父类方法的调用,并在拦截方法相应地织入横切逻辑。
*
*
*/
public class TestJDKProxy {
public static void main(String[] args) {
//1
UserDao target = new UserDaoImpl();
//2
PerformaceHandler handler = new PerformaceHandler(target);
//3 参数:类加载器,创建的代理实例所要实现的一组接口,组织代码对象
UserDao proxy = (UserDao) Proxy.newProxyInstance(target.getClass().getClassLoader(),target.getClass().getInterfaces(),handler);
//操作代理类
proxy.removeUser(1);
}
}
JDK动态代理总结:
1.要明白上述类之间的关系和各自功能,在编写理解。
2.PerformaceHandler织入代码,invoke()接受的参数Method method的两个所有的含义。
3.要理解JDK动态代理的限制以及他实现的步骤。
CGLib动态代理
使用JDK创建代理有一个限制,即它只能为接口创建代理,对于一般类,就需要使用CGLib动态代理。CGLib采用非常底层的字节码技术,可以为一个类创建子类,并在子类中采用方法拦截的技术拦截所有父类方法的调用,并在拦截方法相应地织入横切逻。
CglibProxy类,他的作用是负责织入:
package ProxyTest;
import java.lang.reflect.Method;
import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
/**
* 简述:CglibProxy的作用就相当于PerformaceHandler,不同之处在于它是基于CglibProxy。
* 第一点,getProxy(Class clazz)为一个类创建动态代理对象,该代理对象是指定类clazz的子类,
* 在这个代理对象中,将横切代码组织到其中。
*
*/
public class CglibProxy implements MethodInterceptor{
private Enhancer enhancer = new Enhancer();
public Object getProxy(Class clazz) {
enhancer.setSuperclass(clazz); //① 设置需要创建子类的类
enhancer.setCallback(this);
return enhancer.create(); //②通过字节码技术动态创建子类实例
}
/*
* obj表示父类的实例,method为父类方法的反射对象,args为方法的动态入参,而proxy为代理类实例
*/
public Object intercept(Object obj, Method method, Object[] args,MethodProxy proxy) throws Throwable {
//这里就是在调用 横切代码
PerformanceMonitor.begin(obj.getClass().getName()+"."+method.getName());
//通过反射方法调用目标业务类的业务方法
Object result=proxy.invokeSuper(obj, args);
PerformanceMonitor.end();
return result;
}
}
TestCglibProxy类:创建代理对象并调用。
package ProxyTest;
import springTest.dao.impl.UserDaoImpl;
/**
*
* 问题一:创建代理类的步骤:
* 1.获取CglibProxy对象
* 2.通过动态生成子类的方式创建代理对象
* 问题二:CglibProxy意义
* 解决了JDK创建代理只能代理接口的限制
*
*/
public class TestCglibProxy {
public static void main(String[] args) {
CglibProxy proxy = new CglibProxy();
UserDaoImpl userDao = (UserDaoImpl )proxy.getProxy(UserDaoImpl.class);
userDao.removeUser(1);
}
}
总结:
Spring AOP在底层就是利用JDK动态代理或CGLib动态代理技术为目标Bean织入横切逻辑。在这里,我们对以上两节动态创建代理对象做一个小结。
在PerformaceHandler和CglibProxy中,有三点值得注意的地方是:第一,目标类的所有方法都被添加了性能监视横切的代码,而有时,这并不是我们所期望的,我们可能只希望对业务类中的某些方法织入横切代码;第二,我们手工指定了织入横切代码的织入点,即在目标类业务方法的开始和结束前调用;第三,我们手工编写横切代码。以上三个问题,在AOP中占用重要的地位,因为Spring AOP的主要工作就是围绕以上三点展开:Spring AOP通过Pointcut(切点)指定在哪些类的哪些方法上施加横切逻辑,通过Advice(增强)描述横切逻辑和方法的具体织入点(方法前、方法后、方法的两端等),此外,Spring还通过Advisor(切面)组合Pointcut和Advice。有了Advisor的信息,Spring就可以利用JDK或CGLib的动态代理技术为目标Bean创建织入切面的代理对象了。
JDK动态代理所创建的代理对象,在JDK 1.3下,性能强差人意。虽然在高版本的JDK中,动态代理对象的性能得到了很大的提高,但是有研究表明,CGLib所创建的动态代理对象的性能依旧比JDK的所创建的代理对象的性能高不少(大概10倍)。而CGLib在创建代理对象时性能却比JDK动态代理慢很多(大概8倍),所以对于singleton的代理对象或者具有实例池的代理,因为不需要频繁创建代理对象,所以比较适合用CGLib动态代理技术,反之适合用JDK动态代理技术。此外,由于CGLib采用生成子类的技术创建代理对象,所以不能对目标类中的final方法进行代理。