设计模式系列文章:https://zhum.in/blog/category/notes/design-pattern
Demo code:https://github.com/njZhuMin/BlogSampleCode
代理模式
代理模式 (Proxy Pattern) 是指为其他对象提供一种代理,以控制对这个对象的访问,属于结构型模式。
在某些情况下,一个对象不适合或者不能直接引用另一个对象,而代理对象可以在客户端和目标对象之间起到中介的作用。
代理模式一般包含三种角色:
- 抽象主题角色 (Subject):声明真实主题与代理的共同接口方法,该类可以是接口也可以是抽象类。
- 真实主题角色 (RealSubject):也称为被代理类,该类定义了代理所表示的真实对象,是负责执行系统真正的逻辑业务对象。
- 代理主题角色 (Proxy):也称为代理类,其内部持有 RealSubject 的引用,因此具备完全的对 RealSubject 的代理权。
代理者内部持有真实角色的引用
客户端调用代理对象的方法,此时代理对象会调用被代理对象(真正的执行者)的方法,但可能会在原代码逻辑前后增加一些代码逻辑,而使调用者无感知。
代理模式在实现方式上分为静态代理和动态代理。
静态代理模式
显式声明被代理的对象
依赖关系
举一个常见的例子,购房者有购房的需求,通常会通过房产中介来寻找合适的房源。购房者是真正有找房需求的人,但是大量房源掌握在房产中介手上,这时房产中介就作为购房者的代理,替买房者执行了寻找房源的动作。
public interface IHouseBuyer {
void findHouse();
}
public class ShanghaiBuyer implements IHouseBuyer {
@Override
public void findHouse() {
System.out.println("[Shanghai Buyer] Want to find a 100m^2 house in Shanghai.");
}
}
public class ShanghaiAgent {
private IHouseBuyer houseBuyer;
public ShanghaiAgent(IHouseBuyer houseBuyer) {
this.houseBuyer = houseBuyer;
}
public void findHouseInShanghai() {
System.out.println("[Shanghai Agent] Search house for buyer in Shanghai...");
houseBuyer.findHouse();
System.out.println("[Shanghai Agent] House found! Contact house buyer for deal...");
}
}
public class Test {
public static void main(String[] args) {
ShanghaiAgent shanghaiAgent = new ShanghaiAgent(new ShanghaiBuyer());
shanghaiAgent.findHouseInShanghai();
}
}
依赖关系如下:
在这个案例中,一家上海房产中介作为代理者,可以帮助上海的购房者寻找房源。而购房者不需要关心寻找房源的过程细节,只需要委托代理者即可完成这个动作。
静态代理的业务场景
那么,现在又有一个北京的购房者需要找北京的房源:
public class BeijingBuyer implements IHouseBuyer {
@Override
public void findHouse() {
System.out.println("[Beijing Buyer] Want to find a 80m^2 house in Beijing.");
}
}
这时上海的房产中介就搞不定了,因为他不了解北京的房源。这就暴露了静态代理模式的弊端:其可以代理的对象不具有通用性。
那么,有没有一个代理者,不管你想找哪里的房,只要你是想找房(满足某个特定接口的动作),我就可以帮你搞定(完成通用的代理)呢?这时就需要动态代理模式了。
常用的动态代理模式的实现方式有:JDK 动态代理和 CGLib 动态代理。
JDK 动态代理
案例
用 JDK 动态代理改造我们的房产中介:
public class JdkProxyAgent implements InvocationHandler {
private IHouseBuyer target;
public IHouseBuyer getInstance(IHouseBuyer target){
this.target = target;
Class<?> clazz = target.getClass();
return (IHouseBuyer) Proxy.newProxyInstance(
clazz.getClassLoader(), clazz.getInterfaces(), this);
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
beforeInvoke();
Object result = method.invoke(this.target, args);
afterInvoke();
return result;
}
private void beforeInvoke() {
System.out.println("[Proxy Agent] Search house for buyer...");
}
private void afterInvoke() {
System.out.println("[Proxy Agent] House found! Contact house buyer for deal...");
}
}
public class JDKProxyAgentTest {
public static void main(String[] args) {
JdkProxyAgent proxyAgent = new JdkProxyAgent();
IHouseBuyer shanghaiBuyer = proxyAgent.getInstance(new ShanghaiBuyer());
shanghaiBuyer.findHouse();
IHouseBuyer beijingBuyer = proxyAgent.getInstance(new BeijingBuyer());
beijingBuyer.findHouse();
}
}
原理实现
JDK 动态代理采用字节重组,重新生成对象来替代原始对象,以达到动态代理的目的。 JDK 动态代理生成对象的步骤如下:
- 获取被代理对象的引用,然后反射获取它的所有接口。
- 重新生成一个新的类,实现被代理类实现的所有接口。
- 动态生成 Java 代码,新加的业务逻辑方法由一定的逻辑代码调用。
- 编译动态生成的 Java 源代码并生成 class 文件。
- 将新的 class 文件重新加载到 JVM 中。
以上过程就叫字节码重组。 JDK 规范中指出,在 ClassPath 下以 $
开头的 class 文件,通常都是自动生成的。
理解了 JDKProxy 字节码重组思路,我们来照猫画虎试试能不能碰瓷出一个低配版的 JDKProxy 出来。
研究重组的源文件
在这里打个断点,我们可以发现通过 JdkProxyAgent 生成的对象并不是 ShanghaiBuyer 的原始对象了,而是一个 com.sun.proxy.$Proxy0
对象。
JdkProxyAgent proxyAgent = new JdkProxyAgent();
IHouseBuyer shanghaiBuyer = proxyAgent.getInstance(new ShanghaiBuyer());
那么我们就把这个对象想办法搞出来,看看 JDKProxy 重组后的对象长什么样:
public class JDKProxyAgentTest {
public static void main(String[] args) {
JdkProxyAgent proxyAgent = new JdkProxyAgent();
IHouseBuyer shanghaiBuyer = proxyAgent.getInstance(new ShanghaiBuyer());
shanghaiBuyer.findHouse();
IHouseBuyer beijingBuyer = proxyAgent.getInstance(new BeijingBuyer());
beijingBuyer.findHouse();
// Serialize JDKProxy generated class to disk
try {
// JDK 9+ moved generateProxyClass to java.lang.reflect.ProxyGenerator
// With JDK 8, call sun.misc.ProxyGenerator directly
Class<?> clazz = Class.forName("java.lang.reflect.ProxyGenerator");
Method method = clazz.getDeclaredMethod("generateProxyClass", String.class, Class[].class);
method.setAccessible(true);
byte[] bytes = (byte[]) method.invoke(null,"Proxy0", new Class[]{IHouseBuyer.class});
FileOutputStream fos = new FileOutputStream("Proxy0.class");
fos.write(bytes);
fos.flush();
fos.close();
} catch (Exception e) {
e.printStackTrace();
}
}
}
生成的 Class 文件内容如下:
import example.Proxy.IHouseBuyer;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.lang.reflect.UndeclaredThrowableException;
public final class Proxy0 extends Proxy implements IHouseBuyer {
private static Method m1;
private static Method m3;
private static Method m2;
private static Method m0;
publicProxy0(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 findHouse() 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("example.Proxy.IHouseBuyer").getMethod("findHouse");
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());
}
}
}
我们发现,$Proxy0
继承了 Proxy
类,同时还实现了 IHouseBuyer
接口,而且重写了 findHouse()
等方法。在静态块中用反射查找到了目标对象的所有方法,而且保存了所有方法的引用,然后通过反射调用重写后的目标对象的对应方法。
基于 JDKProxy 原理的实现
从使用者角度入手,我们先要提供一个 InvocationHandler
接口:
public interface MyInvocationHandler {
Object invoke(Object var1, Method var2, Object[] var3) throws Throwable;
}
然后需要实现 Proxy 的接口和一个将编译好的 class 文件加载进来的 ClassLoader 类:
public class MyClassLoader extends ClassLoader {
private File classPathFile;
public MyClassLoader() {
String classPath = MyClassLoader.class.getResource("").getPath();
this.classPathFile = new File(classPath);
}
protected Class<?> findClass(String name) throws ClassNotFoundException {
String className = MyClassLoader.class.getPackage().getName() + "." + name;
if (classPathFile != null) {
File classFile = new File(classPathFile, name.replaceAll("\\.", "/") + ".class");
if (classFile.exists()) {
try (FileInputStream fis = new FileInputStream(classFile);
ByteArrayOutputStream bos = new ByteArrayOutputStream()) {
byte[] buff = new byte[1024];
int len;
while ((len = fis.read(buff)) != -1) {
bos.write(buff, 0, len);
}
return defineClass(className, bos.toByteArray(), 0, bos.size());
} catch (IOException e) {
e.printStackTrace();
}
}
}
return null;
}
}
动态代理实现:
public class MyProxy {
private static final String ln = "\n";
public static Object newProxyInstance(MyClassLoader classLoader,
Class<?>[] interfaces,
MyInvocationHandler h) {
// 1. Dynamically generate .java src file
String srcCode = generateSrc(interfaces);
if (srcCode == null) {
throw new RuntimeException("Error generating proxy class source file.");
}
// 2. Save Proxy0.java to disk
String srcPath = MyProxy.class.getResource("").getPath();
File srcFile = new File(srcPath + "Proxy0.java");
try (FileWriter fileWriter = new FileWriter(srcFile)) {
fileWriter.write(srcCode);
fileWriter.flush();
} catch (IOException e) {
e.printStackTrace();
}
// 3. Compile Proxy0.java file toProxy0.class
JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
try (StandardJavaFileManager fileManager = compiler.getStandardFileManager
(null, null, StandardCharsets.UTF_8)) {
Iterable<? extends JavaFileObject> iterable = fileManager.getJavaFileObjects(srcFile);
JavaCompiler.CompilationTask task = compiler.getTask(null, fileManager, null, null, null, iterable);
task.call();
} catch (IOException e) {
e.printStackTrace();
}
// 4. Load Proxy0.java into JVM
Class<?> proxyClass = null;
try {
proxyClass = classLoader.findClass("Proxy0");
boolean delTempSrcFile = srcFile.delete();
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
// 5. Re-assemble bytecode and return proxied object
if (proxyClass == null) {
throw new RuntimeException("Error loading generated class file");
}
try {
Constructor<?> constructor = proxyClass.getConstructor(MyInvocationHandler.class);
return constructor.newInstance(h);
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
private static String generateSrc(Class<?>[] interfaces){
StringBuilder sb = new StringBuilder();
sb.append(MyProxy.class.getPackage() + ";" + ln)
.append("import " + interfaces[0].getName() + ";" + ln)
.append("import java.lang.reflect.*;" + ln)
.append("public final class Proxy0 implements " + interfaces[0].getName() + " {" + ln)
.append("private MyInvocationHandler h;" + ln)
.append("publicProxy0(MyInvocationHandler h) {" + ln)
.append("this.h = h;" + ln)
.append("}" + ln);
for (Method m : interfaces[0].getMethods()) {
Class<?>[] params = m.getParameterTypes();
StringBuilder paramNames = new StringBuilder();
StringBuilder paramValues = new StringBuilder();
StringBuilder paramClasses = new StringBuilder();
// Get single method params
for (int i = 0; i < params.length; i++) {
Class<?> clazz = params[i];
String type = clazz.getName();
String paramName = "var" + i;
paramNames.append(type + " " + paramName);
paramValues.append(paramName);
paramClasses.append(clazz.getName() + ".class");
if (i < params.length - 1) {
paramNames.append(",");
paramClasses.append(",");
paramValues.append(",");
}
}
sb.append("public final " + m.getReturnType().getName() + " " + m.getName())
.append("(" + paramNames.toString() + ") {" + ln)
.append("try {" + ln)
.append("Method m = " + interfaces[0].getName() + ".class.getMethod(\"")
.append(m.getName() + "\", new Class[]{ " + paramClasses.toString() + " });" + ln);
String returnInvokedMethod = "this.h.invoke(this, m, new Object[]{" + paramValues.toString() + "})";
if (m.getReturnType() != void.class) {
if (isBasicDataType(m.getReturnType())) {
sb.append("return ((" + getPackagedDataType(m.getReturnType()) + ")" + returnInvokedMethod + "." + m.getReturnType().getSimpleName() + "Value();" + ln);
} else {
sb.append("return (" + m.getReturnType().getSimpleName() + ") " + returnInvokedMethod + ";" + ln);
}
} else {
sb.append(returnInvokedMethod + ";" + ln);
}
sb.append("} catch(Error _ex) { }" + ln)
.append("catch(Throwable e) {" + ln)
.append("throw new UndeclaredThrowableException(e);" + ln)
.append("}" + ln);
sb.append(getEmptyReturnCode(m.getReturnType()) + ln);
sb.append("}" + ln);
}
sb.append("}" + ln);
return sb.toString();
}
private static String getPackagedDataType(Class<?> clazz) {
return clazz.getSimpleName().substring(0, 1).toUpperCase() + clazz.getSimpleName().substring(1);
}
private static boolean isBasicDataType(Class<?> clazz) {
return clazz == byte.class || clazz == short.class || clazz == int.class ||
clazz == long.class || clazz == float.class || clazz == double.class ||
clazz == char.class || clazz == boolean.class;
}
private static String getEmptyReturnCode(Class<?> clazz) {
if (clazz == void.class) {
return "";
}
if (clazz == byte.class || clazz == short.class || clazz == int.class ||
clazz == long.class) {
return "return 0;";
}
if (clazz == float.class || clazz == double.class) {
return "return 0.0;";
}
if (clazz == char.class) {
return "return \"\";";
}
if (clazz == boolean.class) {
return "return false;";
}
return "return null;";
}
}
简单分别测试一下带返回值和多参数的方法:
public interface IRichHouseBuyer {
void findHouse();
String payTaxes(String tax1, String tax2, String tax3);
}
public class RichHouseBuyer implements IRichHouseBuyer {
@Override
public void findHouse() {
System.out.println("[Rich Buyer] Wants to find a large house...");
}
@Override
public String payTaxes(String tax1, String tax2, String tax3) {
System.out.println("[Rich Buyer] Paying tax for deal...");
return "Taxes paid: " + tax1 + ", " + tax2 + ", " + tax3;
}
}
public class MyProxyAgent implements MyInvocationHandler {
private IRichHouseBuyer target;
public IRichHouseBuyer getInstance(IRichHouseBuyer target){
this.target = target;
Class<?> clazz = target.getClass();
return (IRichHouseBuyer) MyProxy.newProxyInstance(
new MyClassLoader(), clazz.getInterfaces(), this);
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
beforeInvoke();
Object result = method.invoke(this.target, args);
afterInvoke();
return result;
}
private void beforeInvoke() {
System.out.println("[Proxy Agent] Prepare for paperwork...");
}
private void afterInvoke() {
System.out.println("[Proxy Agent] Closing deal successfully...");
}
}
public class MyProxyAgentTest {
public static void main(String[] args) {
MyProxyAgent proxyAgent = new MyProxyAgent();
IRichHouseBuyer richBuyer = proxyAgent.getInstance(new RichHouseBuyer());
richBuyer.findHouse();
System.out.println(richBuyer.payTaxes("10W", "2W", "20W"));
}
}
CGLib 动态代理
案例
与 JDK 代理的实现方式不同,CGLib 不关心这个类实现的接口,他是通过动态继承目标对象来实现动态代理的:
public class CGLibProxyAgent implements MethodInterceptor {
public Object getInstance(Class<?> clazz) {
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(clazz);
enhancer.setCallback(this);
return enhancer.create();
}
@Override
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
beforeInvoke();
Object result = methodProxy.invokeSuper(o, objects);
afterInvoke();
return result;
}
private void beforeInvoke() {
System.out.println("[Proxy Agent] Search house for buyer...");
}
private void afterInvoke() {
System.out.println("[Proxy Agent] House found! Contact house buyer for deal...");
}
}
public class CGLibProxyAgentTest {
public static void main(String[] args) {
try {
CGLibProxyAgent proxyAgent = new CGLibProxyAgent();
IHouseBuyer shanghaiBuyer = (IHouseBuyer)proxyAgent.getInstance(ShanghaiBuyer.class);
shanghaiBuyer.findHouse();
IHouseBuyer beijingBuyer = (IHouseBuyer)proxyAgent.getInstance(BeijingBuyer.class);
beijingBuyer.findHouse();
} catch (Exception e) {
e.printStackTrace(); }
}
}
原理简单分析
在测试方法里加上一行属性,让 CGLib 将动态代理生成的中间 class 文件输出:
System.setProperty(DebuggingClassWriter.DEBUG_LOCATION_PROPERTY, "cglib_proxy_class/");
可以看到对于一个代理类,CGLib 生成了 3 个 Class 文件:
ShanghaiBuyerEnhancerByCGLIB596e8770
ShanghaiBuyerFastClassByCGLIBb24a9524
ShanghaiBuyerEnhancerByCGLIB596e8770FastClassByCGLIB8df1674d
其中第一个是继承了 IHouseBuyer
的代理类,我们可以找到其中实现了 findHouse
方法。代理类会获得所有从父类继承来的方法,并且会有对应的 MethodProxy
方法。
public class ShanghaiBuyerEnhancerByCGLIB596e8770 extends ShanghaiBuyer implements Factory {
private static final Method CGLIBfindHouse0Method;
private static final MethodProxy CGLIBfindHouse0Proxy;
final void CGLIBfindHouse0() {
super.findHouse();
}
public final void findHouse() {
MethodInterceptor var10000 = this.CGLIBCALLBACK_0;
if (var10000 == null) {
CGLIBBIND_CALLBACKS(this);
var10000 = this.CGLIBCALLBACK_0;
}
if (var10000 != null) {
var10000.intercept(this, CGLIBfindHouse0Method, CGLIBemptyArgs, CGLIBfindHouse0Proxy);
} else {
super.findHouse();
}
}
}
而调用者正是调用 CGLib 的代理方法,从而调用真正的实现方法:
MethodProxy
的 invokeSuper()
方法非常关键,我们看一下它具体做了什么:
public Object invokeSuper(Object obj, Object[] args) throws Throwable {
try {
this.init();
MethodProxy.FastClassInfo fci = this.fastClassInfo;
return fci.f2.invoke(fci.i2, obj, args);
} catch (InvocationTargetException var4) {
throw var4.getTargetException();
}
}
}
public class MethodProxy {
private Signature sig1;
private Signature sig2;
private MethodProxy.CreateInfo createInfo;
private final Object initLock = new Object();
private volatile MethodProxy.FastClassInfo fastClassInfo;
public static MethodProxy create(Class c1, Class c2, String desc, String name1, String name2) {
MethodProxy proxy = new MethodProxy();
proxy.sig1 = new Signature(name1, desc);
proxy.sig2 = new Signature(name2, desc);
proxy.createInfo = new MethodProxy.CreateInfo(c1, c2);
return proxy;
}
}
private static class CreateInfo {
Class c1;
Class c2;
NamingPolicy namingPolicy;
GeneratorStrategy strategy;
boolean attemptLoad;
public CreateInfo(Class c1, Class c2) {
this.c1 = c1;
this.c2 = c2;
AbstractClassGenerator fromEnhancer = AbstractClassGenerator.getCurrent();
if (fromEnhancer != null) {
this.namingPolicy = fromEnhancer.getNamingPolicy();
this.strategy = fromEnhancer.getStrategy();
this.attemptLoad = fromEnhancer.getAttemptLoad();
}
}
}
private static class FastClassInfo {
FastClass f1;
FastClass f2;
int i1;
int i2;
private FastClassInfo() {}
}
上面的代码调用就是获取代理类对应的 FastClass,并执行代理方法。
CGLib 代理执行代理方法的效率之所以比 JDK 的高,是因为 CGlib 采用了 FastClass 机制,它的原理简单来说就是:为代理类和被代理类各生成一个类,这个类会为代理类或被代理类的方法分配一个索引。这个索引在方法调用时当作一个入参,FastClass 就可以直接定位要调用的方法并直接进行调用,省去了反射的过程,所以调用效率比 JDK 基于反射机制的动态代理高。
public class MethodProxy {
private void init() {
if (this.fastClassInfo == null) {
synchronized(this.initLock) {
if (this.fastClassInfo == null) {
MethodProxy.CreateInfo ci = this.createInfo;
MethodProxy.FastClassInfo fci = new MethodProxy.FastClassInfo();
fci.f1 = helper(ci, ci.c1);
fci.f2 = helper(ci, ci.c2);
fci.i1 = fci.f1.getIndex(this.sig1);
fci.i2 = fci.f2.getIndex(this.sig2);
this.fastClassInfo = fci;
this.createInfo = null;
}
}
}
}
}
进一步,FastClass 并不是在程序启动时就与代理类一起被创建的,而是在第一次执行 MethodProxy
的 invoke()
或 invokeSuper()
方法时生成的。上面的 helper
方法会判断如果 FastClass 对象已经在缓存中就直接取出,缓存中没取到就生成新的 FastClass 。
至此,CGLib 动态代理的原理我们就基本搞清楚了。
文章评论