理解JDK动态代理为什么必须要基于接口

目录
  • 1. 前言
  • 2. 一个简单的例子
    • 2.1. 定义接口
    • 2.2. 接口实现类
    • 2.3. 自定义 Handler
    • 2.4. 测试
    • 2.5. 输出结果
  • 3. 源码分析
    • 3.1. newProxyInstance() 方法
  • 4. 代理对象长啥样
    • 4.1. 代理对象长啥样
    • 4.2. $Proxy0 反编译
  • 5. JDK 动态代理为什么要有接口

1. 前言

JDK 动态代理的应用还是非常广泛的,例如在 Spring、MyBatis 以及 Feign 等很多框架中动态代理都被大量的使用,可以说学好 JDK 动态代理,对于我们阅读这些框架的底层源码还是很有帮助的

2. 一个简单的例子

在分析原因之前,我们先完整的看一下实现 JDK 动态代理需要几个步骤,首先需要定义一个接口

2.1. 定义接口

public interface Worker {
    void work();
}

2.2. 接口实现类

public class Programmer implements Worker {
    @Override
    public void work() {
        System.out.println("coding...");
    }
}

2.3. 自定义 Handler

自定义一个 Handler,实现 InvocationHandler 接口,通过重写内部的 invoke() 方法实现逻辑增强

public class WorkHandler implements InvocationHandler {
    private final Object target;
    public WorkHandler(Object target) {
        this.target = target;
    }
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        if (method.getName().equals("work")) {
            System.out.println("before work...");
            Object result = method.invoke(target, args);
            System.out.println("after work...");
            return result;
        }
        return method.invoke(target, args);
    }
}

2.4. 测试

在 main() 方法中进行测试,使用 Proxy 类的静态方法 newProxyInstance() 生成一个代理对象并调用方法

public class MainTest {
    public static void main(String[] args) {
        Programmer programmer = new Programmer();
        Worker worker = (Worker) Proxy.newProxyInstance(
                programmer.getClass().getClassLoader(),
                programmer.getClass().getInterfaces(),
                new WorkHandler(programmer));
        worker.work();
    }
}

2.5. 输出结果

before work...
coding...
after work...

3. 源码分析

既然是一个代理的过程,那么肯定存在原生对象和代理对象之分,下面我们查看源码中是如何动态的创建代理对象的过程。

上面例子中,创建代理对象调用的是 Proxy 类的静态方法 newProxyInstance(),查看一下源码

3.1. newProxyInstance() 方法

public class Proxy implements java.io.Serializable {
	protected InvocationHandler h;
	// 有参构造器,参数是 InvocationHandler
	protected Proxy(InvocationHandler h) {
        Objects.requireNonNull(h);
        this.h = h;
    }
	@CallerSensitive
	public static Object newProxyInstance(ClassLoader loader,
                                          Class<?>[] interfaces,
                                          InvocationHandler h)
		throws IllegalArgumentException {

		// 如果h为空直接抛出空指针异常,之后所有的单纯的判断null并抛异常,都是此方法
		Objects.requireNonNull(h);
		// 拷贝类实现的所有接口
    	final Class<?>[] intfs = interfaces.clone();
		// 获取当前系统安全接口
    	final SecurityManager sm = System.getSecurityManager();
    	if (sm != null) {
			// Reflection.getCallerClass 返回调用该方法的方法的调用类;loader:接口的类加载器
			// 进行包访问权限、类加载器权限等检查
			checkProxyAccess(Reflection.getCallerClass(), loader, intfs);
		}

		// 查找或生成指定的代理类
		Class<?> cl = getProxyClass0(loader, intfs);

		// 用指定的调用处理程序调用它的构造函数
		try {
			if (sm != null) {
				checkNewProxyPermission(Reflection.getCallerClass(), cl);
        	}
	   		// 获取代理类的构造函数对象
	    	// constructorParams是类常量,作为代理类构造函数的参数类型,常量定义如下:
	    	// private static final Class<?>[] constructorParams = { InvocationHandler.class };
			final Constructor<?> cons = cl.getConstructor(constructorParams);
			final InvocationHandler ih = h;
			if (!Modifier.isPublic(cl.getModifiers())) {
				AccessController.doPrivileged(new PrivilegedAction<Void>() {
            		public Void run() {
                		cons.setAccessible(true);
                        return null;
                	}
            });
		}
			// 根据代理类的构造函数对象来创建需要返回的代理类对象
			return cons.newInstance(new Object[]{h});
		} // 省略 catch......
	}
}
  • 在 checkProxyAccess() 方法中,进行参数验证
  • 在 getProxyClass0() 方法中,生成一个代理类 Class 或寻找已生成过的代理类的缓存
  • 通过 getConstructor() 方法获取生成的代理类的构造方法
  • 通过 newInstance() 方法,生成最终的代理对象

