既然有动态代理,那么肯定有静态代理,静态代理请参考
作用: 与静态代理一样,但动态代理解决了 静态代理中 1个静态代理,只代理1种类型的目标对象 的问题
在 JDK 动态代理中涉及如下角色:
- 目标类的接口 Interface
- 目标类 target
- 处理模版 Handler
- 在内存中生成的动态代理类
- java.lang.reflect.Proxy
废话少说先上代码
实例
目标类的接口
要有jdk动态代理,你的类必须实现接口
/** * UserService 是 目标类的接口 * login() 就是要被代理的方法 */public interface UserService { boolean login(String userName, String password);}复制代码
目标类
注意,要代理的方法,必须是从接口中实现的
/** * UserServiceImpl 是目标类,或者叫被代理的类 * login() 就是要被代理的方法 */public class UserServiceImpl implements UserService { @Override public boolean login(String userName, String password) { System.out.println("校验账号"); return false; }}复制代码
处理模版类
这是一个InvocationHandler的实现类
作用: 定义一种对目标方法做某种操作的模版(这里运用了面向抽象编程的思想) 如下,定义了一个在目标方法前后输出log的模版。
说明: LogHandler 有一个 属性 —— 目标对象,可以看到我特意把它定义成 Object 类型,而不是 UserService 类型,也就是说 LogHandler 的目标对象可以是任意类型,而不局限在 UserService。这样一来 LogHandler 就可以在 任意类型的任意方法 前后输出log,所以称它为处理模版
import java.lang.reflect.InvocationHandler;import java.lang.reflect.Method;/** * 这是一个自定义的处理模版,它将在 目标方法 的前后输出log */public class LogHandler implements InvocationHandler { // 目标对象 //private UserService target; private Object target; // 初始化时,将目标对象传进来 public LogHandler(Object target) { this.target = target; } /** * 定义一个在目标方法执行前后输出log的处理模版 * * @param proxy 动态代理对象 * @param method 目标方法 * @param args 目标的参数 */ @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { before(); //调用目标对象的目标方法,target 是初始化时传人的目标对象 Object result = method.invoke(target, args); after(); return result; } private void before() { System.out.println("调用目标方法之前输出的log"); } private void after() { System.out.println("调用目标方法之后输出的log"); }}复制代码
这里留一个疑问,谁去调用invoke()方法,方法中的三个参数是什么意思(虽然我已经写上去了)?
测试类
上面三个类构成了jdk动态代理的最小组成单位,接下来,编写测试类使用jdk动态代理
import java.lang.reflect.Proxy;public static void main(String[] args) { //创建一个目标对象 UserService userServiceImpl = new UserServiceImpl(); // 创建 目标对象的 处理模版 LogHandler userServiceLogHandler = new LogHandler(userServiceImpl); /** * Proxy.newProxyInstance 会根据 目标对象的 类加载器、接口的Class、目标对象的处理模版,动态生成一个代理类,并返回代理对象 * userServiceProxy 就是 动态创建出的代理对象,它实现了 UserService 接口 的login方法 */ UserService userServiceProxy = (UserService) Proxy.newProxyInstance(userServiceImpl.getClass().getClassLoader(), new Class[]{UserService.class}, userServiceLogHandler); userServiceProxy.login("wqlm", "123"); System.out.println("\n代理类的名称为" + userServiceProxy.getClass().getName());}复制代码
输出结果
/Library/Java/jdk1.8.0_201/Contents/Home/bin/java...调用目标方法之前输出的log校验账号调用目标方法之后输出的log代理类的名称为com.sun.proxy.$Proxy0Process finished with exit code 0复制代码
java.lang.reflect.Proxy
测试方法中就是使用该类的方法来生成代理对象的!
这是JDK动态代理中最为重要的一个类,通过该类的 newProxyInstance 方法,我们可以生成一个代理类
public static Object newProxyInstance(ClassLoader loader, Class [] interfaces, InvocationHandler h) throws IllegalArgumentException复制代码
如上,newProxyInstance 需要三个参数
- ClassLoader ,目标类target 的类加载器
- Class<?>[] ,目标类的接口 Interface 的Class
- InvocationHandler , 处理模版类 Handler
生成代理类的过程如下
- Proxy.newProxyInstance 方法, 通过传递给它的参数 Class<?>[] interfaces, InvocationHandler h 生成代理类的字节码
- Proxy 通过传递给它的参数(ClassLoader)来加载生成的代理类的字节码
- 返回加载完成的代理类的对象
动态生成的代理类
由于代理类是在运行时动态生成的,没有.java文件,也没有.class文件。但是根据上面的运行结果我们知道,生成的动态代理类叫 com.sun.proxy.$Proxy0,可以通过
byte[] bytes = ProxyGenerator.generateProxyClass("$Proxy0",new Class []{userServiceProxy.getClass()});String pathDir = "/Users/wqlm/Desktop";String path = "/$Proxy0.class";File f = new File(pathDir);path = f.getAbsolutePath() + path;f = new File(path);if (f.exists()) { f.delete();}try { f.createNewFile();} catch (IOException e) { e.printStackTrace();}try (FileOutputStream fos = new FileOutputStream(path)) { fos.write(bytes, 0, bytes.length);} catch (Exception e) { e.printStackTrace();} 复制代码
获取字节码流,在经过反编译,就可以得到大概的.java文件。以下是处理过的 (去掉了不需要关注掉内容)代理类的.java文件
import com.example.demo.service.UserService;import java.lang.reflect.InvocationHandler;import java.lang.reflect.Method;import java.lang.reflect.Proxy;import java.lang.reflect.UndeclaredThrowableException;/** * 代理类 继承了 Proxy,并实现了 目标接口 UserService */public final class $Proxy0 extends Proxy implements UserService { private static Method m3; static { try { m3 = Class.forName("com.sun.proxy.$Proxy0").getMethod("login", new Class[]{Class.forName("java.lang.String"), Class.forName("java.lang.String")}); } catch (NoSuchMethodException var2) { throw new NoSuchMethodError(var2.getMessage()); } catch (ClassNotFoundException var3) { throw new NoClassDefFoundError(var3.getMessage()); } } // 初始化时,将处理模版 传递给父类 public $Proxy0(InvocationHandler var1) { super(var1); } /** * 这就是代理方法,可以看到,它是同过调用父类的 处理模版 的invoke 方法,然后把自己,以及目标方法和参数传递进去 */ public final boolean login(String var1, String var2) { try { return ((Boolean) super.h.invoke(this, m3, new Object[]{var1, var2})).booleanValue(); } catch (RuntimeException | Error var4) { throw var4; } catch (Throwable var5) { throw new UndeclaredThrowableException(var5); } }}复制代码
- 动态代理类实现了目标类的接口,并实现了接口中的方法,该方法的实现逻辑是, 调用父类 —— Proxy 的 h 的 invoke()方法
- 其中h 是 在创建动态代理实例时 newProxyInstance(ClassLoader loader, Class<?>[]interfaces,InvocationHandler h) 传入的第3个参数InvocationHandler对象
- 在 InvocationHandler.invoke()中通过反射,调用目标对象
还记得在 处理模版那节留的疑问吗—— “留一个疑问,谁去调用invoke()方法,方法中的三个参数是什么意思” 现在我们知道了,是代理类去调用invoke(),它会传3个参数,自己(代理对象自己)、目标方法、参数列表
总结
JDK的动态代理
特点: 不需要显式实现与目标类相同的接口,而是将这种实现推迟到程序调用时由 JVM来实现,
- 即:在使用时再创建动态代理类 和 实例
- 静态代理则是在代理类实现时就指定与目标相同的接口
优点:一个代理类就可以代理多个目标类,避免重复、多余代码
缺点
- 相比与静态代理,效率低。 静态代理是直接调用目标对象方法,而动态代理则需要先生成类和对象,在通过Java反射机制间接调用目标对象的方法
- 应用场景局限, 由于每个代理类都继承了 java.lang.reflect.Proxy 类,而Java又只能支持单继承,导致不能针对类 创建代理类,只能针对接口 创建 代理类。即,动态代理只能代理实现了接口的类。
应用场景
- 需要代理的对象数量较多的情况下。数量较少时,推荐直接使用静态代理
- 面向切面编程时
与静态代理的区别
设计模式 | 代理类创建时机 | 原理 | 效率 | 能代理的目标类的数量 |
---|---|---|---|---|
动态代理 | 运行时动态创建 | 反射 | 低 | 能代理多个目标类 |
静态代理 | 运行前,先显示创建好 | / | 高 | 只能代理一个目标类 |