设计模式 之 代理模式- 2020-10-26 12:16- 设计模式: java,编程,设计模式,代理模式,CGLB,动态代理


代理模式分为两种 静态代理,动态代理。
代理中的角色: 代理类,目标方法,目标类

静态代理

原理

静态代理是由我们自己编写的代理类,代理类中指定引用的真实对象,使用真实对象去执行接口,在编译时期就确定了。

优点和缺点

优点:
1.因为是我们手动编写的代理类,可控性好,可针对指定接口做扩展,例如增加日志或是数据统计等。
2.避免了生成大对象,造成资源浪费
缺点
1.手动编写 维护等成本增加,如果有多个类需要代理,则维护成本更增加。
2.代码耦合度高,不利于通用性。
3.违反 开闭原则 (开闭原则:增加扩展,避免更新)
因为代理类中耦合了被代理类,如果被代理的方法更新或者删除了,那么代理类中相应的也需要更新或删除,从而违反了开闭原则。

dome

  1. /**
  2. * 接口类
  3. *
  4. * @Author Bai
  5. * @Date 2020-10-26 14:25
  6. */
  7. public interface HelloService {
  8. void say(String msg);
  9. }
  1. /**
  2. * 真实对象:被代理对象
  3. *
  4. * @Author Bai
  5. * @Date 2020-10-26 14:25
  6. */
  7. public class HelloServiceImpl implements HelloService {
  8. @Override
  9. public void say(String msg) {
  10. System.out.println("向大家say" + msg);
  11. }
  12. }
  1. /**
  2. * 代理类
  3. *
  4. * @Author Bai
  5. * @Date 2020-10-26 14:26
  6. */
  7. public class HelloServiceProxy {
  8. /**
  9. * 真实引用对象
  10. */
  11. private HelloService target;
  12. public HelloServiceProxy(HelloService target) {
  13. this.target = target;
  14. }
  15. @Override
  16. public void say(String msg) {
  17. System.out.println("say proxy 开始");
  18. target.say(msg);
  19. System.out.println("say proxy 结束");
  20. }
  21. }
  1. @Test
  2. public void test1() {
  3. HelloService helloService = new HelloServiceImpl();
  4. HelloServiceProxy proxy = new HelloServiceProxy(helloService);
  5. proxy.say("hello world");
  6. }

动态代理
动态代理有种实现方式,JDK动态代理和CGLB。JDK动态代理 主要是java.lang.reflect包下的类,实现jdk动态代理需要实现InvocationHandler。CGLB是第三方字节码处理库,它的底层是基于ASM框架来处理字节码实现代理的。

JDK动态代理

原理

主要基于java.lang.reflect.Proxy和java.lang.reflect.InvocationHandler来实现动态代理,所有生成的代理类都继承于Proxy,InvocationHandler是一个接口,类似于调用处理器,我们自己在实现动态代理时都必须要实现java.lang.reflect.InvocationHandler。jdk底层其实也是通过asm框架来实现对字节码的操作,读字节码并生生成对应的class文件。

  1. 1.获取被代理类的引用,并获取引用类的所有接口,通过反射获取。
  2. 2.Proxy基于被代理类的接口生成新的类,拥有被代理类的所有接口。
  3. 3.动态生成java代码,把增强逻辑加入到新代码中。
  4. 4.重新编译代理类生成新的class文件
  5. 5.加载代理类的class文件并重新运

优点和缺点

优点:
1.不需要手动编写代理类,运行时会自动生成代理类
缺点
1.被代理的类 必须要实现接口,没有实现接口或是抽象类的 类 无法被代理

dome

1.实现InvocationHandler

  1. import java.lang.reflect.InvocationHandler;
  2. import java.lang.reflect.Method;
  3. import java.lang.reflect.Proxy;
  4. /**
  5. * JDK动态代理handler
  6. *
  7. * @Author Bai
  8. * @Date 2020-10-26 14:52
  9. */
  10. public class HelloInvocationHandler implements InvocationHandler {
  11. /**
  12. * 真实对象:被代理的对象引用
  13. */
  14. private Object target;
  15. public HelloInvocationHandler(Object target) {
  16. super();
  17. this.target = target;
  18. }
  19. /**
  20. * @param proxy 代理类
  21. * @param method 代理类的方法
  22. * @param args 入参
  23. * @return
  24. * @throws Throwable
  25. */
  26. @Override
  27. public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
  28. System.out.println("HelloInvocationHandler#invoke start");
  29. //两者执行有何区别?target or proxy
  30. //proxy 会造成死循环,猜测可能是因为invoke内部实现 需要生成proxy的实例,去调用proxy实例的invoke,就会造成循环调用
  31. // Object invoke = method.invoke(proxy, args);
  32. //这里要传入目标类
  33. Object invoke = method.invoke(target, args);
  34. System.out.println("HelloInvocationHandler#invoke end");
  35. return invoke;
  36. }
  37. /**
  38. * 获取代理对象
  39. *
  40. * @return
  41. */
  42. public Object getProxy() {
  43. //第一个入参 使用哪个loader将proxy对象加载到内存中,一般是跟被代理对象同个loader就可以
  44. //第二个入参 proxy要代理的是哪些接口
  45. Class<?> aClass = target.getClass();
  46. saveProxyClass0();
  47. return Proxy.newProxyInstance(aClass.getClassLoader(), aClass.getInterfaces(), this);
  48. }
  49. private void saveProxyClass0() {
  50. //通过反编译工具可以查看源代码
  51. byte[] bytes = ProxyGenerator.generateProxyClass("$Proxy0", new Class[]{HelloService.class});
  52. try {
  53. FileOutputStream os = new FileOutputStream("E://$Proxy0.class");
  54. os.write(bytes);
  55. os.close();
  56. } catch (IOException e) {
  57. e.printStackTrace();
  58. }
  59. }
  60. }