上面这个过程中,获取构造方法和生成代理对象都是利用的 Java 中的反射机制,而需要重点看的是生成代理类的方法 getProxyClass0()

3.1.1. getProxyClass0() 方法

private static Class<?> getProxyClass0(ClassLoader loader,
                                           Class<?>... interfaces) {

	// 接口数不得超过 65535 个,这么大,足够使用的了
	if (interfaces.length > 65535) {
		throw new IllegalArgumentException("interface limit exceeded");
	}
	// 如果缓存中有代理类了直接返回,否则将由代理类工厂ProxyClassFactory创建代理类
	return proxyClassCache.get(loader, interfaces);
}

如果缓存中已经存在了就直接从缓存中取,这里的 proxyClassCache 是一个 WeakCache 类型,如果缓存中目标 classLoader 和接口数组对应的类已经存在,那么返回缓存的副本。如果没有就使用 ProxyClassFactory 去生成 Class 对象

3.1.1.1. get() 方法

// key:类加载器;parameter:接口数组
public V get(K key, P parameter) {
	// 检查指定类型的对象引用不为空null。当参数为null时,抛出空指针异常
	Objects.requireNonNull(parameter);
	// 清除已经被 GC 回收的弱引用
	expungeStaleEntries();
	// 将ClassLoader包装成CacheKey, 作为一级缓存的key
	Object cacheKey = CacheKey.valueOf(key, refQueue);

	// 获取得到二级缓存
    ConcurrentMap<Object, Supplier<V>> valuesMap = map.get(cacheKey);
	// 没有获取到对应的值
	if (valuesMap == null) {
		ConcurrentMap<Object, Supplier<V>> oldValuesMap
                = map.putIfAbsent(cacheKey,
                                  valuesMap = new ConcurrentHashMap<>());
		if (oldValuesMap != null) {
                valuesMap = oldValuesMap;
        }
	}

	// 根据代理类实现的接口数组来生成二级缓存key
	Object subKey = Objects.requireNonNull(subKeyFactory.apply(key, parameter));
	// 通过subKey获取二级缓存值
	Supplier<V> supplier = valuesMap.get(subKey);
	Factory factory = null;
	// 这个循环提供了轮询机制, 如果条件为假就继续重试直到条件为真为止
	while (true) {
		if (supplier != null) {
			// 在这里supplier可能是一个Factory也可能会是一个CacheValue
			// 在这里不作判断, 而是在Supplier实现类的get方法里面进行验证
            V value = supplier.get();
            if (value != null) {
            	return value;
            }
        }
		if (factory == null) {
            factory = new Factory(key, parameter, subKey, valuesMap);
        }
		if (supplier == null) {
			// 到这里表明subKey没有对应的值, 就将factory作为subKey的值放入
        	supplier = valuesMap.putIfAbsent(subKey, factory);
           	if (supplier == null) {
                supplier = factory;
           	}
			// 否则, 可能期间有其他线程修改了值, 那么就不再继续给subKey赋值, 而是取出来直接用
            } else {
			// 期间可能其他线程修改了值, 那么就将原先的值替换
            if (valuesMap.replace(subKey, supplier, factory)) {
                supplier = factory;
			} else {
                supplier = valuesMap.get(subKey);
            }
		}
	}
}

