CGLib 动态代理

JDK自从1.3版本开始,就引入了动态代理,JDK的动态代理用起来非常简单,但是它有一个限制,就是使用动态代理的对象必须实现一个或多个接口。如果想代理没有实现接口的类可以使用CGLIB包。

        CGLIB是一个强大的高性能的代码生成包。它被许多AOP的框架(例如Spring AOP)使用,为他们提供方法的interception(拦截)。Hibernate也使用CGLIB来代理单端single-ended(多对一和一对一)关联。EasyMock通过使用模仿(moke)对象来测试java代码的包。它们都通过使用CGLIB来为那些没有接口的类创建模仿(moke)对象。

        CGLIB包的底层是通过使用一个小而快的字节码处理框架ASM,来转换字节码并生成新的类。不鼓励直接使用ASM,因为它要求你必须对JVM内部结构包括class文件的格式和指令集都很熟悉。

        下面的图显示了和CGLIB包和一些框架和语言的关系。

                     CGLib 动态代理_第1张图片

Spring AOP和Hibernate同时使用JDK的动态代理和CGLIB包。Spring AOP,如果不强制使用CGLIB包,默认情况是使用JDK的动态代理来代理接口。

  CGLIB包对代理那些没有实现接口的类非常有用。它是通过动态的生成一个子类去覆盖所要代理类的不是final的方法,并设置好callback,则原有类的每个方法调用就会转变成调用用户定义的拦截方法(interceptors)。

  net.sf.cglib.proxy.Callback接口在CGLIB包中是一个很关键的接口,所有被net.sf.cglib.proxy.Enhancer类调用的回调(callback)接口都要继承这个接口。 用net.sf.cglib.proxy.MethodInterceptor接口是最通用的回调(callback)类型,它经常被基于代理的AOP用来实现拦截(intercept)方法的调用。

  net.sf.cglib.proxy.MethodInterceptor能够满足任何的拦截(interception )需要,但是某些特殊情况下可能使用过度。为了简化和提高性能,CGLIB包提供了一些专门的回调(callback)类型。例如:

CGLib 动态代理_第2张图片

net.sf.cglib.proxy.FixedValue 为提高性能,FixedValue回调对强制某一特别方法返回固定值是有用的。 
net.sf.cglib.proxy.NoOp NoOp回调把对方法调用直接委派到这个方法在父类中的实现。 
net.sf.cglib.proxy.LazyLoader 当实际的对象需要延迟装载时,可以使用LazyLoader回调。一旦实际对象被装载,它将被每一个调用代理对象的方法使用。 
net.sf.cglib.proxy.Dispatcher 和LazyLoader不同的是,当代理方法被调用时,装载对象的方法也总要被调用。 
net.sf.cglib.proxy.ProxyRefDispatcher 和Dispatcher不同的是,它可以把代理对象作为装载对象方法的一个参数传递。

  为了更好的使用代理,我们可以使用自己定义的MethodInterceptor类型回调(callback)来代替net.sf.cglib.proxy.NoOp回调。当对代理中所有方法的调用时,都会转向MethodInterceptor类型的拦截(intercept)方法,在拦截方法中再调用底层对象相应的方法。

       下面我们模拟一个数据库的CRUD操作的Dao的实现来简单运用CGLib:

1.创建一个对Table操作的DAO类。

public class TableDAO {
	public void create(){
		System.out.println("create() is running !");
	}
	public void query(){
		System.out.println("query() is running !");
	}
	public void update(){
		System.out.println("update() is running !");
	}
	public void delete(){
		System.out.println("delete() is running !");
	}
}

接下来我们创建一个DAO工厂,用来生成DAO实例。

public class TableDAOFactory {
	private static TableDAO tDao = new TableDAO();
	public static TableDAO getInstance(){
		return tDao;
	}
}
下面我们创建客户端,用来调用CRUD方法。
public class Client {

