有一个用户接口和实现类,我们需要在登录成功前插入账号密码的检测,登录成功后插入欢迎的提示(简单起见全部用文字打印到控制台).
public interface User { void login(); } public class UserImpl implements User { @Override public void login() { System.out.println("登录成功"); } } 复制代码
我们首先很容易就会想到直接在源码上编写,实际中直接修改源码情况可能不被允许,符合设计模式中的开闭原则.那么我们可以用另一种方式,也就是静态代理来实现.
方法很简单,有点类似设计模式中的适配器模式.定义一个UserStaticProxy实现User接口然后组合User,在login方法中添加对应代码即可.
class UserStaticProxy implements User{ private User user; public UserStaticProxy(User user) { this.user = user; } @Override public void login() { System.out.println("账号密码检测"); user.login(); System.out.println("欢迎登录"); } } 复制代码
我们知道静态代理可以解决我们的需求,但是我们需要增强功能都要自己手动编写一个代理类,重复代码过多.这时就有考虑能否让jvm为我们自动在运行时创建一个代理类,我们只需提供信息即可.也就是将代理类抽象出来.
我们从我们编写的静态代理类入手,发现它肯定是需要实现接口,也就是我们准备要代理的类UserImpl所实现的接口User. 还需要一个参数是我们要代理的类User.其它暂时看不出来.
jdk提供的生成代理类的方法是调用Proxy的静态方法newProxyInstance.它需要三个参数.
public interface InvocationHandler { public Object invoke(Object proxy, Method method, Object[] args) throws Throwable; } 复制代码
然后代码就如下
public class UserDynamicProxy implements InvocationHandler { @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { return null; } public static void main(String[] args) { User o =(User) Proxy.newProxyInstance( User.class.getClassLoader(), new Class[]{User.class}, new UserDynamicProxy() ); o.login(); } 复制代码
我们生成了代理类,然后调用了它的login方法,结果什么也没有输出.
我们有必要看一看生成的代理类长什么样.因为代理类是运行时生成的,我们看不到,所以在编译运行时添加VM参数-Dsun.misc.ProxyGenerator.saveGeneratedFiles=true 用来将生成的代理类生成出来. 我们运行程序在idea中 com.sun.proxy下会有我们的代理$proxy0,它的内部结构长这样,我删减一部分暂时不需要看的.
public final class $Proxy0 extends Proxy implements User { private static Method m1; private static Method m2; private static Method m3; private static Method m0; public $Proxy0(InvocationHandler var1) throws { super(var1); } public final void login() throws { try { super.h.invoke(this, m3, (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")); m2 = Class.forName("java.lang.Object").getMethod("toString"); m3 = Class.forName("proxy.User").getMethod("login"); m0 = Class.forName("java.lang.Object").getMethod("hashCode"); } catch (NoSuchMethodException var2) { throw new NoSuchMethodError(var2.getMessage()); } catch (ClassNotFoundException var3) { throw new NoClassDefFoundError(var3.getMessage()); } } } 复制代码
可以看到这个代理类实现了User接口,而且它还默认继承了Proxy.当我们调用代理类的login方法时,它去调用了 super.h.invoke(this, m3, (Object[])null),有没有很熟悉它调用的是父类的h属性的invoke方法. 我们在创建代理类的时候InvocationHandler不就是需要实现invoke方法吗.
我们看一下代理类的构造方法,它把InvocationHandler赋值给了父类的构造方法.
public $Proxy0(InvocationHandler var1) throws { super(var1); } 复制代码
进入父类Proxy,查看到父类的属性h
protected InvocationHandler h; 复制代码
缕一下顺序,也就是当我们创建代理类时,它将InvocationHandler的实现类赋值给了父类的属性h,然后在我们调用login方法时,调用了父类h的invoke方法.也就是最终调用的我们的实现类UserDynamicProxy的invoke方法了.(绕了一大圈)
看一下代理类的invoke方法的三个参数,看名字我们就知道大概是啥
然后再来看看代理类$Proxy0中传递过来的三个参数.
super.h.invoke(this, m3, (Object[])null); 复制代码
第一个是这个代理类对象本身this,第二个是m3,这个m3对应是方法,我们看看$Proxy0 中的static块
static { try { m1 = Class.forName("java.lang.Object").getMethod("equals", Class.forName("java.lang.Object")); m2 = Class.forName("java.lang.Object").getMethod("toString"); m3 = Class.forName("proxy.User").getMethod("login"); m0 = Class.forName("java.lang.Object").getMethod("hashCode"); } catch (NoSuchMethodException var2) { throw new NoSuchMethodError(var2.getMessage()); } catch (ClassNotFoundException var3) { throw new NoClassDefFoundError(var3.getMessage()); } 复制代码
这里是反射的知识,反射不懂需要去补补了,即可不懂也应该能大概看懂,也就是m3就是User接口的方法login.第三个参数为null,因为我们的login方法没有参数,所以直接为null.当有参数时,也就我们调用 代理类.login(参数a),这个参数a会传给第三个参数.
继续缕一下,代理类调用login方法后会去调用invoke方法,同时invoke会携带好三个参数,代理类对象本身,对应的方法Method和方法的参数.
好了,那么我们如何实现我们的需求.无非就是在invoke方法上编写代码,我们先要将login的登陆成功打印出来先吧,调用method.invoke调用方法,它需要两个参数,一个是调用方法的对象,一个是方法的参数.参数我们已经有了从invoke方法的第三个参数里拿,但是调用方法的对象我们没有.所以我们还需要组合一下User.然后构造时将UserImpl传递给属性o,用于method的invoke调用.然后只需要在method.invoke前后执行相应的逻辑即可.
public class UserDynamicProxy implements InvocationHandler { private User o; public UserDynamicProxy(User o) { this.o = o; } @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { System.out.println("账号密码检测"); method.invoke(o,args); System.out.println("欢迎登录"); return null; } public static void main(String[] args) { User o =(User) Proxy.newProxyInstance( User.class.getClassLoader(), new Class[]{User.class}, new UserDynamicProxy(new UserImpl()) ); o.login(); } 输出结果:账号密码检测 登录成功 欢迎登录 复制代码
对比一下我们刚刚的静态代理的实现是不是发现很类似.
我们发现$proxy0是继承Proxy的然后实现我们的User接口,也就是说jdk提供的动态代理,只能对实现接口的实现类进行代理,因为java无法多继承.而cglib可以对不实现接口的类进行代理.