很明显,重点关注下面代码

Object subKey = Objects.requireNonNull(subKeyFactory.apply(key, parameter));

3.1.1.1.1. apply() 方法

private static final class ProxyClassFactory
        implements BiFunction<ClassLoader, Class<?>[], Class<?>> {

	// 代理类的前缀名都以 $Proxy 开始
	private static final String proxyClassNamePrefix = "$Proxy";

	// 使用唯一的编号给作为代理类名的一部分,如 $Proxy0,$Proxy1 等
	private static final AtomicLong nextUniqueNumber = new AtomicLong();

	@Override
	public Class<?> apply(ClassLoader loader, Class<?>[] interfaces) {

   		Map<Class<?>, Boolean> interfaceSet = new IdentityHashMap<>(interfaces.length);
        for (Class<?> intf : interfaces) {

        	// 验证指定的类加载器(loader)加载接口所得到的Class对象(interfaceClass)是否与intf对象相同
			Class<?> interfaceClass = null;
            try {
				interfaceClass = Class.forName(intf.getName(), false, loader);
			} catch (ClassNotFoundException e) {
			}
			if (interfaceClass != intf) {
				throw new IllegalArgumentException(
                	intf + " is not visible from class loader");
			}

            // 验证该Class对象是不是接口
            if (!interfaceClass.isInterface()) {
				throw new IllegalArgumentException(
                	interfaceClass.getName() + " is not an interface");
			}

            // 验证该接口是否重复
            if (interfaceSet.put(interfaceClass, Boolean.TRUE) != null) {
            	throw new IllegalArgumentException(
                	"repeated interface: " + interfaceClass.getName());
            }
		}
	    // 声明代理类所在包
        String proxyPkg = null;
        int accessFlags = Modifier.PUBLIC | Modifier.FINAL;

        // 验证所有非公共的接口在同一个包内;公共的就无需处理
        for (Class<?> intf : interfaces) {
        	int flags = intf.getModifiers();
            if (!Modifier.isPublic(flags)) {
            	accessFlags = Modifier.FINAL;
                String name = intf.getName();
                int n = name.lastIndexOf('.');
				// 截取完整包名
                String pkg = ((n == -1) ? "" : name.substring(0, n + 1));
                if (proxyPkg == null) {
                	proxyPkg = pkg;
                } else if (!pkg.equals(proxyPkg)) {
                	throw new IllegalArgumentException(
                    	"non-public interfaces from different packages");
                }
        	}
		}
 		// 1.根据规则生成文件名
		if (proxyPkg == null) {
			/*如果都是public接口,那么生成的代理类就在com.sun.proxy包下如果报		java.io.FileNotFoundException: com\sun\proxy\$Proxy0.class
			(系统找不到指定的路径。)的错误,就先在你项目中创建com.sun.proxy路径*/
        	proxyPkg = ReflectUtil.PROXY_PACKAGE + ".";
		}
        long num = nextUniqueNumber.getAndIncrement();
	    // 代理类的完全限定类名,如 com.sun.proxy.$Proxy0.calss
        String proxyName = proxyPkg + proxyClassNamePrefix + num;

        // 2.生成代理的字节码数组
        byte[] proxyClassFile = ProxyGenerator.generateProxyClass(
                proxyName, interfaces, accessFlags);
        // 3.生成 Class
        try {
        	return defineClass0(loader, proxyName,
                                    proxyClassFile, 0, proxyClassFile.length);
        } catch (ClassFormatError e) {
            throw new IllegalArgumentException(e.toString());
        }
	}
}

在 apply() 方法中,主要做了下面 3 件事

  • 根据规则生成文件名
  • 利用 ProxyGenerator.generateProxyClass() 方法生成代理的字节码数组
  • 调用方法 defineClass0() 生成 Class

3.1.2. getConstructor() 和 newInstance() 方法

