首页 > 代码库 > java动态代理原理及解析

java动态代理原理及解析

代理:设计模式

代理模式是一种常用的设计模式,其目的就是为其他对象提供一个代理以控制对某个真实对象的访问。代理类负责为委托类预处理消息,过滤消息并转发消息,以及进行消息被委托类执行后的后续处理。

技术分享

通过代理层这一中间层,有效的控制对于真实委托类对象的直接访问,同时可以实现自定义的控制策略(Spring的AOP机制),设计上获得更大的灵活性。

java动态代理的类和接口(jdk1.6源码)

1,java.lang.reflect.Proxy:动态代理机制的主类,提供一组静态方法为一组接口动态的生成对象和代理类。

// 方法 1: 该方法用于获取指定代理对象所关联的调用处理器
public static InvocationHandler getInvocationHandler(Object proxy) 

// 方法 2:该方法用于获取关联于指定类装载器和一组接口的动态代理类的类对象
public static Class<?> getProxyClass(ClassLoader loader, 
Class<?>... interfaces)

// 方法 3:该方法用于判断指定类对象是否是一个动态代理类
public static boolean isProxyClass(Class<?> cl) 

// 方法 4:该方法用于为指定类装载器、一组接口及调用处理器生成动态代理类实例
public static Object newProxyInstance(ClassLoader loader,
 Class<?>[] interfaces,InvocationHandler h)

2,java.lang.reflect.InvocationHandler:调用处理器接口,自定义invokle方法,用于实现对于真正委托类的代理访问。

/**
 该方法负责集中处理动态代理类上的所有方法调用。
 第一个参数既是代理类实例,
 第二个参数是被调用的方法对象
 第三个方法是调用参数。
 调用处理器根据这三个参数进行预处理或分派到委托类实例上发射执行
*/
public Object invoke(Object proxy, Method method, Object[] args)
    throws Throwable;

3,java.lang.ClassLoader:类装载器类,将类的字节码装载到 Java 虚拟机(JVM)中并为其定义类对象,然后该类才能被使用。Proxy类与普通类的唯一区别就是其字节码是由 JVM 在运行时动态生成的而非预存在于任何一个 .class 文件中
每次生成动态代理类对象时都需要指定一个类装载器对象:newProxyInstance()方法第一个参数

动态代理机制

java动态代理创建对象的过程为如下步骤:
1,通过实现 InvocationHandler 接口创建自己的调用处理器;

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

2,通过为 Proxy 类指定 ClassLoader 对象和一组 interface 来创建动态代理类;

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

3,通过反射机制获得动态代理类的构造函数,其唯一参数类型是调用处理器接口类型;

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

4,通过构造函数创建动态代理类实例,构造时调用处理器对象作为参数被传入。

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

为了简化对象创建过程,Proxy类中的newProxyInstance方法封装了2~4,只需两步即可完成代理对象的创建。

// InvocationHandlerImpl 实现了 InvocationHandler 接口,并能实现方法调用从代理类到委托类的分派转发
InvocationHandler handler = new InvocationHandlerImpl(..); 

// 通过 Proxy 直接创建动态代理类实例
Interface proxy = (Interface)Proxy.newProxyInstance( classLoader, 
     new Class[] { Interface.class }, 
     handler );

动态代理的注意点:
1,包:代理接口是public,则代理类被定义在顶层包(package为空),否则(default),代理类被定义在该接口所在包,

技术分享

2,生成的代理类为public final,不能被继承,

3,类名:格式是“$ProxyN”,N是逐一递增的数字,代表Proxy被第N次动态生成的代理类,要注意,对于同一组接口(接口的排列顺序也相同),不会重复创建动态代理类,而是返回一个先前已经创建并缓存了的代理类对象。提高了效率。

技术分享

4,类继承关系:

技术分享
Proxy 类是它的父类,这个规则适用于所有由 Proxy 创建的动态代理类。(也算是java动态代理的一处缺陷,java不支持多继承,所以无法实现对class的动态代理,只能对于Interface的代理)而且该类还实现了其所代理的一组接口,这就是为什么它能够被安全地类型转换到其所代理的某接口的根本原因。

5,代理类的根类 java.lang.Object 中有三个方法也同样会被分派到调用处理器的 invoke 方法执行,它们是 hashCode,equals 和 toString,
代码在反编译中
技术分享

一个动态代理的demo

import java.lang.reflect.InvocationHandler;  
import java.lang.reflect.Method;  
import java.lang.reflect.Proxy;  

public class HelloServiceProxy implements InvocationHandler {  


    private Object target;    
    /**  
     * 绑定委托对象并返回一个【代理占位】 
     * @param target 真实对象 
     * @return  代理对象【占位】 
     */    
    public  Object bind(Object target, Class[] interfaces) {    
        this.target = target;    
        //取得代理对象    
       return Proxy.newProxyInstance(target.getClass().getClassLoader(),    
                target.getClass().getInterfaces(), this);
    }    

