一、前言
动态代理 是一种设计模式,JDK 所提供的动态代理是基于接口实现。动态代理用处颇多,比如 Mybatis 使用动态代理代理 Mapper 对象,我们不需要写实现类就能调用接口所定义的方法并获取返回结果。也可以用于日志、AOP 等业务场景。
二、JDK Demo
2.1 定义需要被代理的接口以及实现类
/*** #1 定义需要动态代理的接口*/public interface Subject {/*** 被代理的方法*/void doing();}/*** #2 接口实现类*/public class SubjectImpl implements Subject{@Overridepublic void doing() {System.out.println("I'm doing");}}
2.2 定义InvocationHandler
我们需要对上述的 doing() 方法进行代理,加一句话吧:
/*** #3 定义动态代理InvocationHandler*/public class SubjectInvocationHandlerImpl implements InvocationHandler {// 原始对象private Object target;public SubjectInvocationHandlerImpl(Object target) {this.target = target;}// proxy: JDK生成的代理对象@Overridepublic Object invoke(Object proxy, Method method, Object[] args) throws Throwable {// BeforeSystem.out.println("Before doing, I need a good sleep");Object result = method.invoke(target, args);System.out.println("After doing, I also need a good sleep");return result;}}
此外需要通过构造器传入被代理对象,并作为参数传入
method.invoke(target,args)方法中。
2.3 创建 Proxy
public class JdkDynamicDemo {public static void main(String[] args) {// #1 创建 需要被代理对象SubjectImpl subject = new SubjectImpl();// #2 使用Proxy静态方法创建代理对象Subject subjectProxy = (Subject) Proxy.newProxyInstance(JdkDynamicDemo.class.getClassLoader(),new Class[]{Subject.class},new SubjectInvocationHandlerImpl(subject));// #3 执行目标方法subjectProxy.doing();}}// OUTPUT// Before doing, I need a good sleep// I'm doing// After doing, I also need a good sleep
简单几个步骤,我们不需要改动目标类方法 doing() 源码,就能在方法执行前、后进行自定义逻辑处理。
三、剖析
3.1 Java Doc 关于 Proxy 说明
Proxy 提供静态方法用以创建对象,它的行为类似接口实例所定义的一样,在此基础上,允许我肯定义方法调用。
一个代理类是在运行时根据指定接口集合(list)创建,所创建的代理对象也属于 Proxy。
每个 Proxy 实例需要一个与之关联的 invocation Handler 对象,它实现 InvocationHandler 接口。通过其代理接口之一对代理实例进行的方法调用将分派给该实例的调用处理程序的 invoke 方法,并传递该代理实例,一个标识所调用方法的 java.lang.reflect.Method 对象以及一个数组。
一个代理类有如下属性:
- 名称独一无二,并以
@Proxy开头。 - 代理类为
final且为非抽象类。 - 代理类继承
java.lang.reflect.Proxy类。 - 代理类完成按照其创建顺序实现接口。
- 调用
Class#getInterfaces()返回所有实现的接口。 - 调用
Class#getMethods getMethods返回所有实现的方法。 - 与 Object 一致的安全规则。
Proxy#isProxyClass方法用以判断当前类是否为代理对象。当不同接口具有相同的方法签名时,接口顺序就变得重要了,代理类的接口列表包含该方法的最前接口的方法的 Method 对象将会传递给
InvocationHandler。3.2 源码分析 (JDK 14)
JDK 动态代理源码在 JDK9 被重写了,代码如下:
// java.lang.reflect.Proxypublic static Object newProxyInstance(ClassLoader loader,Class<?>[] interfaces,InvocationHandler h) {Objects.requireNonNull(h);final Class<?> caller = System.getSecurityManager() == null? null: Reflection.getCallerClass();// 查找或生成指定的代理类及其构造函数。Constructor<?> cons = getProxyConstructor(caller, loader, interfaces);// 使用反射实例化对象return newProxyInstance(caller, cons, h);}
// java.lang.reflect.Proxyprivate static Class<?> defineProxyClass(Module m, List<Class<?>> interfaces) {String proxyPkg = null; // package to define proxy class inint accessFlags = Modifier.PUBLIC | Modifier.FINAL;/** Record the package of a non-public proxy interface so that the* proxy class will be defined in the same package. Verify that* all non-public proxy interfaces are in the same package.*/for (Class<?> intf : interfaces) {int flags = intf.getModifiers();if (!Modifier.isPublic(flags)) {accessFlags = Modifier.FINAL; // non-public, finalString pkg = intf.getPackageName();if (proxyPkg == null) {proxyPkg = pkg;} else if (!pkg.equals(proxyPkg)) {throw new IllegalArgumentException("non-public interfaces from different packages");}}}if (proxyPkg == null) {// all proxy interfaces are publicproxyPkg = m.isNamed() ? PROXY_PACKAGE_PREFIX + "." + m.getName(): PROXY_PACKAGE_PREFIX;} else if (proxyPkg.isEmpty() && m.isNamed()) {throw new IllegalArgumentException("Unnamed package cannot be added to " + m);}if (m.isNamed()) {if (!m.getDescriptor().packages().contains(proxyPkg)) {throw new InternalError(proxyPkg + " not exist in " + m.getName());}}/** Choose a name for the proxy class to generate.*/long num = nextUniqueNumber.getAndIncrement();String proxyName = proxyPkg.isEmpty()? proxyClassNamePrefix + num: proxyPkg + "." + proxyClassNamePrefix + num;ClassLoader loader = getLoader(m);trace(proxyName, m, loader, interfaces);/** Generate the specified proxy class.* PROXY_GENERATOR_V49一般为false, 选择 ProxyGenerator.generateProxyClass 生成字节码文件*/byte[] proxyClassFile = PROXY_GENERATOR_V49? ProxyGenerator_v49.generateProxyClass(proxyName, interfaces, accessFlags): ProxyGenerator.generateProxyClass(loader, proxyName, interfaces, accessFlags);try {Class<?> pc = JLA.defineClass(loader, proxyName, proxyClassFile,null, "__dynamic_proxy__");reverseProxyCache.sub(pc).putIfAbsent(loader, Boolean.TRUE);return pc;} catch (ClassFormatError e) {throw new IllegalArgumentException(e.toString());}}

最重要的方法为 ProxyGenerator.generateProxyClass(loader, proxyName, interfaces, accessFlags) ,他根据入参生成相对应的代理类的字节码。
四、总结
- JDK 动态代理前提之一是需要用户传入自定义接口,JDK 根据接口生成对应的字节码(生成的字节码会持久化至本地,可以在启动时配置
jdk.proxy.ProxyGenerator.saveGeneratedFiles),相当于帮助我们实现了一个代理对象。