返回代理类的 Class 后的流程,获取构造方法和生成代理对象都是利用的 Java 中的反射机制

4. 代理对象长啥样

4.1. 代理对象长啥样

创建代理对象流程的源码分析完了,我们可以先通过 debug 来看看上面生成的这个代理对象究竟是个什么

和源码中看到的规则一样,是一个 Class 为 $Proxy0 的对象。再看一下代理对象的 Class 的详细信息

类的全限定类名是 com.sun.proxy.$Proxy0,在上面我们提到过,这个类是在运行过程中动态生成的

4.2. $Proxy0 反编译

看一下反编译后 $Proxy0.java 文件的内容,下面的代码中,我只保留了核心部分,省略了无关紧要的 equals()、toString()、hashCode() 方法的定义

public final class $Proxy0 extends Proxy implements Worker{
    public $Proxy0(InvocationHandler invocationhandler){
        super(invocationhandler);
    }
    public final void work(){
        try{
            super.h.invoke(this, m3, null);
            return;
        }catch(Error _ex) { }
        catch(Throwable throwable){
            throw new UndeclaredThrowableException(throwable);
        }
    }
    private static Method m3;
    static {
        try{
            m3 = Class.forName("com.hydra.test.Worker").getMethod("work", new Class[0]);
            //省略其他Method
        }//省略catch
    }
}

这个临时生成的代理类 $Proxy0 中主要做了下面的几件事

  • 在这个类的静态代码块中,通过 反射机制 初始化了多个静态方法 Method 变量,除了接口中的方法还有 equals()、toString()、hashCode() 这三个方法
  • 代理类 $Proxy0 继承了父类 Proxy,在其实例化的过程中会调用父类的构造器,而父类 Proxy 中的构造器中传入的 InvocationHandler 对象实际上是我们自定义的 WorkHandler 的实例。此时就可以调用 WorkHandler 的 invoke() 方法了
  • 同时,代理类 $Proxy0 也实现了自定义的接口 Worker,并重写了 work() 方法,在 work()方法内又调用了 InvocationHandler 的 invoke() 方法,也就是实际上调用了 WorkHandler 的 invoke() 方法

到这里,整体的流程就分析完了,我们可以用一张图来简要总结上面的过程

5. JDK 动态代理为什么要有接口

其实如果不看上面的分析,我们也应该知道,要扩展一个类有常见的两种方式,继承父类或实现接口。这两种方式都允许我们对方法的逻辑进行增强,但现在不是由我们自己来重写方法,而是要想办法让 JVM 去调用 InvocationHandler 中的 invoke() 方法,也就是说代理类需要和两个东西关联在一起

  • 被代理类
  • InvocationHandler

而 JDK 动态代理处理这个问题的方式是选择继承父类 Proxy,并把 InvocationHandler 保存在父类的对象中

public class Proxy implements java.io.Serializable {
    protected InvocationHandler h;

    protected Proxy(InvocationHandler h) {
        Objects.requireNonNull(h);
        this.h = h;
    }

    // ......
}

通过父类 Proxy 的构造方法,保存了创建代理对象过程中传进来的 InvocationHandler 的实例,使用 protected 修饰保证了它可以在子类中被访问和使用。

但是同时,因为 Java 是单继承的,因此在代理类 $Proxy0 继承了 Proxy 后,其只能通过实现目标接口的方式来实现方法的扩展,达到我们增强目标方法逻辑的目的

以上为个人经验,希望能给大家一个参考,也希望大家多多支持我们。

(0)