2.测试

  1. /**
  2. * jdk动态代理
  3. */
  4. @Test
  5. public void jdkProxyTest() throws Throwable {
  6. //真实对象
  7. HelloService helloService = new HelloServiceImpl();
  8. //代理handler
  9. HelloInvocationHandler handler = new HelloInvocationHandler(helloService);
  10. // 代理对象
  11. HelloService helloProxyService = (HelloService) handler.getProxy();
  12. //使用代理对象执行
  13. System.out.println("--- helloProxyService.result ---");
  14. helloProxyService.say("helloProxyService.jdk动态代理");
  15. //使用handler执行
  16. String[] args = new String[]{"helloProxyService.invoke"};
  17. //对应 Object invoke = method.invoke(proxy, args)
  18. // Object say = handler.invoke(helloProxyService, helloProxyService.getClass().getMethod("say", String.class), args);
  19. //对应 Object invoke = method.invoke(target, args)
  20. //入参: 代理对象,被调用的目标方法,入参
  21. Object say = handler.invoke(helloProxyService, helloService.getClass().getMethod("say", String.class), args);
  22. System.out.println("--- HelloProxyService.result ---" + say);
  23. }

3.JDK动态生成的代理类源码

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

CGLB代理

原理 todo

第三方字节码处理库,底层是asm框架,如果要了解原理就需要了解asm框架,以及它的一些设计模式
cglb是通过生成被代理类的子类来实现代理。

优点和缺点todo

优点:

  1. 被代理对象无须实现接口,对类的入侵没有那么深

缺点:如果类被final修饰,那么这个类将无法被代理

dome

  1. import org.springframework.cglib.proxy.Enhancer;
  2. import org.springframework.cglib.proxy.MethodInterceptor;
  3. import org.springframework.cglib.proxy.MethodProxy;
  4. import java.lang.reflect.Method;
  5. /**
  6. * cglb动态代理实现
  7. *
  8. * @Author Bai
  9. * @Date 2020-10-26 16:00
  10. */
  11. public class CglibProxy implements MethodInterceptor {
  12. private Enhancer enhancer = new Enhancer();
  13. /**
  14. * 获取代理类
  15. *
  16. * @param clazz 真实类
  17. * @return
  18. */
  19. public Object getProxy(Class clazz) {
  20. //设置需要创建子类的类
  21. enhancer.setSuperclass(clazz);
  22. enhancer.setCallback(this);
  23. //通过字节码技术动态创建子类实例
  24. return enhancer.create();
  25. }
  26. @Override
  27. public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
  28. System.out.println("前置代理");
  29. //通过代理类调用父类中的方法
  30. Object result = proxy.invokeSuper(obj, args);
  31. System.out.println("后置代理");
  32. return result;
  33. }
  34. }
  1. /**
  2. * jdk动态代理
  3. */
  4. @Test
  5. public void cglbProxyTest() throws Throwable {
  6. //真实对象
  7. CglibProxy cglibProxy = new CglibProxy();
  8. HelloService helloService = (HelloService) cglibProxy.getProxy(HelloServiceImpl.class);
  9. helloService.say("cglb动态代理");
  10. }
  1. 执行结果:
  2. 前置代理
  3. 向大家saycglb动态代理
  4. 后置代理

两者区别:JDK动态代理/CGLB动态代理

jdk动态代理需要 实现接口
CGLB不需要实现接口

应用

aop

  1. aopJDK动态代理和CGLB动态代理实现,当需要代理的类实现了接口,既使用jdk动态代理,如果需要代理的类没有实现接口则自动切换为CGLB代理。<br /> jdk动态代理是基于反射来获取被代理的类,要去被代理的类必须要实现接口。<br /> cjlb是代码生成的类库,它是在运行过程中动态的生成类的子类来实现代理的,cglb是基于继承来实现的,所以如果一个类被final修饰,那么这个类将不能被cglb代理。

源码分析todo

问题整理

1.invoke接口的第一个入参proxy不是很经常用,为什么还要传过来呢?
2.静态代理为啥要构造参数里给出了来
3.动态代理为什么要实现接口?

参考资料:
http://hollischuang.gitee.io/tobetopjavaer/#/basics/java-basic/static-proxy
**