代理模式

代理模式就是设置一个中间代理来控制访问原目标对象,以达到增强原对象的功能和简化访问。简而言之,代理对象=增强代码+目标对象

11、动态代理模式 - 图1

  • RealSubject(目标对象):真实的业务逻辑类。比如房东,它的action是租房
  • Subject:定义代理类和RealSubject的公共对外方法。比如租房
  • Proxy:代理类,帮助RealSubject去完成action,同时还能增强action

静态代理类

首先编写Subject接口,定义rent方法

  1. public interface Subject {public void rent();}

接着编写一个房东类,房东需要去租房,是具体的逻辑实现

  1. public class LandLord implements Subject {
  2. @Override
  3. public void rent() {
  4. System.out.println("房东要租房");
  5. }
  6. }

编写一个中介类,负责帮助房东租房

  1. public class Proxy implements Subject {
  2. public LandLord landLord;
  3. public Proxy(LandLord landLord){
  4. this.landLord = landLord;
  5. }
  6. @Override
  7. public void rent() {
  8. System.out.println("带你去看房");
  9. landLord.rent();
  10. System.out.println("带你签合同");
  11. }
  12. }

最后就是真正的房客去租房了

  1. public class Client {
  2. public static void main(String[] args) {
  3. LandLord landLord = new LandLord();
  4. Proxy proxy = new Proxy(landLord);
  5. proxy.rent();
  6. }
  7. }

缺陷:程序员要手动为每一个目标类编写对应的代理类。如果当前系统已经有成百上千个类,为这些目标类添加代理类,工作量太大了,因此增强目标类(比如打日志)变得很难处理。

动态代理

动态代理利用JDK API,动态的在内存中构建代理对象,从而实现对目标对象的代理功能。

静态代理和动态代理的区别:

  • 静态代理在编译时就已经实现,编译完成后代理类是一个实际的class文件
  • 动态代理是在运行时动态生成,即编译完成后没有实际的class文件,而是在运行时动态生成类字节码,并加载到JVM中

以上面的租房为例,现在房东除了租房还要卖房

  1. public interface Subject {
  2. public void rent();
  3. public void sale();
  4. }
  5. public class LandLord implements Subject {
  6. @Override
  7. public void rent() {
  8. System.out.println("房东要租房");
  9. }
  10. @Override
  11. public void sale() {
  12. System.out.println("房东要卖房子");
  13. }
  14. }

编写中介类ProxyHandler

  1. public class ProxyHandler {
  2. public Object newProxyInstance(Object targetObject){
  3. InvocationHandler h = new InvocationHandler() {
  4. /**
  5. *
  6. * @param proxy:代理对象,给jdk使用,任何时候都不要动这个对象
  7. * @param method:当前要执行的目标对象的方法
  8. * @param args:方法调用时的参数
  9. * @return
  10. * @throws Throwable
  11. */
  12. @Override
  13. public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
  14. System.out.println("带你去看房");
  15. Object result = method.invoke(targetObject, args);
  16. System.out.println("带你去签合同");
  17. return result;
  18. }
  19. };
  20. ClassLoader loader = targetObject.getClass().getClassLoader();
  21. Class<?>[] interfaces = targetObject.getClass().getInterfaces();
  22. return Proxy.newProxyInstance(loader, interfaces, h);
  23. }
  24. }

测试

  1. public class Client {
  2. public static void main(String[] args) {
  3. ProxyHandler proxyHandler=new ProxyHandler();
  4. Subject subject=(Subject) proxyHandler.newProxyInstance(new LandLord());
  5. subject.rent();
  6. System.out.println("==================");
  7. subject.sale();
  8. }
  9. }

在生成代理内对象的时候,最重要的方法是Proxy.newProxyInstance,方法的参数要求传入接口,这样子返回的对象就继承了这些接口,具体的实现原理看下面的介绍。

Java Proxy的原理

代理类是动态生成的,该代理类肯定继承了Subject接口,要不然怎么能调用目标对象的方法。其次为了达到增强目标对象的功能,代理类的方法中不仅要调用目标方法,还要有增强功能。由于字节码是在内存中生成的,为了方便学习,我们将字节码保存到本地。

  1. package com.zstu.proxy;
  2. import sun.misc.ProxyGenerator;
  3. import java.io.FileOutputStream;
  4. public class GenerateClass {
  5. public static void generateClassFile(Class clazz, String proxyName) throws Exception{
  6. byte[] classFile = ProxyGenerator.generateProxyClass(proxyName, new Class[]{clazz});
  7. FileOutputStream fileOutputStream = new FileOutputStream(proxyName + ".class");
  8. fileOutputStream.write(classFile);
  9. fileOutputStream.close();
  10. }
  11. public static void main(String[] args) throws Exception{
  12. generateClassFile(Subject.class, "proxy");
  13. }
  14. }

