Java静态代理与动态代理
一、概述
1、什么是代理模式
先来看看代理模式的定义:代理模式给某一个对象提供一个代理对象,并由代理对象控制对原对象的引用。通俗地讲,代理模式就是提供一个对象相当于中介者,他可以帮我们调用目标的功能,同时我们可以在使用目标功能的同时扩展其他功能,而不需要直接改到目标类。
2、为什么要使用代理模式
通过代理模式帮我们调用目标类,那为什么我们不直接调用目标类,而要额外弄一个类出来间接调用,这不是闲得慌吗?存在即是道理,既然存在这样一种模式,自然有它的好处,下面来看下代理模式的好处:
- 中介隔离作用:在某些情况下,如果某个类并不想被直接调用,那么通过创建代理类,就 可以避免目标类被直接调用,这样可以保护目标类不会被曝光
- 可以扩展功能而不用影响目标类:假设有一个核心类提供了一些核心业务,而我们希望使用这个功能时上报日志,或者某个业务要先进行某些处理才调用,那这样如果直接改核心类,就会给核心功能加上了一些无关的内容,变得这个功能与业务有很大关联,难以复用。这时我们使用代理类的话,就可以在代理类中添加自己的业务逻辑,而不需要破坏原有的核心功能。
3、代理模式的种类
代理模式可以分为静态代理和动态代理,下面会进行详解。
二、静态代理
1、概念
静态代理指是由程序员手动编写或者自动生成的代理类,在程序编译的时候就可以生成对应的class文件,在运行时可以直接使用。
2、静态代理的实现
静态代理实现起来比较简单,只需要与目标类实现相同的接口,然后持有目标类对象,就可以对目标类进行扩展。下面看例子:
首先创建一个接口ISayHello.java:
public interface ISayHello { void sayHello(); }复制代码
目标类User.java:
public class User implements ISayHello { @Override public void sayHello() { System.out.println("hello"); } }复制代码
静态代理类StaticProxy.java
public class StaticProxy implements ISayHello { /** 持有目标类的对象,实现间接调用 */ private User target; public StaticProxy(User target) { this.target = target; } @Override public void sayHello() { System.out.println("before hello"); if (target != null) { target.sayHello(); // 实际调用目标功能 } System.out.println("after hello"); } }复制代码
静态代理的使用
public class Main { public static void main(String[] args) { StaticProxy proxy = new StaticProxy(new User()); proxy.sayHello(); } }复制代码
输出结果
before hello
hello
after hello复制代码
3、静态代理应用场景
对一些第三方框架进行扩展代理、自动生成的代码,例如Android中的AIDL等。
三、动态代理
1、概述
与静态代理不同,动态代理不需要提前创建对象,只需要提供一个动态创建器,程序会在运行时候动态生成对应的代理类。
2、动态代理的好处
动态代理的好处主要就是可以减少对接口的依赖,降低耦合度,减少冗余代码,方便对代理方法的统一管理。我们可以从以下几点体现出来:
减少方法的实现:如果一个接口有1000个方法,而我们只想代理其中某几个方法,使用静态代理需要代理类实现所有的方法,如果接口增加一个方法,相应的代理类也要增加实现,代码会变得十分冗余。但如果使用动态代理,就可以简单地只对某几个方法进行处理,不需要加入大量无用的代码。
可以服务多个目标类:因为静态代理在编译时期就已经生成对应的类,所以对于每一个代理类,都已经固定了要服务的目标类,这就导致了如果需要多个目标类,就要编写对应的代理类,导致类会很多。而使用动态代理,则可以一个动态处理器为不同的目标类作服务,从而减少类的编写。
3、动态代理的实现
动态代理的实现主要使用了InvocationHandler,下面看例子:
接口和目标类均使用静态代理的例子,现在实现动态代理类DynamicProxyHandler.java:
public class DynamicProxyHandler implements InvocationHandler { private Object target; public Object bind(Object target) { this.target = target; if (target == null) { return null; } // 通过newProxyInstance方法创建代理类 return Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(), this); } @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { if (method.getName().equals("sayHello")) { // 如果是需要代理的方法则进行处理 System.out.println("before hello"); Object invoke = method.invoke(target, args); // 实际调用目标功能 System.out.println("after hello"); return invoke; } return method.invoke(target, args); } }复制代码
动态代理的使用
public class Main {
public static void main(String[] args) {
DynamicProxyHandler proxyHandler = new DynamicProxyHandler();
ISayHello proxy = (ISayHello) proxyHandler.bind(new User());
proxy.sayHello();
}
}复制代码
输出结果
before hello
hello
after hello复制代码
4、动态代理的原理
从上面的例子可以看到,动态代理生存代理类主要是依靠Proxy.newProxyInstance这一个静态方法,因此,下面就看看这一个方法做了什么
public static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h) throws IllegalArgumentException { Objects.requireNonNull(h); final Class<?>[] intfs = interfaces.clone(); final SecurityManager sm = System.getSecurityManager(); if (sm != null) { checkProxyAccess(Reflection.getCallerClass(), loader, intfs); } // 通过缓存获取代理类,没有则生成代理类 Class<?> cl = getProxyClass0(loader, intfs); try { if (sm != null) { checkNewProxyPermission(Reflection.getCallerClass(), cl); } // 获取代理类的构造函数 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 }复制代码
上面这个方法抛开一些检查的逻辑,核心方法就是getProxyClass0(loader, intfs)来生成我们需要的代理类,后后通过构造方法传入InvocationHandler来创建实例,接下来看一下getProxyClass0(loader, intfs)的实现
private static Class<?> getProxyClass0(ClassLoader loader, Class<?>... interfaces) { if (interfaces.length > 65535) { // 检查接口的方法数 throw new IllegalArgumentException("interface limit exceeded"); } // 从缓存中获取代理类,如果没有则会进行生成 // proxyClassCache = new WeakCache<>(new KeyFactory(), new ProxyClassFactory()); return proxyClassCache.get(loader, interfaces); }复制代码
这个方法只有几行代码,首先检查一下接口的数量,然后从缓存中获取类,如果找不到的话会使用ProxyClassFactory来生成,下面看看是如何生成的
private static final class ProxyClassFactory implements BiFunction<ClassLoader, Class<?>[], Class<?>> { // 代理类的名称前缀 private static final String proxyClassNamePrefix = "$Proxy"; // 代理类的序号,以此确定唯一的代理类 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) { Class<?> interfaceClass = null; try { interfaceClass = Class.forName(intf.getName(), false, loader); } catch (ClassNotFoundException e) { } if (interfaceClass != intf) { // 判断是否可以由指定的类加载 } if (!interfaceClass.isInterface()) { // 判断是否一个接口 } if (interfaceSet.put(interfaceClass, Boolean.TRUE) != null) { // 判断是否有重复的接口 } } String proxyPkg = null; // 生成代理类的访问标志, 默认是public final的 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"); } } } if (proxyPkg == null) { // 公有的接口,则默认包名com.sun.proxy proxyPkg = ReflectUtil.PROXY_PACKAGE + "."; } // 生成代理类序号 long num = nextUniqueNumber.getAndIncrement(); // 代理类的名字 包名+前缀+序号,例如com.sun.proxy$Proxy1 String proxyName = proxyPkg + proxyClassNamePrefix + num; // 使用generateProxyClass生成字节码 byte[] proxyClassFile = ProxyGenerator.generateProxyClass( proxyName, interfaces, accessFlags); try { // 根据二进制文件生成相应的Class实例 return defineClass0(loader, proxyName, proxyClassFile, 0, proxyClassFile.length); } catch (ClassFormatError e) { throw new IllegalArgumentException(e.toString()); } } }复制代码
可以看到,这个类通过 apply来生成我们所需的代理类,这个类主要做了几件事:
- 进行一些合法性判断,不合法则抛出异常
- 生成包名+前缀+序号的代理类,例如com.sun.proxy$Proxy1
- 如果接口是公有的,则默认包名是com.sun.proxy;否则和接口的包名相同
- 通过ProxyGenerator.generateProxyClass来生成字节码
- 同过defineClass0来生成class实
5、动态代理应用场景
例如Retrofit网络加载框架、Spring中AOP的实现等。
四、总结
- 代理模式可以在不改变目标类的情况下对功能进行扩展
- 静态代理编译时便生成class文件,动态代理运行时通过反射生成对应的class文件
- 静态代理需要实现接口的所有方法,一个代理类能服务的目标类有限
- 动态代理可以只对某些方法进行处理,一个代理处理器可以服务多个目标类
- 对于一些接口比较简单、或者自动生成的通用性代码,可以选择使用静态代理;对于一些庞大的接口,频繁地需要改动接口,你已经觉得改得很烦,可以选择使用动态代理。
五、参考资料
Java动态代理从入门到原理再到实战
JDK动态代理的底层实现之Proxy源码分析
还没有评论,来说两句吧...