简单模拟Spring AOP功能 (2) JDK动态代理技术和CGLib的动态代理

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方法进行代理。



  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

良之才-小良

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值