如果想增强一个方法的功能,无非就是直接在方法体内直接修改。但这也无非给一些有代码洁癖人士一丝丝不悦!于是乎我们即不想在原来的代码里修改,又不想把原有的代码重新写一次,那么前辈们就发明了代理.
注意:本文以 JdkProxy 为基础展开所有描述!
参与对象
那么一个代理过程参与的对象有以下几项:
- 目标接口
- 目标类(Target)
- 代理基类(Proxy)
- 生成的代理类
- 调用处理程序(InvocationHandler)
目标接口。
至于为什么要用接口,这是JdkProxy的理论知识。文章结束后你也会明白!- public interface Fight {
- /**
- * 射击
- */
- void shot();
- /**
- * 炸弹
- */
- void bomb();
- }
复制代码 目标类
就是需要被代理的类!- public class BeautifulCountryTarget implements Fight {
- private static final Logger LOGGER = LoggerFactory.getLogger(BeautifulCountryTarget.class);
- /**
- * 射击
- */
- @Override
- public void shot() {
-
- LOGGER.debug("M4 shot ---> big goose!");
-
- }
- /**
- * 炸弹
- */
- @Override
- public void bomb() {
-
- LOGGER.debug("HIMARS fire ---> big goose!");
- }
- }
复制代码 调试和输出结果
- public class JdkProxy {
-
-
- private static final Logger LOGGER = LoggerFactory.getLogger(JdkProxy.class);
- /**
- * 通过 Proxy.create 生成的对象是代理对象,基于 接口的代理对象,那么有以下几点是需要注意
- *
- * 1:生成的 代理对象 是实现了 目标接口
- * 2:生成的 代理对象 与 代理目标 是兄弟关系 (都实现了同一个目标接口)
- *
- */
- public static void main(String[] args) throws InterruptedException {
-
- // jdkProxy
- jdkProxyTest();
-
- }
-
-
- public static void jdkProxyTest() throws InterruptedException {
- //类加载器,负责把生成的class($Proxy???)文件加载到JVM
- ClassLoader loader = JdkProxy.class.getClassLoader();
- //需要代理的目标(对象)
- BeautifulCountryTarget target = new BeautifulCountryTarget();
- //增强目标方法的处理程序
- InvocationHandler handler = (proxy, method, args) -> {
- LOGGER.debug("大哥你在旁边看着喝Coffee!,武器开给,我来打!");
- //方法调用,(目标对象,参数)
- return method.invoke(target, args);
- };
- //创建代理人
- Fight w_k_l_Fight = (Fight)Proxy.newProxyInstance(
- loader,
- new Class[]{Fight.class},
- handler
- );
-
- //打印下类路径,方便使用 arthas 进行反张译
- LOGGER.debug("proxy<w_k_l_Fight> class {}", w_k_l_Fight.getClass());
-
- //代理人调用方法
- w_k_l_Fight.shot();
- w_k_l_Fight.bomb();
-
- }
- }
复制代码 打印出- 19:22:41.839 [main] DEBUG com.java.coffeetime.aop.JdkProxy -- proxy<w_k_l_Fight> class class jdk.proxy1.$Proxy0
- 19:22:41.842 [main] DEBUG com.java.coffeetime.aop.JdkProxy -- 大哥你在旁边看着喝Coffee!,武器开给,我来打!
- 19:22:41.842 [main] DEBUG com.java.coffeetime.aop.BeautifulCountryTarget -- M4 shot ---> big goose!
- 19:22:41.842 [main] DEBUG com.java.coffeetime.aop.JdkProxy -- 大哥你在旁边看着喝Coffee!,武器开给,我来打!
- 19:22:41.842 [main] DEBUG com.java.coffeetime.aop.BeautifulCountryTarget -- HIMARS fire ---> big goose!
复制代码 可以看出代理生效了,-_-!
那么通过 Proxy.create函数生成的是一字节码文件(至于怎么生成,太高端没去研究),它被 loader 加载到JVM,通过 debug只看了类名$Proxy0
模拟手写代理类
既然是系统自己生成的,那么我们自己可以自己写一个,不用系统生成的~。由理论知识可以写出以下 代理类- /**
- * 模拟 通过 Proxy.create 出来的 $Proxy?? 类,
- *
- */
- public class SimulateProxy extends Proxy implements Fight {
-
-
- protected SimulateProxy(InvocationHandler h) {
- super(h);
- }
-
-
- /**
- * 射击
- */
- @Override
- public void shot() {
- try {
- // 代理的方法
- Method pMethod = Fight.class.getMethod("shot");
- //调用增强处理器
- super.h.invoke(this, pMethod, null);
- } catch (Throwable e) {
- e.printStackTrace();
- }
- }
- /**
- * 炸弹
- */
- @Override
- public void bomb() {
- try {
- // 代理的方法
- Method pMethod = Fight.class.getMethod("bomb");
- //调用增强处理器
- super.h.invoke(this, pMethod, null);
- } catch (Throwable e) {
- e.printStackTrace();
- }
- }
- }
复制代码 调试输出
- public class JdkProxy {
-
-
- private static final Logger LOGGER = LoggerFactory.getLogger(JdkProxy.class);
- /**
- * 通过 Proxy.create 生成的对象是代理对象,基于 接口的代理对象,那么有以下几点是需要注意
- *
- * 1:生成的 代理对象 是实现了 目标接口
- * 2:生成的 代理对象 与 代理目标 是兄弟关系 (都实现了同一个目标接口)
- *
- */
- public static void main(String[] args) throws InterruptedException {
-
- // 模拟代理类
- simulateProxyTest();
-
- }
-
-
- public static void simulateProxyTest() {
-
- //需要代理的目标(对象)
- BeautifulCountryTarget target = new BeautifulCountryTarget();
- //增强目标方法的函数
- InvocationHandler handler = (proxy, method, args) -> {
- LOGGER.debug("大哥你在旁边看着喝Coffee!,武器开给,我来打!");
- return method.invoke(target, args);
- };
-
- //创建代理人 这里换成自己手写的
- Fight w_k_l_Fight = new SimulateProxy(handler);
- //代理人调用方法
- w_k_l_Fight.shot();
- w_k_l_Fight.bomb();
- }
- }
复制代码 同样输出- 19:38:23.950 [main] DEBUG com.java.coffeetime.aop.JdkProxy -- 大哥你在旁边看着喝Coffee!,武器开给,我来打!
- 19:38:23.951 [main] DEBUG com.java.coffeetime.aop.BeautifulCountryTarget -- M4 shot ---> big goose!
- 19:38:23.951 [main] DEBUG com.java.coffeetime.aop.JdkProxy -- 大哥你在旁边看着喝Coffee!,武器开给,我来打!
- 19:38:23.951 [main] DEBUG com.java.coffeetime.aop.BeautifulCountryTarget -- HIMARS fire ---> big goose!
复制代码 总结
我们先来看下系统生成的$Proxy0是什么样。
现在通过在main方法添加 System.setProperty("jdk.proxy.ProxyGenerator.saveGeneratedFiles", "true");可以把系统生成的代理class文件写到目录里root/jdk/proxy1/$Proxy0.class。我这里直接贴出来,方便和上下文对比。- public final class $Proxy0 extends Proxy implements Fight {
- private static final Method m0;
- private static final Method m1;
- private static final Method m2;
- private static final Method m3;
- private static final Method m4;
- public $Proxy0(InvocationHandler param1) {
- super(var1);
- }
- public final int hashCode() {
- try {
- return (Integer)super.h.invoke(this, m0, (Object[])null);
- } catch (RuntimeException | Error var2) {
- throw var2;
- } catch (Throwable var3) {
- throw new UndeclaredThrowableException(var3);
- }
- }
- public final boolean equals(Object var1) {
- try {
- return (Boolean)super.h.invoke(this, m1, new Object[]{var1});
- } catch (RuntimeException | Error var2) {
- throw var2;
- } catch (Throwable var3) {
- throw new UndeclaredThrowableException(var3);
- }
- }
- public final String toString() {
- 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 void shot() {
- try {
- super.h.invoke(this, m3, (Object[])null);
- } catch (RuntimeException | Error var2) {
- throw var2;
- } catch (Throwable var3) {
- throw new UndeclaredThrowableException(var3);
- }
- }
- public final void bomb() {
- try {
- super.h.invoke(this, m4, (Object[])null);
- } catch (RuntimeException | Error var2) {
- throw var2;
- } catch (Throwable var3) {
- throw new UndeclaredThrowableException(var3);
- }
- }
- static {
- try {
- m0 = Class.forName("java.lang.Object").getMethod("hashCode");
- m1 = Class.forName("java.lang.Object").getMethod("equals", Class.forName("java.lang.Object"));
- m2 = Class.forName("java.lang.Object").getMethod("toString");
- m3 = Class.forName("com.java.coffeetime.aop.Fight").getMethod("shot");
- m4 = Class.forName("com.java.coffeetime.aop.Fight").getMethod("bomb");
- } catch (NoSuchMethodException var2) {
- throw new NoSuchMethodError(var2.getMessage());
- } catch (ClassNotFoundException var3) {
- throw new NoClassDefFoundError(var3.getMessage());
- }
- }
- private static Lookup proxyClassLookup(Lookup var0) throws IllegalAccessException {
- if (var0.lookupClass() == Proxy.class && var0.hasFullPrivilegeAccess()) {
- return MethodHandles.lookup();
- } else {
- throw new IllegalAccessException(var0.toString());
- }
- }
- }
复制代码 那么对比下系统生成的和自己手写的代理类区别 SimulateProxy和 $Proxy0,可以看出系统生成的代理类比我们手写的比较优雅一些!
- 利用静态方法把需要代理的目标方法在加载阶段就初始化了,而不需要每次调用的时候去通过反射获取到.
- 多了 equals,toString,hashCode,有意思的是代理类和目标类 equals 是 ture, 但是不同的实例!
- 19:49:59.622 [main] DEBUG com.java.coffeetime.aop.JdkProxy -- w_k_l_Fight equals target ? true
- 19:49:59.622 [main] DEBUG com.java.coffeetime.aop.JdkProxy -- w_k_l_Fight == target ? false
复制代码 - 通过生成的代理类可以看出,的确是实现了 目标接口(和目标类一样),和代理类是兄弟关系,但又胜似兄弟!
- 对异常的处理
本文示例代码
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作! |