两万字吐血总结,代理模式及手写实现动态代理(aop原理,基于jdk动态代理)

Jennifer ·
更新时间:2024-11-13
· 829 次阅读

代理模式及手实现动态代理(aop原理)一、代理模式1. 定义2. 示例(1)静态代理(2)动态代理3. 通用类图4. 代理模式的优点二、jdk动态代理实现原理1. jdk动态代理源码分析(通过该示例学会阅读源码的方法)2.jdk动态代理生成的代理类的源码3.总结三、手写实现jdk动态代理 一、代理模式

熟悉代理模式的可以直接点击目录第二章,jdk动态代理实现原理,本文的精髓所在,通过这个例子,教大家如何去学习源码。

1. 定义

代理模式(Proxy Pattern) 是一个使用频率非常高的设计模式,其定义如下:

Provide a surrogate or placeholder for another object to control access to it.(为其他对象提供一种代理以控制对这个对象的访问。)

2. 示例 (1)静态代理

游戏者接口

/** * 游戏者接口 */ public interface IGamePlayer { /** * 登录游戏 * * @param user 用户名 * @param password 用户密码 */ void login(String user, String password); /** * 玩游戏 */ void play(); }

游戏者

public class GamePlayer implements IGamePlayer { private String name = ""; public GamePlayer(String name) { this.name = name; } /** * 登录 */ @Override public void login(String user, String password) { System.out.println("登录名为" + user + "的用户" + this.name + "登录成功!"); } /** * 玩游戏 */ @Override public void play() { System.out.println(this.name + "的账号正在进行游戏"); } }

代练者

