Java 动态代理

动态代理是指在运行时,动态生成代理类。代理类的字节码将在运行时生成并载入当前的ClassLoader。

生成动态代理类的方法很多,如JDK自带的动态代理、CGLIB、Javassist或者ASM库。

JDK动态代理使用简单,它内置在JDK中,因此不需要引入第三方Jar包,但相对功能比较弱。CGLIB和Javassist都是高级的字节码生成库,总体性能比JDK自带的动态代理好,而且功能十分强大。ASM是低级的字节码生成工具,使用ASM已经近乎在使用Javabytecode编程,对开发人员要求较高,也是性能最好的一种动态代理生成工具。但ASM的使用过于繁琐,而且性能也没有数量级的提升,与CGLIB等高级字节码生成工具相比,ASM程序的可维护性也较差。

JDK实现

1、步骤

1)通过实现InvocationHandler接口创建自己的调用处理器
2)通过为Proxy类指定ClassLoader对象和一组interface来创建动态代理类
3)通过反射机制获得动态代理类的构造函数,其唯一参数类型是调用处理器接口类型
4)通过构造函数创建动态代理类实例,构造时调用处理器对象作为参数被传入

2、创建代理

//InvocationHandlerImpl 实现了InvocationHandler接口,并能实现方法调用从代理类到委托类的分派转发
//其内部通常包含指向委托类实例的引用,用于真正执行分派转发过来的方法调用
InvocationHandler handler = new InvocaitonHandlerImpl(..);

//通过Proxy为包括Interface接口在内的一组接口动态创建代理类的对象
Class clazz = Proxy.getProxyClass(classLoader,new Class[]{Interface.class,...});

//通过反射从生成的类对象获得构造函数对象
Constructor constructor = clazz.getConstructor(new Class[]{InvocationHandler.class});

//通过构造函数对象创建动态代理类实例
Interface Proxy = (Interface)constructor.newInstance(new Object[]{handler});

//Proxy类的静态方法newProxyInstance对上面具体步骤的后三步做了封装,简化了动态代理对象的获取过程。
//InvocationHandlerImpl实现了InvocaitonHandler接口,并能实现方法调用从代理类到委托类的分派转发
InvocaitonHandler handler = new InvocationHandlerImpl(..);
//通过Proxy直接创建动态代理类实例

Interface proxy = (Interface)Proxy.newProxyInstance(classLoader,new Class[]{Interface.class},handler);

3、代码

/**
 * 接口
 * @author Emily
 *
 */
public interface IDBQuery {
    String request();
}

/**
 * 真实的实现类,具体的目标对象
 * @author Emily
 *
 */
public class DBQuery implements IDBQuery {

