java动态代理(JDK和CGLIB原理解析与使用)

CGLIB的动态代理

原理

代理为控制要访问的目标对象提供了一种途径。当访问对象时,它引入了一个间接的层。JDK自从1.3版本开始,就引入了动态代理,并且经常被用来动态地创建代理。JDK的动态代理用起来非常简单,当它有一个限制,就是使用动态代理的对象必须实现一个或多个接口。如果想代理没有实现接口的继承的类,该怎么办?现在我们可以使用CGLIB包。

二、什么是cglib

CGLIB是一个强大的高性能的代码生成包。

1>它广泛的被许多AOP的框架使用,例如:spring AOP和dynaop,为他们提供方法的interception(拦截);

2>hibernate使用CGLIB来代理单端single-ended(多对一和一对一)关联(对集合的延迟抓取,是采用其他机制实现的);

3>EasyMock和jMock是通过使用模仿(moke)对象来测试java代码的包。

它们都通过使用CGLIB来为那些没有接口的类创建模仿(moke)对象。

三、底层

CGLIB包的底层是通过使用一个小而快的字节码处理框架ASM(Java字节码操控框架),来转换字节码并生成新的类。除了CGLIB包,脚本语言例如 Groovy和BeanShell,也是使用ASM来生成java的字节码。当不鼓励直接使用ASM,因为它要求你必须对JVM内部结构包括class文件的格式和指令集都很熟悉。所以cglib包要依赖于asm包,需要一起导入。下图为cglib与一些框架和语言的关系(CGLIB Library and ASM Bytecode Framework)

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

四、实例场景

1. 我们创建一个对Table操作的DAO类,提供了CRUD方法。

BookServiceBean.java