/** * 代练类,负责帮助玩家代练,按时计费 */ public class GamePlayerProxy implements IGamePlayer { /** * 代练对象 */ private IGamePlayer gamePlayer = null; /** * 代练一小时价格 */ private int PER_HUOR_COST = 5; /** * 通过构造方法传入需要代练服务的对象 */ public GamePlayerProxy(IGamePlayer gamePlayer) { this.gamePlayer = gamePlayer; } @Override public void login(String user, String password) { System.out.println("代练开始登录账号"); this.gamePlayer.login(user, password); } @Override public void play() { long strTime = System.currentTimeMillis(); Date date = new Date(strTime); Calendar calendar = Calendar.getInstance(); calendar.setTime(date); System.out.println("代练开始时间是" + date); this.gamePlayer.play(); long endTime = System.currentTimeMillis(); int costTime = (int) (endTime - strTime); //使用毫秒模拟小时,给开始时间增加消耗的毫秒数个小时 calendar.add(Calendar.HOUR, costTime); System.out.println("代练结束时间是" + calendar.getTime()); System.out.println("共计代练" + costTime + "小时,收费" + costTime * PER_HUOR_COST + "元。"); } }

场景类

public class Client { public static void main(String[] args) { //定义一个痴迷的玩家 IGamePlayer player = new GamePlayer("张三"); //然后再定义一个代练者 IGamePlayer proxy = new GamePlayerProxy(player); //登陆账号 proxy.login("zhangSan", "password"); //开始代练 proxy.play(); } }

测试结果
静态代理测试结果

(2)动态代理

动态代理步骤:
1.创建一个实现接口InvocationHandler的类,它必须实现invoke方法
2.创建被代理的类以及接口
3.通过Proxy的静态方法
newProxyInstance(ClassLoaderloader, Class[] interfaces, InvocationHandler h)创建一个代理
4.通过代理调用方法

Person接口

public interface Person { void hello(); }

ZhangSan实现类

public class ZhangSan implements Person { @Override public void hello() { System.out.println("我是张三,大家好"); } }

MyInvocationHandler

public class MyInvocationHandler implements InvocationHandler { private ZhangSan zhangSan; public MyInvocationHandler(ZhangSan zhangSan) { this.zhangSan = zhangSan; } @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { before(); Object o = method.invoke(zhangSan, args); after(); return o; } public void before() { System.out.println("动态代理前置处理"); } public void after() { System.out.println("动态代理后置处理"); } } Test类public class Test { public static void main(String[] args) { try { Person person = (Person) Proxy.newProxyInstance(Test.class.getClassLoader(), ZhangSan.class.getInterfaces(), new MyInvocationHandler(new ZhangSan())); person.hello(); } catch (Exception e) { e.printStackTrace(); } } } 测试结果
动态代理测试结果 3. 通用类图

代理模式的通用类如图所示,如果不能理解也可以先看下面的示例,再返回来重新查看通用模型
代理模式通用类图
代理模式也叫做委托模式,它是一项基本设计技巧。许多其他的模式,如状态模式、策
略模式、访问者模式本质上是在更特殊的场合采用了委托模式,而且在日常的应用中,代理 模式可以提供非常好的访问控制。在一些著名开源软件中也经常见到它的身影,如Spring框架中的aop就是基于代理模式(更确切的说是动态代理)实现的。我们先看一下类图中三个组件的解释:

- Subject抽象主题角色

抽象主题类可以是抽象类也可以是接口,是一个最普通的业务类型定义,无特殊要求。

/** * 业务接口 */ public interface ISubject { /** * 定义一个方法 */ void request(); }

- RealSubject具体主题角色

也叫做被委托角色、被代理角色。它才是冤大头,是业务逻辑的具体执行者,这个往往是我们需要关注的重点。

/** * 实际业务对象 */ public class RealISubject implements ISubject { /** * 业务实现方法 */ @Override public void request() { //业务逻辑处理 } }

- Proxy代理主题角色

也叫做委托类、代理类。它负责对真实角色的应用,把所有抽象主题类定义的方法限制
委托给真实主题角色实现,并且在真实主题角色处理完毕前后做预处理和善后处理工作。

public class Proxy implements ISubject { /** * 被代理对象的引用 */ private ISubject subject = null; /** * 默认被代理对象 */ public Proxy() { this.subject = new Proxy(); } /** * 传入被代理对象 */ public Proxy(ISubject subject) { this.subject = subject; } /** * 代理对象控制被代理对象行为,添加预处理和后置处理,如可以添加日志打印,权限控制,事务管理 */ @Override public void request() { before(); this.subject.request(); after(); } public void before() { //预处理 } public void after() { //后置处理 } } 4. 代理模式的优点 职责清晰
主题角色(被代理对象)就是实现实际的业务逻辑,不用关心其他非本职责的逻辑处理,如日志,事务的开启、关闭、回滚等,就可以通过后期代理来实现。在spring中常说的AOP(面向切面编程)的思想就是将代码进行横向切分,通过预编译方式和运行期间动态代理实现程序功能的统一维护。它可以将模块划分的更加细致,减少各个模块和公用模块之间的耦合,让我们将关注点转移到业务本身。 高扩展性
具体主题角色(被代理对象)是随时都会发生变化的,只要它实现了接口,甭管它如何变化,都逃不脱如来佛的手掌(接口),那我们的代理类完全就可以在不做任何修改的情况下使用。在这种情况下,我们的代理类只需直接调用真实对象的业务方法即可,我们只需要关心流程控制,和一些其他的逻辑。如我们在使用业务代码前,进行权限验证,如进入业务代码前,记录此次调用,将调用记录(如时间,调用方法,调用地点)写入数据库来方便后台监控用户的行为。同样的,我们在主题角色(被代理对象)中只需专注于业务逻辑变更即可。 二、jdk动态代理实现原理 1. jdk动态代理源码分析(通过该示例学会阅读源码的方法)

以之前的动态代理 代码为例,我们阅读jdk动态代理源码分析其实现原理。在阅读源码时我们最重要的是找对切入点,在这段代码中,没有复杂的逻辑,很明显,我们以newProxyInstance(ClassLoaderloader, Class[] interfaces, InvocationHandler h)这个方法为切入点。我们在代码中着重观察我们的代理类生成的路径,f3进入这个方法。这里如果不懂或者暂时不感兴趣可直接查看下一部分和总结部分查看结论。推荐大家最好可以跟着自己走一遍。大家也可以查看我的另一篇文章ArrayList核心源码分析-扩容机制(jdk1.8)来练习源码阅读能力。

newProxyInstance:Proxy.java(java.lang.reflect)
在这个方法中,找到关键代码,这一行代码返回代理类的class文件,下面再通过反射创造实例返回。(我这里因为篇幅原因省去了这些类的具体代码,如果看起来有点晕,强烈建议自己动手试试,或者直接查看结论)

/* * Look up or generate the designated proxy class. * 查找或生成指定的代理类。 */ Class cl = getProxyClass0(loader, intfs);

再次点击f3进入getProxyClass0方法

getProxyClass0:Proxy (java.lang.reflect)
这个方法非常简单,我们观察它的返回值

// If the proxy class defined by the given loader implementing // the given interfaces exists, this will simply return the cached copy; // otherwise, it will create the proxy class via the ProxyClassFactory //如果存在由实现了给定接口的给定加载器定义的代理类,则将仅返回缓存的副本; //否则,它将通过ProxyClassFactory创建代理类。在java中是采用了享元模式存储了已经生成的代理类的class文件 return proxyClassCache.get(loader, interfaces);

再次f3进入get方法

get:WeakCache (java.lang.reflect)
我们可以观察到最终总是返回value

while (true) { if (supplier != null) { // supplier might be a Factory or a CacheValue instance V value = supplier.get(); if (value != null) { return value; } } ...

f3进入get方法

public interface Supplier { /** * Gets a result. * * @return a result */ T get(); }

我们走到这里无法确定到底调用的是那个实现类,此时可以在上一层V value = supplier.get();处打上断点。然后debug运行Test类,可以看到停到断点处。
断点运行
我们可以观察到调用的是WeakCache$Factory类的方法。找到该类的这个方法
在这里插入图片描述

get:WeakCache$Factory (java.lang.reflect)即WeakCache的内部类Factory
观察该方法,我们发现最终return value,再细看有一行create value,发现这一行关键代码,==在这行打上断点。==我们每次进行类的跳转如果不是很清楚就可以在每次跳转前打上断点。

value = Objects.requireNonNull(valueFactory.apply(key, parameter));

这段代码requireNonNull()做一个非空检查,核心是valueFactory.apply(key, parameter),再次f3进入该方法在这里插入图片描述
可以看到这是一个接口,我们无法判断是那个实现类,我们继续使用上次的方法,查看到底调用的是哪个实现类,在debug中f6,走到我们上一步中打下的断点。
在这里插入图片描述
我们可以看到代理类的class文件从这个方法中生成,我们观察debug窗口变量监控
在这里插入图片描述
可以看到发现我们调用的是Proxy$ProxyClassFactory的apply()方法。找到该类的该方法
在这里插入图片描述

apply:Proxy$ProxyClassFactory (java.lang.reflect)

我们观察到该方法中有一行代码,生成指定的proxy class,这就是我们要找的东西啦。
在这里插入图片描述
我们可以看到该方法生成class文件的byte流,我们再进入方法,可以观察到其实是在使用文件写入的方式,动态写入java文件,然后编译java文件生成class文件,最后将其转换为byte流返回。其实动态代理和静态代理的区别就是:静态是在运行程序时已经生成了class文件并且加载进了jvm。我们看到下一行defineClass0()其实就是将该class文件动态的加载到jvm中,显然动态代理就是运行时加载代理类的class。我们会在第三部分会自己手写实现这个gennerate方法,所以不在重复。可以从第三部分中获得更多的细节。

2.jdk动态代理生成的代理类的源码

改变之前的Test测试类代码如下

public class Test { public static void main(String[] args) { try { Person person = (Person) Proxy.newProxyInstance(Test.class.getClassLoader(), ZhangSan.class.getInterfaces(), new MyInvocationHandler(new ZhangSan())); //之前源码分析中的关键方法 byte[] bytes = ProxyGenerator.generateProxyClass("$Proxy0", new Class[]{Person.class}); //在哪里存储生成的class文件,因为idea中有反编译插件,所以我们可以看到该java文件源码 FileOutputStream f = new FileOutputStream("src/main/java/com/jdk_proxy/$Proxy0.class"); f.write(bytes); person.hello(); System.out.println(person.getClass()); } catch (Exception e) { e.printStackTrace(); } } }

生成的class文件如下,注意看注释行

// // Source code recreated from a .class file by IntelliJ IDEA // (powered by Fernflower decompiler) // import com.jdk_proxy.Person; import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import java.lang.reflect.Proxy; import java.lang.reflect.UndeclaredThrowableException; //可以看到该类实现了newProxyInstance:Proxy.java(java.lang.reflect)这个方法传入的接口参数,继承了Proxy类 public final class $Proxy0 extends Proxy implements Person { private static Method m1; private static Method m3; private static Method m2; private static Method m0; //我们可以看到它会传递InvocationHandler至父类Proxy public $Proxy0(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 hello() throws { try { //注意这里的三个参数,我们可以看到他将this和在staic块中反射生成的接口中的方法,和调用代理传入的参数 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); } } //在加载时通过反射从实现的接口中获得接口中的Method对象。 static { try { m1 = Class.forName("java.lang.Object").getMethod("equals", Class.forName("java.lang.Object")); m3 = Class.forName("com.jdk_proxy.Person").getMethod("hello"); 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类

public class Proxy implements java.io.Serializable { ... /** * the invocation handler for this proxy instance. * @serial */ protected InvocationHandler h; protected Proxy(InvocationHandler h) { Objects.requireNonNull(h); this.h = h; } ...

InvocationHandler类

public interface InvocationHandler { public Object invoke(Object proxy, Method method, Object[] args) throws Throwable; } 3.总结 动态代理实现过程(结合上面的class文件和我们示例程序) 通过实现 InvocationHandler 接口,自定义调用处理器; 通过调用 newProxyInstance:Proxy.java(java.lang.reflect) 指定ClassLoader对象和一组 interface 来创建动态代理类; 通过newProxyInstance传入的接口来动态的生成代理类java文件(下一部分有自己实现代码,可以详细了解原理),然后进行编译,最后加载到jvm中。代理类中的所有方法都调用了newProxyInstance:Proxy.java(java.lang.reflect) 中传入的InvocationHandler的invoke方法。也就是说我们的代理类重写了接口中的所有方法,然后再这些方法中只做了一件事,调用invoke:InvocationHandler。 通过反射创建代理类对象返回。 源码分析总结
我们将断点打ProxyGenerator.generateProxyClass("$Proxy0", new Class[]{Person.class});这个方法上,debug运行
在这里插入图片描述
生成Java文件,编译成class文件,然后加载到jvm中,整个调用链如下。在学习时如果上面源码分析部分有点晕可以利用这种方法,快速定位代码。
在这里插入图片描述 三、手写实现jdk动态代理

Generator

public class Generator { static String rt = "\r\n"; public static Class generator(Class[] interfaces, MyClassLoader loader, MyInvocationHandler h) throws Exception { try { // 1.创建代理类java源码文件,写入到硬盘中.. Method[] methods = interfaces[0].getMethods(); String name = interfaces[0].getName(); String packageName = name.substring(0, name.lastIndexOf(".")); StringBuilder stringBuilder = new StringBuilder(); //包名 stringBuilder.append("package "); stringBuilder.append(packageName); stringBuilder.append(";"); stringBuilder.append(rt); //导入的类 stringBuilder.append("import java.lang.reflect.Method;"); stringBuilder.append(rt); stringBuilder.append("import com.proxy_design.*;"); stringBuilder.append(rt); //类的声明 stringBuilder.append("public class Proxy0 extends MyProxy implements "); stringBuilder.append(interfaces[0].getName()); stringBuilder.append("{"); stringBuilder.append(rt); //构造方法 stringBuilder.append("\tpublic Proxy0(MyInvocationHandler h){"); stringBuilder.append(rt); stringBuilder.append("\t\tsuper(h);"); stringBuilder.append(rt); stringBuilder.append("\t}"); stringBuilder.append(rt); //添加重写后的方法,在所有方法中调用super.h.invoke方法即可 stringBuilder.append(getMethodString(methods, interfaces[0])); stringBuilder.append(rt); stringBuilder.append("}"); stringBuilder.append(rt); String proxyClass = stringBuilder.toString(); // 2. 将代理类源码文件写入硬盘中,根据自己的目录输入 String filename = "src/main/java/" + packageName.replace(".", "/") + "/Proxy0.java"; File f = new File(filename); FileWriter fw = new FileWriter(f); fw.write(proxyClass); fw.flush(); fw.close(); // 3.使用JavaJavaCompiler 编译该Proxy0源代码 获取class文件 JavaCompiler compiler = ToolProvider.getSystemJavaCompiler(); StandardJavaFileManager fileMgr = compiler.getStandardFileManager(null, null, null); Iterable units = fileMgr.getJavaFileObjects(filename); JavaCompiler.CompilationTask t = compiler.getTask(null, fileMgr, null, null, null, units); t.call(); fileMgr.close(); return loader.findClass("Proxy0"); } catch (Exception e) { e.printStackTrace(); } throw new ClassNotFoundException(); } public static String getMethodString(Method[] methods, Class intf) { StringBuilder proxyMe = new StringBuilder(); for (int i = 0; i < methods.length; i++) { proxyMe.append("\tprivate static Method m").append(i).append(";").append(rt); } for (int i = 0; i < methods.length; i++) { proxyMe.append("\tpublic void "); proxyMe.append(methods[i].getName()); proxyMe.append("(){"); proxyMe.append(rt); proxyMe.append("\t\ttry {"); proxyMe.append(rt); proxyMe.append("\t\t\tsuper.h.invoke(this,m"); proxyMe.append(i); proxyMe.append(",null);"); proxyMe.append(rt); proxyMe.append("\t\t} catch (Throwable throwable) {"); proxyMe.append(rt); proxyMe.append("\t\t\tthrowable.printStackTrace();"); proxyMe.append(rt); proxyMe.append("\t\t}"); proxyMe.append(rt); proxyMe.append("\t}"); proxyMe.append(rt); } //从接口中反射获得所有方法 proxyMe.append("\tstatic {"); proxyMe.append(rt); proxyMe.append("\t\ttry{"); proxyMe.append(rt); for (int i = 0; i < methods.length; i++) { proxyMe.append("\t\t\tm"); proxyMe.append(i); proxyMe.append("="); proxyMe.append(intf.getName()); proxyMe.append(".class.getMethod(\""); proxyMe.append(methods[i].getName()); proxyMe.append("\",new Class[]{});"); proxyMe.append(rt); } proxyMe.append("\t\t} catch (NoSuchMethodException var2) {"); proxyMe.append(rt); proxyMe.append("\t\t\tthrow new NoSuchMethodError(var2.getMessage());"); proxyMe.append(rt); proxyMe.append("\t\t}"); proxyMe.append(rt); proxyMe.append("\t}"); return proxyMe.toString(); } }

MyClassLoader

public class MyClassLoader extends ClassLoader { @Override protected Class findClass(String name) throws ClassNotFoundException { //class文件将生成的位置 File file = new File("src/main/java/com/proxy_design/Proxy0.class"); if (file.exists()) { try { //将文件转换为byte流 FileInputStream fis = new FileInputStream(file); ByteArrayOutputStream bos = new ByteArrayOutputStream(1000); byte[] b = new byte[1000]; int n; while ((n = fis.read(b)) != -1) { bos.write(b, 0, n); } fis.close(); bos.close(); byte[] buffer = bos.toByteArray(); //加载类返回类,此时静态块中会被调用 return defineClass("com.proxy_design." + name, buffer, 0, buffer.length); } catch (IOException e) { e.printStackTrace(); } } return super.findClass(name); }

}
```

MyInvocationHandler

public interface MyInvocationHandler { public Object invoke(Object proxy, Method method, Object[] args) throws Throwable; }

MyInvocationHandlerImp

public class MyInvocationHandlerImp implements MyInvocationHandler { private ZhangSan zhangSan; public MyInvocationHandlerImp(ZhangSan zhangSan) { this.zhangSan = zhangSan; } @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { System.out.println("动态代理前置处理"); //调用被代理对象的真实方法 Object o = method.invoke(zhangSan, args); System.out.println("动态代理后置处理"); return o; } }

MyProxy

public class MyProxy { /** * 在使用动态代理生成的proxy类中,他会继承本类,然后在构造方法中传入h */ protected MyInvocationHandler h; public MyProxy(MyInvocationHandler h) { this.h = h; } public static Object newProxyInstance(MyClassLoader loader, Class[] interfaces, MyInvocationHandler h) throws Exception { Class proxy = Generator.generator(interfaces, loader, h); Constructor proxyConstructor = proxy.getConstructor(MyInvocationHandler.class); return proxyConstructor.newInstance(h); } }

Test

public class Test { public static void main(String[] args) { try { Person proxyMapper = (Person) MyProxy.newProxyInstance(new MyClassLoader(), ZhangSan.class.getInterfaces(), new MyInvocationHandlerImp(new ZhangSan()) ); proxyMapper.hello(); } catch (Exception e) { e.printStackTrace(); } } }

Person、ZhangSan和上面一样。(动态代理示例)

生成的Proxy类源码

public class Proxy0 extends MyProxy implements com.proxy_design.Person{ public Proxy0(MyInvocationHandler h){ super(h); } private static Method m0; //实际生效方法 public void hello(){ try { super.h.invoke(this,m0,null); } catch (Throwable throwable) { throwable.printStackTrace(); } } static { try{ m0=com.proxy_design.Person.class.getMethod("hello",new Class[]{}); } catch (NoSuchMethodException var2) { throw new NoSuchMethodError(var2.getMessage()); } } }
作者:Java新生代



代理 动态代理 aop 代理模式 jdk 动态

需要 登录 后方可回复, 如果你还没有账号请 注册新账号