    public DBQuery(){
        try {
            Thread.sleep(1000); //可能包含数据库连接等耗时操作
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    @Override
    public String request() {
        return "request string";
    }

}

/**
 * JDK动态代理的实现类
 * @author Emily
 *
 */
public class JdkDbQueryHandler implements InvocationHandler{

    IDBQuery real = null; //主题接口

	/**
	 * 生成Handler
	 */
	@Override
	public Object invoke(Object proxy, Method method, Object[] args)
            throws Throwable {
        if(real == null)
            real = new DBQuery(); //如果是第一次调用,则生成真实对象
        return real.request();  //使用真实主题完成实际的操作
    }

    /**
     * 利用Handler生成动态代理对象
     * @return
     */
    public static IDBQuery createJdkProxy(){
        //根据指定的类加载器和接口以及截获器,返回代理类的一个实例对象
        //ClassLoader loader :指定被代理对象的类加载器
        //Class[] Interfaces :指定被代理对象所以事项的接口
        //InvocationHandler h :指定需要调用的InvocationHandler对象
        IDBQuery jdkProxy = (IDBQuery) Proxy.newProxyInstance(ClassLoader.getSystemClassLoader(), new Class[]{IDBQuery.class}, new JdkDbQueryHandler());
        return jdkProxy;
    }

}

执行过程:
在这里插入图片描述
如图,目标对象的方法调用被Proxy拦截,在InvocationHandler中的回调方法中通过反射调用,这种动态代理的方法实现了对类的方法的运行时修改。

JDK动态代理有个缺点,那就是不能对类进行代理,只能对接口进行代理,如果我们的类没有实现任何接口,那么就不能使用这种方式进行动态代理(因为$Proxy()这个类继承了Proxy,Java的集成不允许出现多个父类)。准确的说这不应该是缺点,一个良好的系统,每个类都应该有一个接口与之对应。针对接口编程。

CGLIB实现

package com.ltt.dynamic;

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 Emily
 *
 */
public class CglibDbQueryInterceptor implements MethodInterceptor{
    IDBQuery real = null;

    /**
     * 处理代理逻辑的切入类
     */
    @Override
    public Object intercept(Object arg0, Method arg1, Object[] arg2,
            MethodProxy arg3) throws Throwable {
        if (real == null) {    //代理类 的内部逻辑
            real = new DBQuery();
            return real.request();
        }
        return null;
    }

    /**
     * 生成动态代理
     * @return
     */
    public static IDBQuery createCglibProxy(){
        Enhancer enhancer = new Enhancer();
        //指定切入器,定义代理类逻辑
        enhancer.setCallback(new CglibDbQueryInterceptor());
        //指定实现的接口
        enhancer.setInterfaces(new Class[]{IDBQuery.class});

        IDBQuery cglibProxy = (IDBQuery) enhancer.create();
        return cglibProxy;
    }

}

Javassist实现

一种是使用代理工厂创建,另一种通过使用动态代码创建。使用代理工厂创建时,方法与CGLIB类似,也需要实现一个用于代理逻辑处理的Handler:例如createJavassistDynProxy();使用动态代码创建,生成字节码,这种方式可以非常灵活,甚至可以在运行时生成业务逻辑,如createJavassistBytecodeDynamicProxy()方法。

/**
 * Javassist动态代理
 * @author Emily
 *
 */
public class JavassistDynDbQueryHandler implements MethodHandler{

    IDBQuery real = null;

    /*
     * (non-Javadoc) 实现用于代理逻辑处理的Handler
     * @see javassist.util.proxy.MethodHandler#invoke(java.lang.Object, java.lang.reflect.Method, java.lang.reflect.Method, java.lang.Object[])
     */
    @Override
    public Object invoke(Object arg0, Method arg1, Method arg2, Object[] arg3)
            throws Throwable {
        if (real == null) 
            real = new DBQuery();
        return real.request();

    }

    /**
     * 创建动态代理
     * @return
     * @throws Exception
     */
    public static IDBQuery createJavassistDynProxy() throws Exception{
        ProxyFactory proxyFactory = new ProxyFactory();
        proxyFactory.setInterfaces(new Class[]{IDBQuery.class});  //指定接口
        Class proxyClass = proxyFactory.createClass();
        IDBQuery javassistProxy = (IDBQuery) proxyClass.newInstance(); //设置Handler处理器
        ((ProxyObject) javassistProxy).setHandler(new JavassistDynDbQueryHandler());
        return javassistProxy;
    }

    /**
     * 运行时生成业务逻辑
     * @return
     * @throws Exception
     */
    public static IDBQuery createJavassistBytecodeDynamicProxy() throws Exception{
        ClassPool mPool = new ClassPool(true);
        //定义类名
        CtClass mCtc = mPool.makeClass(IDBQuery.class.getName() + "JavaassistBytecodeProxy");
        //需要实现接口
        mCtc.addInterface(mPool.get(IDBQuery.class.getName()));
        //添加构造函数
        mCtc.addConstructor(CtNewConstructor.defaultConstructor(mCtc));

        //添加类的字段信息,使用动态Java代码
        mCtc.addField(CtField.make("public " + IDBQuery.class.getName() + " real;", mCtc));
        String dbQueryname = DBQuery.class.getName();
        //添加方法,这里使用动态Java代码指定内部逻辑
        mCtc.addMethod(CtNewMethod.make("public String request() { if(real==null) real = new " +dbQueryname+"(); return real.request();}", mCtc));

        //基于以上信息,生成动态类
        Class pc = mCtc.toClass();
        //生成动态类的实例
        IDBQuery bytecodeProxy = (IDBQuery) pc.newInstance();
        return bytecodeProxy;
    }

}

三种动态代理:JDK动态代理创建速度优于CGLIB动态代理,但是在函数的调用性能上远不如CGLIB和Javassist。故CGLIB和Javassist整体性能上比JDK动态代理好。

性能比较:

Create JDK Proxy: 13 ms  
Create CGLIB Proxy: 217 ms  
Create JAVAASSIST Proxy: 99 ms  
Create JAVAASSIST Bytecode Proxy: 168 ms  
================  
Run JDK Proxy: 2224 ms, 634,022 t/s  
Run CGLIB Proxy: 1123 ms, 1,255,623 t/s  
Run JAVAASSIST Proxy: 3212 ms, 438,999 t/s  
Run JAVAASSIST Bytecode Proxy: 206 ms, 6,844,977 t/s   
----------------  
Run JDK Proxy: 2169 ms, 650,099 t/s  
Run CGLIB Proxy: 1059 ms, 1,331,506 t/s  
Run JAVAASSIST Proxy: 3328 ms, 423,697 t/s  
Run JAVAASSIST Bytecode Proxy: 202 ms, 6,980,521 t/s   
----------------  
Run JDK Proxy: 2174 ms, 648,604 t/s  
Run CGLIB Proxy: 1032 ms, 1,366,342 t/s  
Run JAVAASSIST Proxy: 3119 ms, 452,088 t/s  
Run JAVAASSIST Bytecode Proxy: 207 ms, 6,811,910 t/s  
----------------

Javassist字节码最快,CGLIB次之,是JDK的两倍。

它们都是通过字节码生成来实现动态代理的。只不过是它们生成的字节码不一样,像JDK,CGLIB都考虑了很多因素,以及继承或包装了自己的一些类,所以生成的字节码非常大,而我们很多时候用不上这些,手工生成的字节码非常小(Javassist是手工生成的字节码),所以速度快。

另外,ASM也是手工生成的字节码,速度也很快,但是它没有一个数量级,通常情况下选用Javassist生成字节码的方式。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值