生成的proxy.class我们用反编译工具查看

  1. //
  2. // Source code recreated from a .class file by IntelliJ IDEA
  3. // (powered by Fernflower decompiler)
  4. //
  5. import com.zstu.proxy.Subject;
  6. import java.lang.reflect.InvocationHandler;
  7. import java.lang.reflect.Method;
  8. import java.lang.reflect.Proxy;
  9. import java.lang.reflect.UndeclaredThrowableException;
  10. public final class proxy extends Proxy implements Subject {
  11. private static Method m1;
  12. private static Method m4;
  13. private static Method m2;
  14. private static Method m3;
  15. private static Method m0;
  16. public proxy(InvocationHandler var1) throws {
  17. super(var1);
  18. }
  19. public final boolean equals(Object var1) throws {
  20. try {
  21. return (Boolean)super.h.invoke(this, m1, new Object[]{var1});
  22. } catch (RuntimeException | Error var3) {
  23. throw var3;
  24. } catch (Throwable var4) {
  25. throw new UndeclaredThrowableException(var4);
  26. }
  27. }
  28. public final void rent() throws {
  29. try {
  30. super.h.invoke(this, m4, (Object[])null);
  31. } catch (RuntimeException | Error var2) {
  32. throw var2;
  33. } catch (Throwable var3) {
  34. throw new UndeclaredThrowableException(var3);
  35. }
  36. }
  37. public final String toString() throws {
  38. try {
  39. return (String)super.h.invoke(this, m2, (Object[])null);
  40. } catch (RuntimeException | Error var2) {
  41. throw var2;
  42. } catch (Throwable var3) {
  43. throw new UndeclaredThrowableException(var3);
  44. }
  45. }
  46. public final void sale() throws {
  47. try {
  48. super.h.invoke(this, m3, (Object[])null);
  49. } catch (RuntimeException | Error var2) {
  50. throw var2;
  51. } catch (Throwable var3) {
  52. throw new UndeclaredThrowableException(var3);
  53. }
  54. }
  55. public final int hashCode() throws {
  56. try {
  57. return (Integer)super.h.invoke(this, m0, (Object[])null);
  58. } catch (RuntimeException | Error var2) {
  59. throw var2;
  60. } catch (Throwable var3) {
  61. throw new UndeclaredThrowableException(var3);
  62. }
  63. }
  64. static {
  65. try {
  66. m1 = Class.forName("java.lang.Object").getMethod("equals", Class.forName("java.lang.Object"));
  67. m4 = Class.forName("com.zstu.proxy.Subject").getMethod("rent");
  68. m2 = Class.forName("java.lang.Object").getMethod("toString");
  69. m3 = Class.forName("com.zstu.proxy.Subject").getMethod("sale");
  70. m0 = Class.forName("java.lang.Object").getMethod("hashCode");
  71. } catch (NoSuchMethodException var2) {
  72. throw new NoSuchMethodError(var2.getMessage());
  73. } catch (ClassNotFoundException var3) {
  74. throw new NoClassDefFoundError(var3.getMessage());
  75. }
  76. }
  77. }

可见,当代理对象调用sale方法的时候,首先会调用InvocationHandler的invoke方法,这个方法里添加了增强功能,并会调用目标对象的sale方法

mybatis的模拟

我们知道,在mybatis中只要写一个接口,那么mybatis就会帮我们实现这个接口,里面的原理就是使用了动态代理。

新建UserMapper.java,其中的Select注解使我们自己定义的

  1. package com.zstu.xyz.center.core.dao;
  2. import java.util.List;
  3. public interface UserMapper {
  4. @Select("select * from user")
  5. public List<String> query();
  6. }

创建代理对象并测试

  1. package com.zstu.xyz.center.core.dao;
  2. import java.lang.reflect.InvocationHandler;
  3. import java.lang.reflect.Method;
  4. import java.lang.reflect.Proxy;
  5. public class ProxyHandler {
  6. public static Object newProxyInstance(Class aclass){
  7. InvocationHandler h = new InvocationHandler() {
  8. @Override
  9. public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
  10. System.out.println("connection...");
  11. Select declaredAnnotation = method.getDeclaredAnnotation(Select.class);
  12. String value = declaredAnnotation.value();
  13. System.out.println(value);
  14. return null;
  15. }
  16. };
  17. ClassLoader loader = aclass.getClassLoader();
  18. Class<?>[] interfaces = new Class[]{aclass};
  19. return Proxy.newProxyInstance(loader, interfaces, h);
  20. }
  21. public static void main(String[] args) {
  22. UserMapper proxy = (UserMapper)newProxyInstance(UserMapper.class);
  23. proxy.query();
  24. }
  25. }

运行结果:

  1. connection...
  2. select * from user

cglib

使用java自带的API实现代理类,被代理的类必须实现接口;而cglib不需要这个要求。代理对象是继承了目标对象,通过重写目标对象的方法起到增强作用。

Spring提供的cglib代理:

  1. package com.zstu.springdemo.aop;
  2. import org.springframework.cglib.proxy.Enhancer;
  3. import org.springframework.cglib.proxy.MethodInterceptor;
  4. import org.springframework.cglib.proxy.MethodProxy;
  5. import java.lang.reflect.Method;
  6. public class ProxyTest {
  7. public static void main(String[] args) {
  8. Enhancer enhancer = new Enhancer();
  9. enhancer.setSuperclass(LandLord.class);
  10. enhancer.setCallback(new MethodInterceptor() {
  11. @Override
  12. public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
  13. System.out.println("带你去看房");
  14. methodProxy.invokeSuper(o, null);
  15. System.out.println("带你去签合同");
  16. return null;
  17. }
  18. });
  19. LandLord landLordProxy = (LandLord) enhancer.create();
  20. landLordProxy.rent();
  21. }
  22. }