    @Override    
    /** 
     * 同过代理对象调用方法首先进入这个方法. 
     * @param proxy --代理对象 
     * @param method -- 方法,被调用方法. 
     * @param args -- 方法的参数 
     */  
    public Object invoke(Object proxy , Method method, Object[] args) throws Throwable {    
        System.err.println("############我是JDK动态代理################");  
        Object result = null;    
        //反射方法前调用  
        System.err.println("我准备说hello。");    
        //反射执行方法  相当于调用target.sayHelllo;  
        result=method.invoke(target, args);  
        //反射方法后调用.  
        System.err.println("我说过hello了");    
        return result;    
    }    
} 

其中,bind方法中的newProxyInstanc方法,就是生成一个代理对象,第一个参数是类加载器,第二个参数是真实委托对象所实现的的接口(代理对象挂在那个接口下),第三个参数this代表当前HelloServiceProxy类,换句话说是使用HelloServiceProxy作为对象的代理。

invoke方法有三个参数:第一个proxy是代理对象,第二个是当前调用那个方法,第三个是方法的参数。

public class ProxyTest {  

    public static void main(String[] args) {   
        HelloServiceProxy proxy = new HelloServiceProxy();  
        HelloService service = new HelloServiceImpl();  
        //绑定代理对象。  
        service = (HelloService) proxy.bind(service, new Class[] {HelloService.class});  
        //这里service经过绑定,就会进入invoke方法里面了。  
        service.sayHello("张三");  
    }  
}  

测试结果:

############我是JDK动态代理################  
我准备说hello。  
hello 张三  
我说过hello了

源码跟踪

Proxy 类

// 映射表:用于维护类装载器对象到其对应的代理类缓存
private static Map loaderToCache = new WeakHashMap(); 

// 标记:用于标记一个动态代理类正在被创建中
private static Object pendingGenerationMarker = new Object(); 

// 同步表:记录已经被创建的动态代理类类型,主要被方法 isProxyClass 进行相关的判断
private static Map proxyClasses = Collections.synchronizedMap(new WeakHashMap()); 

// 关联的调用处理器引用
protected InvocationHandler h;

Proxy 静态方法 newProxyInstance

public static Object newProxyInstance(ClassLoader loader, 
            Class<?>[] interfaces, 
            InvocationHandler h) 
            throws IllegalArgumentException { 

    // 检查 h 不为空,否则抛异常
    if (h == null) { 
        throw new NullPointerException(); 
    } 

    // 获得与制定类装载器和一组接口相关的代理类类型对象
    /*
     * Look up or generate the designated proxy class.
     */
        Class<?> cl = getProxyClass0(loader, interfaces); 

    // 通过反射获取构造函数对象并生成代理类实例
    /*
     * Invoke its constructor with the designated invocation handler.
     */
    try {
            final Constructor<?> cons = cl.getConstructor(constructorParams);
            final InvocationHandler ih = h;
            SecurityManager sm = System.getSecurityManager();
            if (sm != null && ProxyAccessHelper.needsNewInstanceCheck(cl)) {
                // create proxy instance with doPrivilege as the proxy class may
                // implement non-public interfaces that requires a special permission
                return AccessController.doPrivileged(new PrivilegedAction<Object>() {
                    public Object run() {
                        return newInstance(cons, ih);
                    }
                });
            } else {
                return newInstance(cons, ih);
            }
    } catch (NoSuchMethodException e) {
        throw new InternalError(e.toString());
    } 
    }

private static Object newInstance(Constructor<?> cons, InvocationHandler h) {
        try {
            return cons.newInstance(new Object[] {h} );
        } catch (IllegalAccessException e) {
            throw new InternalError(e.toString());
        } catch (InstantiationException e) {
            throw new InternalError(e.toString());
        } catch (InvocationTargetException e) {
            Throwable t = e.getCause();
            if (t instanceof RuntimeException) {
                throw (RuntimeException) t;
            } else {
                throw new InternalError(t.toString());
            }
        }
    }

动态代理真正的关键是在 getProxyClass0 方法,

getProxyClass0方法分析

方法分为四个步骤:
1,对这组接口进行一定程度的安全检查
检查接口类对象是否对类装载器可见并且与类装载器所能识别的接口类对象是完全相同的,还会检查确保是 interface 类型而不是 class 类型。

技术分享

2,从 loaderToCache 映射表中获取以类装载器对象为关键字所对应的缓存表,如果不存在就创建一个新的缓存表并更新到 loaderToCache。
loaderToCache存放键值对(接口名字列表,动态生成的代理类的类对象引用)。当代理类正在被创建时它会临时保存(接口名字列表,pendingGenerationMarker)。标记 pendingGenerationMarke 的作用是通知后续的同类请求(接口数组相同且组内接口排列顺序也相同)代理类正在被创建,请保持等待直至创建完成。

/*
     * Find or create the proxy class cache for the class loader.
     */
    Map cache;
    synchronized (loaderToCache) {
        cache = (Map) loaderToCache.get(loader);
        if (cache == null) {
        cache = new HashMap();
        loaderToCache.put(loader, cache);
        }
     }