相关推荐

  • 浅谈Java 代理机制

    目录 一.常规编码方式 二.代理模式概述 三.静态代理 3.1.什么是静态代理 3.2.代码示例 四.Java 字节码生成框架 五.什么是动态代理 六.JDK 动态代理机制 6.1.使用步骤 6.2.代码示例 七.CGLIB 动态代理机制 7.1.使用步骤 7.2.代码示例 八.什么情况下使用动态代理 九.静态代理和动态代理对比 十.总结 一.常规编码方式 在学习代理之前,先回顾以下我们的常规编码方式:所有 interface 类型的变量总是通过向上转型并指向某个实例的. 1)首先,定义一个接口

  • JDK动态代理,代理接口没有实现类,实现动态代理方式

    JDK代理,代理的是接口,那么笔者想一想,既然代理的是接口,那如果没有实现类怎么办,能不能代理.答案是可以的,Mybatis就是这样的. Mybatis使用JDK动态代理来实现Mapper接口,事先保存好Mapper接口,和接口声明的方法,返回值,参数类型,然后代理类的方法调用的时候使用MapperMethod这个事先放入方法缓存里的对象来真实调用功能. 笔者极度简化了一下代码: 被代理的接口: public interface Subject2 { String selectById(); }

  • jdk动态代理和cglib动态代理详解

    目录 静态代理 基于继承的方式实现静态代理 基于聚合的方式实现静态代理 继承与聚合方式实现的静态代理对比 动态代理 JDK动态代理 如何实现一个HashMap的动态代理类? Cglib动态代理 JDK与Cglib动态代理对比? 动态代理和静态代理的区别? Spring如何选择两种代理模式的? 总结 如上图,代理模式可分为动态代理和静态代理,我们比较常用的有动态代理中的jdk动态代理和Cglib代理,像spring框架.hibernate框架中都采用了JDK动态代理,下面将结合代码阐述两种代理模式

  • jdk动态代理使用实例详解

    目录 前言 为什么需要代理 java中常用的代理模式 一.JDK 动态代理 二.cglib静态代理 三.spring中代理的使用 总结 前言 代理模式不管是JDK,spring框架,还是日常的开发中几乎可以说无处不在,下面一张简图描述了代理这个模式的业务场景,有过一些开发经验的同学对这张图应该不难理解: 为什么需要代理 1.原有功能增强 举例来说,当现有的类的代码只能满足一些基本的功能,而这些功能满足不了新需求,但又不能改动以前的代码,这时候就可以考虑使用代理,通过代理类,扩展原有类的功能,客户

  • Java 动态代理的多种实现方式

    一.动态代理简介 优势:在不修改源码的情况下,对目标方法进行相应的增强. 作用:完成程序功能之间的松耦合. 二.动态代理的多种实现 JDK代理:基于接口的动态代理技术(缺点,目标对象必须有接口,如果没有接口,则无法完成动态代理的实现) cglib代理:基于父类的动态代理技术 两者的区别如图所示: 1. 基于JDK的实现 目标接口类: public interface TargetInterface { public void save(); public void print(String st

  • 理解JDK动态代理为什么必须要基于接口

    目录 1. 前言 2. 一个简单的例子 2.1. 定义接口 2.2. 接口实现类 2.3. 自定义 Handler 2.4. 测试 2.5. 输出结果 3. 源码分析 3.1. newProxyInstance() 方法 4. 代理对象长啥样 4.1. 代理对象长啥样 4.2. $Proxy0 反编译 5. JDK 动态代理为什么要有接口 1. 前言 JDK 动态代理的应用还是非常广泛的,例如在 Spring.MyBatis 以及 Feign 等很多框架中动态代理都被大量的使用,可以说学好 JD

  • Java实现JDK动态代理的原理详解

    目录 概念 案例 静态代理 JDK动态代理模式 原理分析 真相大白 概念 代理:为控制A对象,而创建出新B对象,由B对象代替执行A对象所有操作,称之为代理.一个代理体系建立涉及到3个参与角色:真实对象(A),代理对象(B),客户端. 其中的代理对象(B)起到中介作用,连通真实对象(A)与客户端,如果进一步拓展,代理对象可以实现更加复杂逻辑,比如对真实对象进行访问控制. 案例 需求:员工业务层接口调用save需要admin权限,调用list不需要权限,没权限调用时抛出异常提示. 静态代理 /**

  • JDK动态代理与CGLib动态代理的区别对比

    案例: public interface ForumService { void removeTopic(int topicId); void removeForum(int forumId); } 对相关方法进行性能监控 public class ForumServiceImpl implements ForumService { public void removeTopic(int topicId) { // PerformanceMonitor.begin("com.hand.proxy

  • 详解Java JDK动态代理

    今天来看看Java的另一种代理方式--JDK动态代理 我们之前所介绍的代理方式叫静态代理,也就是静态的生成代理对象,而动态代理则是在运行时创建代理对象.动态代理有更强大的拦截请求功能,因为可以获得类的运行时信息,可以根据运行时信息来获得更为强大的执(骚)行(操)力(作). 我们还是以上一个例子为例,这里的IStars接口和Stars类都不需要修改,只需要修改代理类. 创建JDK动态代理需要先实现InvocationHandler接口,并重写其中的invoke方法,具体步骤如下: 1. 创建一个类

  • 深入理解java动态代理的两种实现方式(JDK/Cglib)

    什么是代理模式? 代理模式:在调用处不直接调用目标类进行操作,而是调用代理类,然后通过代理类来调用目标类进行操作.在代理类调用目标类的前后可以添加一些预处理和后处理操作来完成一些不属于目标类的功能. 为什么要使用代理模式? 通过代理模式可以实现对目标类调用的控制.在目标类调用前/后进行一些不属于目标类的操作,如:数据验证.预处理.后处理.异常处理等 什么是静态代理什么是动态代理? 静态代理:代理类只能实现对"特定接口的实现类"进行代理 动态代理:代理类可以实现对多种类的代理 jdk代理

  • Spring AOP注解失效的坑及JDK动态代理

    @Transactional @Async等注解不起作用 之前很多人在使用Spring中的@Transactional, @Async等注解时,都多少碰到过注解不起作用的情况. 为什么会出现这些情况呢?因为这些注解的功能实际上都是Spring AOP实现的,而其实现原理是通过代理实现的. JDK动态代理 以一个简单的例子理解一下JDK动态代理的基本原理: //目标类接口 public interface JDKProxyTestService { void run(); } //目标类 publ

  • Java JDK动态代理(AOP)用法及实现原理详解

    Java-JDK动态代理(AOP)使用及实现原理分析 第一章:代理的介绍 介绍:我们需要掌握的程度 动态代理(理解) 基于反射机制 掌握的程度: 1.什么是动态代理? 2.动态代理能够做什么? 后面我们在用Spirng和Mybatis的时候,要理解怎么使用的. 1.什么是代理? 代理,在我们日常生活之中就有体现,代购,中介,换ip,商家等等. 比如有一家美国的大学,可以对全世界招生.留学中介(代理 ) 留学中介(代理):帮助这家美国的学校招生,中介是学校的代理中介是代替学校完成招生功能 代理特点

  • Java JDK动态代理的基本原理详细介绍

    JDK动态代理详解 本文主要介绍JDK动态代理的基本原理,让大家更深刻的理解JDK Proxy,知其然知其所以然.明白JDK动态代理真正的原理及其生成的过程,我们以后写JDK Proxy可以不用去查demo,就可以徒手写个完美的Proxy.下面首先来个简单的Demo,后续的分析过程都依赖这个Demo去介绍,例子采用JDK1.8运行. JDK Proxy HelloWorld package com.yao.proxy; /** * Created by robin */ public inter

  • JDK动态代理之WeakCache缓存的实现机制

    上一篇我们分析了Proxy类的内部是怎样产生代理类的,我们看到了Proxy内部用到了缓存机制,如果根据提供的类加载器和接口数组能在缓存中找到代理类就直接返回该代理类,否则会调用ProxyClassFactory工厂去生成代理类.这里用到的缓存是二级缓存,它的一级缓存key是根据类加载器生成的,二级缓存key是根据接口数组生成的.具体的内部机制我们直接贴上代码详细解释. //Reference引用队列 private final ReferenceQueue<K> refQueue = new

随机推荐