您的位置:首页 > 编程语言 > Java开发

JAVA动态代理的实现方式

2017-09-23 15:39 681 查看


Spring框架可以说是目前在JAVA平台使用最为广泛的框架之一,我甚至认为可以去掉之一。他最重要的两大基本功能是提供IoC容器和AOP概念的实现。之前有过一篇文章介绍过spring的AOP,请参考http://blog.csdn.net/zsh2050/article/details/44523073。spring的AOP是通过动态代理的方式实现的,在企业面试时,面试官也经常爱提问动态代理如何实现。我们就讲一讲动态代理。

讲动态代理之前,先简单讲一下静态代理。在静态代理的实现中,代理对象与被代理对象必须实现同一个接口。调用程序执行的是代理对象,构造代理对象是必须个给它一个被代理对象,在调用程序中,必须用共同实现的接口定义调用对象,将代理对象转换成共同接口对象。这种静态代理的方式,一个接口只服务于一种类型的对象,如果要代理的对象很多,程序规模较大,就会需要编写很多代理类,显然是不能胜任的。好在还有动态代理的实现方式。实现动态代理主要有两种方式,一是使用JDK原生API提供的方式,二是使用cglib字节码工程的方式。

JDK原生API方式

在JDK1.3之后加入了可协助开发动态代理功能的API,使用动态代理,不必为特定的对象和方法编写特定的代理对象,一个处理者(Handler)可服务于多个对象。首先,这个处理者的类必须实现java.lang.reflect.InvocationHandler接口。

以下面的HelloSpeaker类为例,要使用JDK原生API方式生成动态代理,必须先定义所要代理的接口。

Public interfaceIHello {

Public void hello(String name);

}

 

Public classHelloSpeaker implements IHello {

Public void hello(String name) 
{

     System.out.println(“Hello, ” + name);

}

}

 

如果要在方法hello()的前后打印日志,可以设计一个动态代理类LogHandler。

 

publicclass LogHandler implements InvocationHandler {

 privateLogger logger = Logger.getLogger(this.getClass().getName());

 privateObject delegate;

 publicObject bind(Object delegate) {

       this.delegate =delegate;

       returnProxy.newProxyInstance(delegate.getClass().getClassLoader(),

                                     delegate.getClass().getInterfaces(),this);

 }

 publicObject invoke(Object proxy, Method method, Object[] args) throws Throwable {

        Object result = null;

        try {

               logger.info("methodstart..." + method);

               //执行业务逻辑

result = method.invoke(delegate, args);

               logger.info("methodend..." + method);

         } catch (Exception e){

               logger.info(e.getMessage());

         }

         return result;

  }

}

以上的动态代理代码中,最重要的概念是使用Proxy.newProxyInstance()建立一个代理对象,建立代理时必须告知要代理的接口。在每次操作代理对象时,执行
InvocationHandler

invoke()
方法,
invoke()
方法会传入被代理对象的方法名称与执行参数,实际上要执行业务逻辑的方法会交由method.invoke()
。method.invoke()传回的对象就是实际方法执行的结果。这样LogHandler不必服务于特定对象和接口,HelloSpeaker也不必插入任何有关日志的代码。

测试执行代码如下:

public static voidmain(String[] args) {

  LogHandler logHandler = new LogHandler();

  

  IHello hello = (IHello)logHandler.bind(new HelloSpeaker());

  

  hello.sayHello("cat");

 }

 

Cglib字节码工程的方式

使用JDK动态代理有一个前提,就是被代理对象必须实现一个接口,如果没有实现任何接口的类,可以使用Cglib生成动态代理。CGLib采用了非常底层的字节码技术,其原理是通过字节码技术为一个类创建子类,并在子类中采用方法拦截的技术拦截所有父类方法的调用,顺势织入横切逻辑。下面还以前面的HelloSpeaker类为例。

publicclass LogHandler implements
MethodInterceptor{

 privateLogger logger = Logger.getLogger(this.getClass().getName());

 privateObject delegate;

 public Object bind(Objectdelegate) {

       this.delegate =delegate;

       Enhancerenhancer = new Enhancer();

//设置需要创建子类的类

     enhancer.setSuperclass(this.delegate.getClass());//非final方法

     
//设置回调对象,该对象必须实现

     enhancer.setCallback(this);

     //通过字节码技术动态创建子类实例

     return enhancer.create();

 }

 //实现MethodInterceptor接口方法

 public Object intercept(Object obj, Methodmethod, Object[] args,

  MethodProxy proxy) throws Throwable {

       
Object result = null;

        try {

               logger.info("methodstart..." + method);

               //执行业务逻辑

result =
proxy.invokeSuper(delegate, args);

               logger.info("methodend..." + method);

         } catch (Exception e){

               logger.info(e.getMessage());

         }

         return result;

  }

}

以上代码用cglib的方式再次实现了LogHandler,除少数地方调用的API不同,其它基本相同。注意enhancer.setSuperclass(this.delegate.getClass()),cglib是使用动态生成子类的方式来实现动态代理的,
bc95
并覆盖其中方法实现增强,但因为采用的是继承, 所以只能对父类的非final方法进行代理,当然也不能对final修饰的类进行代理。

 

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

 

Spring就是通过这两种方式的动态代理实现AOP机制的,默认情况下,如果该类实现了接口,spring就用jdk动态代理,如果没有实现接口,spring就使用cglib。当然如果某类实现了接口,仍然可以通过配置,要求spring使用cglib。除了这两种,当然还有其它方式实现动态代理,比如javasist等,dubbo就是使了javasist。

 

早就想写一篇关于动态代理的文章,一直拖到现在,做事情还是要该做就做,毕竟时间这东西我们都不富裕。好吧2017年9月写了。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: