代理模式
代理模式UML使用代理模式创建代理对象,让代理对象来控制对某个对象的访问, 被代理对象可以是远程对象,创建开销大的对象或者需要安全控制的对象等.
Proxy
称为代理对象.
RealSubject
是被代理的对象,也称为委托对象.
Subject
是他们抽象出来的接口.
RealSubject
和Proxy
都继承自Subject
, Proxy
内部持有一个 RealSubject
的变量,调用代理的方法,代理中将直接调用RealSubject
对应的方法.
静态代理
静态代理,在编译期间就需要指定好代理类,即在程序运行前就已经存在代理类的字节码文件,代理类和委托类的关系在运行前就确定了.
interface Subject {
void request();
}
class Proxy implements Subject {
private RealSubject realSubject;
public Proxy(RealSubject realSubject) {
this.realSubject = realSubject;
}
@Override
public void request() {
// 添加log
System.out.println("log...");
realSubject.request();
}
}
class RealSubject implements Subject {
@Override
public void request() {
// todo ...
System.out.println("request from http");
}
}
public static void main(String[] args) {
Subject proxy = new Proxy(new RealSubject());
proxy.request();
}
这样做的优点:
1. 可以隐藏委托类的实现,可以进行权限控制和安全控制.
2. 实现客户端和委托类解耦,只要对外接口不变,客户端就不需要修改调用方式.
3. 通过扩展代理类,进行一些功能的附加与增强.
动态代理
动态代理类的字节码在程序运行时由Java反射机制动态生成,无需程序员手工编写它的源代码.代理类和委托类的关系是在程序运行时确定。
相比于静态代理, 动态代理的优势在于可以很方便的对代理类的函数进行统一的处理,而不用修改每个代理类的函数。
Java中的动态代理是使用Proxy.newProxyInstance()
方法生成的.
先观察下 Proxy.newProxyInstance()
的参数.
Object newProxyInstance(
ClassLoader loader,
Class<?>[] interfaces,
InvocationHandler h
)
ClassLoader loader:
表示加载生成代理类的类加载器,通常情况下是直接使用
当前线程的类加载器 Thread.currentThread().getContextClassLoader()
,
或者 使用 代理接口的类加载器. 如 Subject.class.getClassLoader()
.
少部分情况下,需要使用特殊的类加载器,如Android插件化中,使用动态代理,可能需要传入插件对应的类加载器.
Class<?>[] interfaces:
这里,传入你感兴趣的 委托类所实现的接口.
如下面例子中的 Subject.class
, 则你需要传入 new Class[]{Subject.class}
作为参数.
如果 RealSubject
实现了多个接口Subject1,Subject2,Subject3...
,而你对其中Subject1,Subject2
感兴趣,
你可以传入 new Class[]{Subject1.class,Subject2.class}
, JVM运行时生成的字节码类,将会实现传入的这些接口.
InvocationHandler h
方法调度处理器接口. 内部有一个回调方法 Object invoke(Object proxy, Method method, Object[] args)
.
实现该方法,可以拦截 上一个参数传入的接口的方法, 你可以对这些方法进行增强, 甚至改变方法的行为.
实现 InvocationHandler
通常需要传入一个 委托对象, 然后在invoke()
方法中,对委托对象进行修改或者增强操作.
来看一个简单的动态代理的例子.
// proxy.Subject.java
public interface Subject {
void doSomething();
}
// proxy.RealSubject.java
public class RealSubject implements Subject {
@Override
public void doSomething() {
System.out.println("RealSubject do ...");
}
}
// ReflectTest.java
public class ReflectTest {
public static void main(String[] args) {
// 委托对象
RealSubject realSubject = new RealSubject();
// 方法调度处理器
InvocationHandler handler = new ProxyHandler(realSubject);
// 生成代理对象
Subject proxy = (Subject) Proxy.newProxyInstance(
Thread.currentThread().getContextClassLoader(),
new Class[]{Subject.class},
handler
);
// 这里使用辅助类,保存JVM生成动态代理类到本地,此时先忽略
ProxyUtils.generateClassFile(realSubject.getClass(), "SubjectProxy");
// 代理运行方法
proxy.doSomething();
}
// 代理方法调度器
static class ProxyHandler implements InvocationHandler {
// 委托对象
RealSubject realSubject;
ProxyHandler(RealSubject realSubject) {
this.realSubject = realSubject;
}
/**
* 在此方法中,进行委托对象方法的增强或者修改
* 该示例中只是简单的对其添加log.
*/
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("do before ...");
// 处理委托对象的方法
Object result = method.invoke(realSubject, args);
System.out.println("do after ...");
return result;
}
}
}
//do before ...
//RealSubject do ...
//do after ...
从这里,我们可以窥探出 动态代理与静态代理的一些差别,我们来总结一下.
动态代理使用
InvocationHandler
的invoke()
方法来统一处理委托类的方法, 也就是说, 即使委托对象实现的接口中有几百个方法,我们也只要在这一个方法中处理即可.换句话说,如果委托类增加了一些方法,而我们不需要对方法进行修改,那动态代理这部分的代码,可以不需要改动,静态代理则达不到这种效果.
动态代理在运行时,动态生成字节码数据,我们在编写代码的时候是看不到真正的代理类代码.
动态代理一个重要的应用就是
AOP(面向切面编程)
, 主要处理 日志记录,性能统计,安全控制,事务处理,异常处理等等
如果我们需要对一些 不对外开发的或者不容易直接操作的类或者api进行操作,我们也可以使用 动态代理来处理. 如Android插件化中,对系统资源的HOOK, 如对
ActivityManager
的Hook就用到动态代理技术.
动态代理原理
Java的动态代理是通过 Proxy .newProxyInstance()
方法来生成的. 我们来追踪下源码, 源码中 我把无关和不打紧的代码去掉,以便分析.
public class Proxy {
// 生成的动态代理的构造参数类
private static final Class<?>[] constructorParams = {InvocationHandler.class};
// 动态代理类的缓存池
private static final WeakCache<ClassLoader, Class<?>[], Class<?>>
proxyClassCache = new WeakCache<>(new KeyFactory(), new Proxy.ProxyClassFactory());
public static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h)
throws IllegalArgumentException {
final Class<?>[] intfs = interfaces.clone();
// 1. 生成动态代理类
Class<?> cl = getProxyClass0(loader, intfs);
try {
// 2. 反射创建动态代理类实例
final Constructor<?> cons = cl.getConstructor(constructorParams);
return cons.newInstance(new Object[]{h});
} catch (Exception e) {
throw new InternalError(e.toString(), e);
}
}
private static Class<?> getProxyClass0(ClassLoader loader, Class<?>... interfaces) {
// 如果通过传入的 类加载器和接口类已经缓存过, 则直接从缓存中获取 之前已经生成的代理类
// 否则, 将会通过ProxyClassFactory来创建一份动态代理类
return proxyClassCache.get(loader, interfaces);
}
/**
* 生成代理类的工厂
*/
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) {
// 1. 验证传入的接口类,是否是用传入的 类加载器 加载的,不是则报错
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");
}
// 2. 验证传入的类是接口类型, 不是则报错
if (!interfaceClass.isInterface()) {
throw new IllegalArgumentException(interfaceClass.getName() + " is not an interface");
}
// 3. 验证是否传入重复的接口类 , 是则报错
if (interfaceSet.put(interfaceClass, Boolean.TRUE) != null) {
throw new IllegalArgumentException(
"repeated interface: " + interfaceClass.getName());
}
}
String proxyPkg = null; // package to define proxy class in
int accessFlags = Modifier.PUBLIC | Modifier.FINAL;
// 4. 对所有非公开的接口进行判断,
// 4.1 判断所有的非公开接口,是不是在同一个包下, 不在 则报错, 在则生成的代理类与其 同包名
// 4.2 如果没有非公开的接口,直接使用默认的包名 : com.sun.proxy
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) {
// if no non-public proxy interfaces, use com.sun.proxy package
proxyPkg = ReflectUtil.PROXY_PACKAGE + ".";
}
// 5. 生成动态代理类的名称
// 形如 : com.sun.proxy.$Proxy0 com.sun.proxy.$Proxy1
long num = nextUniqueNumber.getAndIncrement();
String proxyName = proxyPkg + proxyClassNamePrefix + num;
// 6. 通过代理生成器 直接生成 代理类的 字节码数据 即 .class类型的数据
byte[] proxyClassFile = ProxyGenerator.generateProxyClass(
proxyName, interfaces, accessFlags);
try {
// 7. 将字节码数据转为 Class 类
return defineClass0(loader, proxyName,
proxyClassFile, 0, proxyClassFile.length);
} catch (ClassFormatError e) {
throw new IllegalArgumentException(e.toString());
}
}
}
}
从源码中我们可以看出, 生成代理对象大步骤分为两个.
- 生成动态代理类对象
- 反射生成动态代理实例对象
我们主要看第一个步骤. 它又分为好几个小的流程, 在上面源码分析中已经很清楚的写出来,这里就不在赘述.
它最终会调用 ProxyGenerator.generateProxyClass()
方法来生成字节码文件.
然而这个过程 我们并不能拿到 这个生成的对象,也就是说 我们从代码上是看不到这份文件的, 这对我们分析有很大的麻烦.
而既然 代码是通过 ProxyGenerator.generateProxyClass()
这个方法来生成, 我们可以尝试通过这个方法来将生成的代码保存到本地, 以便于分析.
public class ProxyUtils {
/**
* 根据类信息和提供的代理类名称,生成字节码并且保存到本地
*
* @param clazz 委托类
* @param proxyName 生成代理类的名称
*/
public static void generateClassFile(Class<?> clazz, String proxyName) {
// 根据类信息和提供的代理类名称,生成字节码
byte[] classFile = ProxyGenerator.generateProxyClass(proxyName, clazz.getInterfaces());
String path = clazz.getResource(".").getPath();
System.out.println(path);
FileOutputStream out = null;
try {
// 生成.class文件保存到本地
out = new FileOutputStream(path + proxyName + ".class");
out.write(classFile);
out.flush();
} catch (Exception e) {
e.printStackTrace();
} finally {
try {
if (out != null) out.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
这个类可以帮助我们保存生成的代理类对象, 在上个例子中 有调用到过 ProxyUtils.generateClassFile(realSubject.getClass(), "SubjectProxy");
,
我们直接打开生成的类来看下.
public final class SubjectProxy extends Proxy implements Subject {
private static Method m1;
private static Method m3;
private static Method m2;
private static Method m0;
public SubjectProxy(InvocationHandler var1) throws {
super(var1);
}
public final boolean equals(Object var1) throws {
try {
return (Boolean)super.h.invoke(this, m1, new Object[]{var1});
} catch (RuntimeException | Error var3) {
throw var3;
} catch (Throwable var4) {
throw new UndeclaredThrowableException(var4);
}
}
public final void doSomething() throws {
try {
super.h.invoke(this, m3, (Object[])null);
} catch (RuntimeException | Error var2) {
throw var2;
} catch (Throwable var3) {
throw new UndeclaredThrowableException(var3);
}
}
public final String toString() throws {
try {
return (String)super.h.invoke(this, m2, (Object[])null);
} catch (RuntimeException | Error var2) {
throw var2;
} catch (Throwable var3) {
throw new UndeclaredThrowableException(var3);
}
}
public final int hashCode() throws {
try {
return (Integer)super.h.invoke(this, m0, (Object[])null);
} catch (RuntimeException | Error var2) {
throw var2;
} catch (Throwable var3) {
throw new UndeclaredThrowableException(var3);
}
}
static {
try {
m1 = Class.forName("java.lang.Object").getMethod("equals", Class.forName("java.lang.Object"));
m3 = Class.forName("proxy.Subject").getMethod("doSomething");
m2 = Class.forName("java.lang.Object").getMethod("toString");
m0 = Class.forName("java.lang.Object").getMethod("hashCode");
} catch (NoSuchMethodException var2) {
throw new NoSuchMethodError(var2.getMessage());
} catch (ClassNotFoundException var3) {
throw new NoClassDefFoundError(var3.getMessage());
}
}
}
可以看出, 生成的代理类, 是继承 Proxy
类,并且实现了Subject
接口, 构造函数传入 InvocationHandler
对象.
然后,它将实现来的所有方法, 都通过反射的方式,调用 InvocationHandler.invoke()
方法来实现.
而InvocationHandler
中将传入委托类 来完成反射调用.
也就是说, 代理类, 最终调用的是 通过
InvocationHandler.invoke()
方法进行增强或者修改的, 委托类(RealSubject)所对应的方法.
至此,我们分析完了, 动态代理实现的原理.
动态代理的不足
动态代理是一定要基于接口的, 如果委托对象没有实现相应的接口, 是无法对其创建动态代理的.
JDK为我们提供的代理实现方案确实没法解决这个问题, 那怎么办呢? 可以使用 CGLib动态代理
, 这里就不对其进行展开, 感兴趣的可以自行搜索了解.
网友评论