	public static void main(String[] args) {
		TableDAO tableDao = TableDAOFactory.getInstance();
		doMethod(tableDao);
	}
	public static void doMethod(TableDAO dao){
		dao.create();
		dao.query();
		dao.update();
		dao.delete();
	}
}
目前这里并没有CGlib的任何内容。问题不会这么简单的就结束,新的需求来临了,Boss告诉我们这些方法不能开放给用户,只有“张三”才有权使用。怎么办,难道我们要在每个方法上面进行判断吗?对了Proxy可能是最好的解决办法。jdk的代理就可以解决了。 但是jdk的代理需要实现接口,这样,我们的dao类需要改变了。既然不想改动dao又要使用代理,我们这就请出CGlib。我们只需新增一个权限验证的方法拦截器。
public class AuthProxy implements MethodInterceptor {
	private String name ;
	//传入用户名称
	public AuthProxy(String name){
		this.name = name;
	}
	public Object intercept(Object arg0, Method arg1, Object[] arg2,
			MethodProxy arg3) throws Throwable {
		//用户进行判断
		if(!"张三".equals(name)){
			System.out.println("你没有权限!");
			return null;
		}
		return arg3.invokeSuper(arg0, arg2);
	}
}
对我们的dao工厂进行修改,我们提供一个使用代理的实例生成方法

public static TableDAO getAuthInstance(AuthProxy authProxy){
	Enhancer en = new Enhancer();
	//进行代理
	en.setSuperclass(TableDAO.class);
	en.setCallback(authProxy);
	//生成代理实例
	return (TableDAO)en.create();
}
我们这就可以看看客户端的实现了。添加了两个方法用来验证不同用户的权限。

public static void haveAuth(){
	TableDAO tDao = TableDAOFactory.getAuthInstance(new AuthProxy("张三"));
	doMethod(tDao);
}
public static void haveNoAuth(){
	TableDAO tDao = TableDAOFactory.getAuthInstance(new AuthProxy("李四"));
	doMethod(tDao);
}
简单的aop就这样实现了,难道就这样结束了么?Boss又来训话了,不行不行,现在除了"张三"其他人都用不了了,现在不可以这样。他们都来向我反映了,必须使用开放查询功能。现在可难不倒我们了,因为我们使用了CGlib。当然最简单的方式是去修改我们的方法拦截器,不过这样会使逻辑变得复杂,且不利于维护。还好CGlib给我们提供了方法过滤器(CallbackFilter),CallbackFilte可以明确表明被代理的类中不同的方法,被哪个拦截器所拦截。下面我们就来做个过滤器用来过滤query方法

public class AuthProxyFilter implements CallbackFilter{
	public int accept(Method arg0) {
		if(!"query".equalsIgnoreCase(arg0.getName()))
			return 0;
		return 1;
	}

}
我们在工厂中新增一个使用了过滤器的实例生成方法。

public static TableDAO getAuthInstanceByFilter(AuthProxy authProxy){
	Enhancer en = new Enhancer();
	en.setSuperclass(TableDAO.class);
	en.setCallbacks(new Callback[]{authProxy,NoOp.INSTANCE});
	en.setCallbackFilter(new AuthProxyFilter());
	return (TableDAO)en.create();
}
看到了吗setCallbacks中定义了所使用的拦截器,其中NoOp.INSTANCE是CGlib所提供的实际是一个没有任何操作的拦截器,他们是有序的。一定要和CallbackFilter里面的顺序一致。明白了吗?上面return返回的就是返回的顺序。也就是说如果调用query方法就使用NoOp.INSTANCE进行拦截。现在看一下客户端代码。

public static void haveAuthByFilter(){
	TableDAO tDao = TableDAOFactory.getAuthInstanceByFilter(new AuthProxy("张三"));
	doMethod(tDao);

	tDao = TableDAOFactory.getAuthInstanceByFilter(new AuthProxy("李四"));
	doMethod(tDao);
}
现在"李四"也可以使用query方法了,其他方法仍然没有权限。
当然这个代理的实现没有任何侵入性,无需强制让dao去实现接口。

你可能感兴趣的:(CGLib 动态代理)