package com.tech.cglibx; public class BookServiceBean { 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 !"); } }

OK,它就是一个javaBean,提供了CRUD方法的javaBean。

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

package com.tech.cglibx; public class BookServiceFactory { private static BookServiceBean service = new BookServiceBean(); private BookServiceFactory() { } public static BookServiceBean getInstance() { return service; } }

接下来我们创建客户端,用来调用CRUD方法。

public class Client { public static void main(String[] args) { BookServiceBean service = BookServiceFactory.getInstance(); doMethod(service); } public static void doMethod(BookServiceBean service){ service.create(); service.update(); service.query(); service.delete(); } }

OK,完成了,CRUD方法完全被调用了。

当然这里并没有CGlib的任何内容。问题不会这么简单的就结束,新的需求来临了。

2. one day,Boss告诉我们这些方法不能开放给用户,只有“boss”才有权使用。怎么办,难道我们要在每个方法上面进行判断吗?好像这么做也太那啥了吧?对了,Proxy可能是最好的解决办法。jdk的代理就可以解决了。 好了我们来动手改造吧。等等jdk的代理需要实现接口,这样, 我们的dao类需要改变了。既然不想改动dao又要使用代理,我们这就请出CGlib。

我们只需新增一个权限验证的方法拦截器。

package com.tech.cglibx; import java.lang.reflect.Method; import net.sf.cglib.proxy.Enhancer; import net.sf.cglib.proxy.MethodInterceptor; import net.sf.cglib.proxy.MethodProxy; import org.apache.log4j.Logger; public class MyCglibProxy implements MethodInterceptor{ private Logger log=Logger.getLogger(MyCglibProxy.class); public Enhancer enhancer = new Enhancer(); private String name; public MyCglibProxy(String name) { this.name = name ; } /** * 根据class对象创建该对象的代理对象 * 1、设置父类;2、设置回调 * 本质:动态创建了一个class对象的子类 * * @param cls * @return */ public Object getDaoBean(Class cls) { enhancer.setSuperclass(cls); enhancer.setCallback(this); return enhancer.create(); } @Override public Object intercept(Object object, Method method, Object[] args, MethodProxy methodProxy) throws Throwable { log.info("调用的方法是:" + method.getName()); //用户进行判断 if(!"张三".equals(name)){ System.out.println("你没有权限!"); return null; } Object result = methodProxy.invokeSuper(object, args); return result; } }

当然不能忘了对我们的dao工厂进行修改,我们提供一个使用代理的实例生成方法。上面的类中已经提供了一个通用的获取代理实例的方法,没有特殊需求(如下3)的方式可以使用上面的方式获取代理对象。

public static BookServiceBean getProxyInstance(MyCglibProxy myProxy){ Enhancer en = new Enhancer(); //进行代理 en.setSuperclass(BookServiceBean.class); en.setCallback(myProxy); //生成代理实例 return (BookServiceBean)en.create(); }

我们这就可以看看客户端的实现了。添加了两个方法用来验证不同用户的权限

BookServiceBean service = BookServiceFactory.getProxyInstance(new MyCglibProxy("boss")); service.create(); BookServiceBean service2 = BookServiceFactory.getProxyInstance(new MyCglibProxy("john")); service2.create();

OK,"boss"的正常执行,"john"的没有执行。

看到了吗?简单的aop就这样实现了

难道就这样结束了么?

3.grd Boss又来训话了,不行不行,现在除了"boss"其他人都用不了了,现在不可以这样。必须使用开放查询功能。

哈哈,现在可难不倒我们了,因为我们使用了CGlib。当然最简单的方式是去修改我们的方法拦截器,不过这样会使逻辑变得复杂,且不利于维护。

还好CGlib给我们提供了方法过滤器(CallbackFilter),CallbackFilte可以明确表明,被代理的类中不同的方法,被哪个拦截器所拦截。

下面我们就来做个过滤器用来过滤query方法。

package com.tech.cglibx; import java.lang.reflect.Method; import net.sf.cglib.proxy.CallbackFilter; public class MyProxyFilter implements CallbackFilter { @Override public int accept(Method arg0) { if(!"query".equalsIgnoreCase(arg0.getName())) return 0; return 1; } }

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

public static BookServiceBean getAuthInstanceByFilter(MyCglibProxy myProxy){ Enhancer en = new Enhancer(); en.setSuperclass(BookServiceBean.class); en.setCallbacks(new Callback[]{myProxy,NoOp.INSTANCE}); en.setCallbackFilter(new MyProxyFilter()); return (BookServiceBean)en.create(); }

setCallbacks中定义了所使用的拦截器,其中NoOp.INSTANCE是CGlib所提供的实际是一个没有任何操作的拦截器,

他们是有序的,一定要和CallbackFilter里面的顺序一致。上面return返回(0/1)的就是返回的顺序。也就是说如果调用query方法就使用NoOp.INSTANCE进行拦截。

现在看一下客户端代码。

BookServiceBean service = BookServiceFactory.getProxyInstanceByFilter(new MyCglibProxy("jhon")); service.create(); BookServiceBean service2 = BookServiceFactory.getProxyInstanceByFilter(new MyCglibProxy("jhon")); service2.query();

ok,现在"李四"也可以使用query方法了,其他方法仍然没有权限。

当然这个代理的实现没有任何侵入性,无需强制让dao去实现接口。

二、CGLIB使用

JDK实现动态代理需要实现类通过接口定义业务方法,对于没有接口的类,如何实现动态代理呢,这就需要CGLib了。CGLib采用了非常底层的字节码技术,其原理是通过字节码技术为一个类创建子类,并在子类中采用方法拦截的技术拦截所有父类方法的调用,顺势织入横切逻辑。JDK动态代理与CGLib动态代理均是实现Spring AOP的基础。

简单的实现举例:

这是一个需要被代理的类,也就是父类,通过字节码技术创建这个类的子类,实现动态代理。

public class SayHello { public void say(){ System.out.println("hello everyone"); } }

该类实现了创建子类的方法与代理的方法。getProxy(SuperClass.class)方法通过入参即父类的字节码,通过扩展父类的class来创建代理对象。intercept()方法拦截所有目标类方法的调用,obj表示目标类的实例,method为目标类方法的反射对象,args为方法的动态入参,proxy为代理类实例。proxy.invokeSuper(obj, args)通过代理类调用父类中的方法。[java]

public class CglibProxy implements MethodInterceptor{ private Enhancer enhancer = new Enhancer(); public Object getProxy(Class clazz){ //设置需要创建子类的类 enhancer.setSuperclass(clazz); enhancer.setCallback(this); //通过字节码技术动态创建子类实例 return enhancer.create(); } //实现MethodInterceptor接口方法 public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable { System.out.println("前置代理"); //通过代理类调用父类中的方法 Object result = proxy.invokeSuper(obj, args); System.out.println("后置代理"); return result; } }

具体实现类:

public class DoCGLib { public static void main(String[] args) { CglibProxy proxy = new CglibProxy(); //通过生成子类的方式创建代理类 SayHello proxyImp = (SayHello)proxy.getProxy(SayHello.class); proxyImp.say(); } }

输出结果:

前置代理 hello everyone 后置代理

CGLib创建的动态代理对象性能比JDK创建的动态代理对象的性能高不少,但是CGLib在创建代理对象时所花费的时间却比JDK多得多,所以对于单例的对象,因为无需频繁创建对象,用CGLib合适,反之,使用JDK方式要更为合适一些。同时,由于CGLib由于是采用动态创建子类的方法,对于final方法,无法进行代理。

三、JAVA的代理模式

代理模式

代理模式是常用的java设计模式,他的特征是代理类与委托类有同样的接口,代理类主要负责为委托类预处理消息、过滤消息、把消息转发给委托类,以及事后处理消息等。代理类与委托类之间通常会存在关联关系,一个代理类的对象与一个委托类的对象关联,代理类的对象本身并不真正实现服务,而是通过调用委托类的对象的相关方法,来提供特定的服务。

按照代理的创建时期,代理类可以分为两种。

静态代理:由程序员创建或特定工具自动生成源代码,再对其编译。在程序运行前,代理类的.class文件就已经存在了。

动态代理:在程序运行时,运用反射机制动态创建而成。

首先看一下静态代理:

1、Count.java package net.battier.dao; /** * 定义一个账户接口 * * @author Administrator * */ public interface Count { // 查看账户方法 public void queryCount(); // 修改账户方法 public void updateCount(); } 2、CountImpl.java package net.battier.dao.impl; import net.battier.dao.Count; /** * 委托类(包含业务逻辑) * * @author Administrator * */ public class CountImpl implements Count { @Override public void queryCount() { System.out.println("查看账户方法..."); } @Override public void updateCount() { System.out.println("修改账户方法..."); } } 、CountProxy.java package net.battier.dao.impl; import net.battier.dao.Count; /** * 这是一个代理类(增强CountImpl实现类) * * @author Administrator * */ public class CountProxy implements Count { private CountImpl countImpl; /** * 覆盖默认构造器 * * @param countImpl */ public CountProxy(CountImpl countImpl) { this.countImpl = countImpl; } @Override public void queryCount() { System.out.println("事务处理之前"); // 调用委托类的方法; countImpl.queryCount(); System.out.println("事务处理之后"); } @Override public void updateCount() { System.out.println("事务处理之前"); // 调用委托类的方法; countImpl.updateCount(); System.out.println("事务处理之后"); } } 3、TestCount.java package net.battier.test; import net.battier.dao.impl.CountImpl; import net.battier.dao.impl.CountProxy; /** *测试Count类 * * @author Administrator * */ public class TestCount { public static void main(String[] args) { CountImpl countImpl = new CountImpl(); CountProxy countProxy = new CountProxy(countImpl); countProxy.updateCount(); countProxy.queryCount(); } }

观察代码可以发现每一个代理类只能为一个接口服务,这样一来程序开发中必然会产生过多的代理,而且,所有的代理操作除了调用的方法不一样之外,其他的操作都一样,则此时肯定是重复代码。解决这一问题最好的做法是可以通过一个代理类完成全部的代理功能,那么此时就必须使用动态代理完成。

再来看一下动态代理:

JDK动态代理中包含一个类和一个接口:

InvocationHandler接口:

public interface InvocationHandler { public Object invoke(Object proxy,Method method,Object[] args) throws Throwable; }

参数说明:

Object proxy:指被代理的对象。

Method method:要调用的方法

Object[] args:方法调用时所需要的参数

可以将InvocationHandler接口的子类想象成一个代理的最终操作类,替换掉ProxySubject。

Proxy类:

Proxy类是专门完成代理的操作类,可以通过此类为一个或多个接口动态地生成实现类,此类提供了如下的操作方法:

public static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces,

InvocationHandler h)

throws IllegalArgumentException

参数说明:

ClassLoader loader:类加载器

Class<?>[] interfaces:得到全部的接口

InvocationHandler h:得到InvocationHandler接口的子类实例

Ps:类加载器

在Proxy类中的newProxyInstance()方法中需要一个ClassLoader类的实例,ClassLoader实际上对应的是类加载器,在Java中主要有一下三种类加载器;

Booststrap ClassLoader:此加载器采用C++编写,一般开发中是看不到的;

Extendsion ClassLoader:用来进行扩展类的加载,一般对应的是jre\lib\ext目录中的类;

AppClassLoader:(默认)加载classpath指定的类,是最常使用的是一种加载器。

动态代理

与静态代理类对照的是动态代理类,动态代理类的字节码在程序运行时由Java反射机制动态生成,无需程序员手工编写它的源代码。动态代理类不仅简化了编程工作,而且提高了软件系统的可扩展性,因为Java 反射机制可以生成任意类型的动态代理类。java.lang.reflect 包中的Proxy类和InvocationHandler 接口提供了生成动态代理类的能力。

动态代理示例:

1、BookFacade.java package net.battier.dao; public interface BookFacade { public void addBook(); } 2、BookFacadeImpl.java package net.battier.dao.impl; import net.battier.dao.BookFacade; public class BookFacadeImpl implements BookFacade { @Override public void addBook() { System.out.println("增加图书方法。。。"); } } 、BookFacadeProxy.java package net.battier.proxy; import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import java.lang.reflect.Proxy; /** * JDK动态代理代理类 * * @author student * */ public class BookFacadeProxy implements InvocationHandler { private Object target; /** * 绑定委托对象并返回一个代理类 * @param target * @return */ public Object bind(Object target) { this.target = target; //取得代理对象 return Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(), this); //要绑定接口(这是一个缺陷,cglib弥补了这一缺陷) } @Override /** * 调用方法 */ public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { Object result=null; System.out.println("事物开始"); //执行方法 result=method.invoke(target, args); System.out.println("事物结束"); return result; } } 3、TestProxy.java package net.battier.test; import net.battier.dao.BookFacade; import net.battier.dao.impl.BookFacadeImpl; import net.battier.proxy.BookFacadeProxy; public class TestProxy { public static void main(String[] args) { BookFacadeProxy proxy = new BookFacadeProxy(); BookFacade bookProxy = (BookFacade) proxy.bind(new BookFacadeImpl()); bookProxy.addBook(); } }

但是,JDK的动态代理依靠接口实现,如果有些类并没有实现接口,则不能使用JDK代理,这就要使用cglib动态代理了。

Cglib动态代理

JDK的动态代理机制只能代理实现了接口的类,而不能实现接口的类就不能实现JDK的动态代理,cglib是针对类来实现代理的,他的原理是对指定的目标类生成一个子类,并覆盖其中方法实现增强,但因为采用的是继承,所以不能对final修饰的类进行代理。

示例

1、BookFacadeCglib.java package net.battier.dao; public interface BookFacade { public void addBook(); } 2、BookCadeImpl1.java package net.battier.dao.impl; /** * 这个是没有实现接口的实现类 * * @author student * */ public class BookFacadeImpl1 { public void addBook() { System.out.println("增加图书的普通方法..."); } } 3、BookFacadeProxy.java package net.battier.proxy; import java.lang.reflect.Method; import net.sf.cglib.proxy.Enhancer; import net.sf.cglib.proxy.MethodInterceptor; import net.sf.cglib.proxy.MethodProxy; /** * 使用cglib动态代理 * * @author student * */ public class BookFacadeCglib implements MethodInterceptor { private Object target; /** * 创建代理对象 * * @param target * @return */ public Object getInstance(Object target) { this.target = target; Enhancer enhancer = new Enhancer(); enhancer.setSuperclass(this.target.getClass()); // 回调方法 enhancer.setCallback(this); // 创建代理对象 return enhancer.create(); } @Override // 回调方法 public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable { System.out.println("事物开始"); proxy.invokeSuper(obj, args); System.out.println("事物结束"); return null; } } 4、TestCglib.java package net.battier.test; import net.battier.dao.impl.BookFacadeImpl1; import net.battier.proxy.BookFacadeCglib; public class TestCglib { public static void main(String[] args) { BookFacadeCglib cglib=new BookFacadeCglib(); BookFacadeImpl1 bookCglib=(BookFacadeImpl1)cglib.getInstance(new BookFacadeImpl1()); bookCglib.addBook(); } }