本文核心主要参数动态代理和cglib;
在以前的文章中,有提及到动态代理,它要解决的就是,当我们的某些代码前面或后面都需要一些处理的时候,如写日志、事务控制、做agent、自动化代码跟踪等,此时会给你带来无限的方便,这是JVM级别的提供的一种代理机制,不过在这种机制下调用方法在JVM7出来前还没有invokeDynamic的时候,调用的效率是很低的,此时方法调用都是通过method的invoke去实现。
其基本原理是基于实现JVM提供的一个:
InvocationHandler的接口,实现一个方法叫:public Object invoke(Object proxyed, Method method, Object[] args);
创建类的时候,通过实例化这个类(这个类就是实现InvocationHandler的类),再将实际要实现的类的class放进去,通过Proxy来实例化。
以下为一段简单动态代理的实现代码(以下代码放入一个文件:DynamicProxy.java):
import java.lang.reflect.InvocationHandler; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.lang.reflect.Proxy; //定义了一个接口 interface Hello { public String getInfos1(); public String getInfos2(); public void setInfo(String infos1, String infos2); public void display(); } //定义它的实现类 class HelloImplements implements Hello { private volatile String infos1; private volatile String infos2; public String getInfos1() { return infos1; } public String getInfos2() { return infos2; } public void setInfo(String infos1, String infos2) { this.infos1 = infos1; this.infos2 = infos2; } public void display() { System.out.println("\t\t" + infos1 + "\t" + infos2); } } 定义AOP的Agent class AOPFactory implements InvocationHandler { private Object proxyed; public AOPFactory(Object proxyed) { this.proxyed = proxyed; } public void printInfo(String info, Object... args) { System.out.println(info); if (args == null) { System.out.println("\t空值。"); }else { for(Object obj : args) { System.out.println(obj); } } } public Object invoke(Object proxyed, Method method, Object[] args) throws IllegalArgumentException, IllegalAccessException, InvocationTargetException { System.out.println("\n\n====>调用方法名:" + method.getName()); Class<?>[] variables = method.getParameterTypes(); for(Class<?>typevariables: variables) { System.out.println("=============>" + typevariables.getName()); } printInfo("传入的参数为:", args); Object result = method.invoke(this.proxyed, args); printInfo("返回的参数为:", result); printInfo("返回值类型为:", method.getReturnType()); return result; } } //测试调用类 public class DynamicProxy { public static Object getBean(String className) throws InstantiationException, IllegalAccessException, ClassNotFoundException { Object obj = Class.forName(className).newInstance(); InvocationHandler handler = new AOPFactory(obj); return Proxy.newProxyInstance(obj.getClass().getClassLoader(), obj .getClass().getInterfaces(), handler); } @SuppressWarnings("unchecked") public static void main(String[] args) { try { Hello hello = (Hello) getBean("dynamic.HelloImplements"); hello.setInfo("xieyu1", "xieyu2"); hello.getInfos1(); hello.getInfos2(); hello.display(); } catch (Exception e) { e.printStackTrace(); } } }
OK,可以看看输出结果,此时的输出结果不仅仅有自己的Hello实现类的中打印结果,还有proxy代理中的结果,它可以捕获方法信息和入参数,也可以捕获返回结果,也可以操作方法,所以这种非侵入式编程本身就是侵入式的,呵呵!
好了,你会发现都有接口,有些时候写太多接口很烦,而且上面的调用性能的确不怎么样,除了JVM提供的动态代理,还有什么办法吗?有的,org的asm包可以动态修改字节码信息,也就是可以动态在内存中创建class类和修改class类信息;但是听起来貌似很复杂,cglib为我们包装了对asm的操作,整个ASM包的操作非常小,但是代码很精炼,很容易看懂。那么cglib实现的时候,就是通过创建一个类的子类,然后在调用时,子类方法肯定覆盖父类方法,然后子类在完成相关动作后,进行super的回调;
我们来看个例子(首先下载asm包,和cglib包,各个版本不同而不同,我使用的是asm-all-3.1.jar和cglib-2.2.jar):
下面的程序只需创建文件:CglibIntereceptor.java
import java.lang.reflect.Method; import net.sf.cglib.proxy.Enhancer; import net.sf.cglib.proxy.MethodInterceptor; import net.sf.cglib.proxy.MethodProxy; //创建一个类,用来做测试 class TestClass { public void doSome() { System.out.println("====>咿呀咿呀喂"); } } public class CglibIntereceptor { static class MethodInterceptorImpl implements MethodInterceptor { public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable { System.out.println(method); proxy.invokeSuper(obj, args); return null; } } public static void main(String[] args) { Enhancer enhancer = new Enhancer(); enhancer.setSuperclass(TestClass.class); enhancer.setCallback( new MethodInterceptorImpl() ); TestClass my = (TestClass)enhancer.create(); my.doSome(); } }
看看打印结果:
public void dynamic.TestClass.doSome()====>咿呀咿呀喂
//注意看黑色粗体标识出来的代码,首先要实现MethodInterceptor,然后实现方法intercept,内部使用invokeSuper来调用父类;下面的实例都是通过Enhancer 来完成的;细节的后续我们继续探讨,现在就知道这样可以使用,而spring的真正实现也是类似于此,只是spring对于cglib的使用做了其他的包装而已;大家可以去看看spring对事务管理器的源码即可了解真相。
下面问题来了,我们有些时候对某些方法不想去AOP,因为我认为只有需要包装的才去包装,就像事务管理器中切入的时候,我们一般会配置一个模式匹配,哪些类和那些方法才需要做AOP;那么cglib怎么实现的,它提供了一个CallbackFilter来实现这个机制。OK,我们来看一个CallbackFilter的实例:
以下代码创建文件:CglibCallBackFilter.java
import java.lang.reflect.Method; import net.sf.cglib.proxy.Callback; import net.sf.cglib.proxy.CallbackFilter; import net.sf.cglib.proxy.Enhancer; import net.sf.cglib.proxy.MethodInterceptor; import net.sf.cglib.proxy.MethodProxy; import net.sf.cglib.proxy.NoOp; class CallBackFilterTest { public void doOne() { System.out.println("====>1"); } public void doTwo() { System.out.println("====>2"); } } public class CglibCallBackFilter { static class MethodInterceptorImpl implements MethodInterceptor { public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable { System.out.println(method); return proxy.invokeSuper(obj, args); } } static class CallbackFilterImpl implements CallbackFilter { public int accept(Method method) {//返回1代表不会进行intercept的调用 return ("doTwo".equals(method.getName())) ? 1 : 0; } } public static void main(String[] args) { Callback[] callbacks = new Callback[] { new MethodInterceptorImpl(), NoOp.INSTANCE }; Enhancer enhancer = new Enhancer(); enhancer.setSuperclass(CallBackFilterTest.class); enhancer.setCallbacks( callbacks ); enhancer.setCallbackFilter( new CallbackFilterImpl()); CallBackFilterTest callBackFilterTest = (CallBackFilterTest)enhancer.create(); callBackFilterTest.doOne(); callBackFilterTest.doTwo(); } }
看下打印结果:
public void dynamic.CallBackFilterTest.doOne()====>1
====>2
可以看到只有方法1打印出了方法名,方法2没有,也就是方法2调用时没有调用:intercept来做AOP操作,而是直接调用的;可以看出,他上层有一个默认值,而callbacks里面设置了NoOp.INSTANCE,就代表了不做任何操作的一个实例,你应该懂了吧,就是当不做AOP的时候调用那种实例来运行,当需要AOP的时候调用那个实例来运行;怎么对应上的,它自己不知道,accept返回的是一个数组的下标,callbacks是一个数组,那么你猜猜是不是数组的下标呢,你自己换下位置就知道了,呵呵,是的,没错就是数组下标,不相信可以翻翻他的源码就知道了。
其实你可以看出cglib就是在修改字节码,貌似很方面,spring、Hibernate等也大量使用它,但是并不代表你可以大量使用它,尤其是在写业务代码的时候,只有写框架才可以适当考虑使用这些东西,spring的反射等相关一般都是初始化决定的,一般都是单例的,前面谈及到JVM时,很多JVM优化原则都是基于VM的内存结构不会发生变化,如果发生了变化,那么优化就会存在很多的问题了,其次无限制使用这个东西可能会使得VM的Perm Gen内存溢出。
最后我们看个实际应用中没啥用途,但是cglib实现的一些东西,java在基于接口、抽象类的情况下,实现了很多特殊的机制,而cglib可以将两个根本不想管的接口和类合并到一起来操作,这也是字节码的一个功劳,呵呵,它的原理就是在接口下实现了子类,并把其他两个作为回调的方法,即可实现,但是实际应用中这种用法很诡异,cglib中是使用:Mixin来创建,而并非Enhancer了。例子如下:
创建文件:CglibMixin.java
import net.sf.cglib.proxy.Mixin; interface Interface1 { public void doInterface1(); } interface Interface2 { public void doInterface2(); } class ImpletmentClass1 implements Interface1 { public void doInterface1() { System.out.println("===========>方法1"); } } class ImpletmentClass2 implements Interface2 { public void doInterface2() { System.out.println("===========>方法2"); } } public class CglibMixin { public static void main(String []args) { Class<?>[] interfaces = new Class[] { Interface1.class, Interface2.class }; Object[] implementObjs = new Object[] { new ImpletmentClass1(), new ImpletmentClass2()}; Object obj = Mixin.create(interfaces,implementObjs); Interface1 interface1 = (Interface1)obj; Interface2 interface2 = (Interface2)obj; interface1.doInterface1(); interface2.doInterface2(); } }
结果就不用打印了,上面有描述,主要是两个接口、两个实例,最终用一个对象完成了,传递过程中只有一个,比起传统意义上的多态更加具有多态的效果,呵呵,不过还是建议少用。
本文只是简单阐述框架级别动态代理和cglib的实现,后续会深入探讨一些cglib的实现细节和功能,以及如何在框架中抽象出模型出来。