前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Java动态代理是什么?

Java动态代理是什么?

作者头像
对话、
发布2022-02-22 14:41:09
5260
发布2022-02-22 14:41:09
举报
文章被收录于专栏:Android-XjAndroid-Xj

动态代理的类和接口

  • Proxy:动态代理机制的主类,提供一组静态方法为一组接口动态的生成对象和代理类。
代码语言:javascript
复制
// 方法 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)
  • InvocationHandler:调用处理器接口,自定义invokle方法,用于实现对于真正委托类的代理访问。
代码语言:javascript
复制
/**
 该方法负责集中处理动态代理类上的所有方法调用。
 第一个参数既是代理类实例,
 第二个参数是被调用的方法对象
 第三个方法是调用参数。
 调用处理器根据这三个参数进行预处理或分派到委托类实例上发射执行
*/
public Object invoke(Object proxy, Method method, Object[] args)
    throws Throwable;
  • ClassLoader:类装载器类,将类的字节码装载到Java虚拟机(JVM)中并为其定义类对象,然后该类才能被使用。 Proxy类与普通类的唯一区别就是其字节码是由JVM在运行时动态生成的而非预存在于任何一个.class文件中。 每次生成动态代理类对象时都需要指定一个类装载器对象:newProxyInstance()方法第一个参数。

动态代理机制

java动态代理创建对象的过程为如下步骤:

  • 通过实现InvocationHandler接口创建自己的调用处理器
代码语言:javascript
复制
// InvocationHandlerImpl 实现了 InvocationHandler 接口,并能实现方法调用从代理类到委托类的分派转发
// 其内部通常包含指向委托类实例的引用,用于真正执行分派转发过来的方法调用
InvocationHandler handler = new InvocationHandlerImpl(..); 
  • 通过为Proxy类指定ClassLoader对象和一组interface来创建动态代理类
代码语言:javascript
复制
// 通过 Proxy 为包括 Interface 接口在内的一组接口动态创建代理类的类对象
Class clazz = Proxy.getProxyClass(classLoader, new Class[] { Interface.class, ... }); 
  • 通过反射机制获得动态代理类的构造函数,其唯一参数类型是调用处理器接口类型
代码语言:javascript
复制
// 通过反射从生成的类对象获得构造函数对象
Constructor constructor = clazz.getConstructor(new Class[] { InvocationHandler.class });
  • 通过构造函数创建动态代理类实例,构造时调用处理器对象作为参数被传入
代码语言:javascript
复制
// 通过构造函数对象创建动态代理类实例
Interface Proxy = (Interface)constructor.newInstance(new Object[] { handler });

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

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

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

动态代理的注意点

  • 包:代理接口是public,则代理类被定义在顶层包(package为空),否则(default),代理类被定义在该接口所在包,
  • 生成的代理类为public final的,不能被继承
  • 类名:格式是$ProxyNN是逐一递增的数字,代表Proxy被第N次动态生成的代理类,要注意对于同一组接口(接口的排列顺序也相同),不会重复创建动态代理类,而是返回一个先前已经创建并缓存了的代理类对象。提高了效率。
  • Proxy类是它的父类,这个规则适用于所有由Proxy创建的动态代理类。(也算是java动态代理的一处缺陷,java不支持多继承,所以无法实现对class的动态代理,只能对于Interface的代理)而且该类还实现了其所代理的一组接口,这就是为什么它能够被安全地类型转换到其所代理的某接口的根本原因。
  • 代理类的根类java.lang.Object中有三个方法也同样会被分派到调用处理器的invoke方法执行,它们是hashCodeequalstoString.

示例代码:

代码语言:javascript
复制
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方法中的newProxyInstance()方法,就是生成一个代理对象,第一个参数是类加载器,第二个参数是真实委托对象所实现的的接口(代理对象挂在那个接口下),第三个参数this代表当前HelloServiceProxy类,换句话说是使用HelloServiceProxy作为对象的代理。

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

代码语言:javascript
复制
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("张三");  
    }  
}  

源码跟踪

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

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

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

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

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());
        }
    }
}

动态代理真正的关键是在getProxyClass()方法,该方法主要分为四个步骤:

  • 对这组接口进行一定程度的安全检查 检查接口类对象是否对类装载器可见并且与类装载器所能识别的接口类对象是完全相同的,还会检查确保是interface类型而不是class类型。
  • loaderToCache()映射表中获取以类装载器对象为关键字所对应的缓存表,如果不存在就创建一个新的缓存表并更新到loaderToCache()loaderToCache()存放键值对(接口名字列表,动态生成的代理类的类对象引用)。当代理类正在被创建时它会临时保存(接口名字列表pendingGenerationMarker)。标记pendingGenerationMarke()的作用是通知后续的同类请求(接口数组相同且组内接口排列顺序也相同)代理类正在被创建,请保持等待直至创建完成。
代码语言:javascript
复制
/*
 * 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);
  • 动态创建代理类的class对象
代码语言:javascript
复制
/**
 * 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中,需要反编译。

代码语言:javascript
复制
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;  
}
  • 代码生成过程进入结尾部分,根据结果更新缓存表,如果成功则将代理类的类对象引用更新进缓存表,否则清楚缓存表中对应关键值,最后唤醒所有可能的正在等待的线程。
代码语言:javascript
复制
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;

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

本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 动态代理的类和接口
  • 动态代理机制
  • 动态代理的注意点
  • 源码跟踪
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档