。。。。。
do { 
    // 以接口名字列表作为关键字获得对应 cache 值
    Object value = cache.get(key); 
    if (value instanceof Reference) { 
        proxyClass = (Class) ((Reference) value).get(); 
    } 
    if (proxyClass != null) { 
        // 如果已经创建,直接返回
        return proxyClass; 
    } else if (value == pendingGenerationMarker) { 
        // 代理类正在被创建,保持等待
        try { 
            cache.wait(); 
        } catch (InterruptedException e) { 
        } 
        // 等待被唤醒,继续循环并通过二次检查以确保创建完成,否则重新等待
        continue; 
    } else { 
        // 标记代理类正在被创建
        cache.put(key, pendingGenerationMarker); 
        // break 跳出循环已进入创建过程
        break; 
} while (true);

3,动态创建代理类的class对象

/**
         * Choose a name for the proxy class to generate.
         */
        long num;
        synchronized (nextUniqueNumberLock) {
            num = nextUniqueNumber++;
        }
        String proxyName = proxyPkg + proxyClassNamePrefix + num;
        /*
         * Verify that the class loader hasn‘t already
         * defined a class with the chosen name.
         */

        // 动态地生成代理类的字节码数组
        byte[] proxyClassFile = ProxyGenerator.generateProxyClass(
            proxyName, interfaces);
        try {
        // 动态地定义新生成的代理类
            proxyClass = defineClass0(loader, proxyName,
            proxyClassFile, 0, proxyClassFile.length);
        } catch (ClassFormatError e) {
            /*
             * A ClassFormatError here means that (barring bugs in the
             * proxy class generation code) there was some other
             * invalid aspect of the arguments supplied to the proxy
             * class creation (such as virtual machine limitations
             * exceeded).
             */
            throw new IllegalArgumentException(e.toString());
        }
        // 把生成的代理类的类对象记录进 proxyClasses 表
        proxyClasses.put(proxyClass, null);             

首先根据规则(接口public与否),生成代理类的名称,$ProxyN格式,然后动态生成代理类。
所有的代码生成的工作都由 ProxyGenerator 所完成了,该类在rt.jar中,需要反编译

public static byte[] generateProxyClass(final String name,  
                                           Class[] interfaces)  
   {  
       ProxyGenerator gen = new ProxyGenerator(name, interfaces);  
    // 这里动态生成代理类的字节码,由于比较复杂就不进去看了  
       final byte[] classFile = gen.generateClassFile();  

    // 如果saveGeneratedFiles的值为true,则会把所生成的代理类的字节码保存到硬盘上  
       if (saveGeneratedFiles) {  
           java.security.AccessController.doPrivileged(  
           new java.security.PrivilegedAction<Void>() {  
               public Void run() {  
                   try {  
                       FileOutputStream file =  
                           new FileOutputStream(dotToSlash(name) + ".class");  
                       file.write(classFile);  
                       file.close();  
                       return null;  
                   } catch (IOException e) {  
                       throw new InternalError(  
                           "I/O exception saving generated file: " + e);  
                   }  
               }  
           });  
       }  

    // 返回代理类的字节码  
       return classFile;  
   }

4,代码生成过程进入结尾部分,根据结果更新缓存表,如果成功则将代理类的类对象引用更新进缓存表,否则清楚缓存表中对应关键值,最后唤醒所有可能的正在等待的线程。

finally {
        /*
         * We must clean up the "pending generation" state of the proxy
         * class cache entry somehow.  If a proxy class was successfully
         * generated, store it in the cache (with a weak reference);
         * otherwise, remove the reserved entry.  In all cases, notify
         * all waiters on reserved entries in this cache.
         */
        synchronized (cache) {
        if (proxyClass != null) {
            cache.put(key, new WeakReference(proxyClass));
        } else {
            cache.remove(key);
        }
        cache.notifyAll();
        }
    }
    return proxyClass;

InvocationHandler解析

代码参考:http://rejoy.iteye.com/blog/1627405
通过getProxyClass0方法中生成具体的class文件的过程,定义path,讲class文件写到指定的磁盘中,反编译生成的代理class文件。
发现在静态代码块中获取了的方法有:Object中的equals方法、Object中的hashCode方法、Object中toString方法 , 以及invoke的接口方法。

后语

至此,JDK是动态生成代理类,并通过调用解析器,执行接口实现的方法的原理已经一目了然。动态代理加上反射,是很多框架的基础。比如Spring的AOP机制,自定义前置后置通知等控制策略,以及mybatis中的运用反射和动态代理来实现插件技术等等。

文章参考:
http://www.ibm.com/developerworks/cn/java/j-lo-proxy1/index.html
http://blog.csdn.net/ykzhen2015/article/details/50312651

<script type="text/javascript"> $(function () { $(‘pre.prettyprint code‘).each(function () { var lines = $(this).text().split(‘\n‘).length; var $numbering = $(‘
    ‘).addClass(‘pre-numbering‘).hide(); $(this).addClass(‘has-numbering‘).parent().append($numbering); for (i = 1; i <= lines; i++) { $numbering.append($(‘
  • ‘).text(i)); }; $numbering.fadeIn(1700); }); }); </script>

    java动态代理原理及解析