1、什么是代理模式

给目标对象提供一个代理对象,并由代理对象控制对目标对象的引用

2、代理模式的作用

通过引入代理对象的方式来间接访问目标对象,防止直接访问目标对象给系统带来的不必要复杂性。
同时通过代理对象的隔离,可以在目标对象访问后增加额外的业务逻辑,实现功能增强。

3、代码例子

image.png
小红是一个香水代购,而小明是一个购买者,小明托小红买一瓶香水。

  1. /**
  2. * 售卖香水的接口
  3. */
  4. public interface SellPerfume {
  5. void sellPerfume(double price);
  6. }
  1. /**
  2. * 香水工厂
  3. */
  4. public class ChannelFactory implements SellPerfume{
  5. @Override
  6. public void sellPerfume(double price) {
  7. System.out.println("成功购买香奈儿的香水,价格是:" + price + "元");
  8. }
  9. }
  1. /**
  2. * 小红代购
  3. */
  4. public class XiaohongSellProxy implements SellPerfume{
  5. private SellPerfume sellPerfumeFactory;
  6. public XiaohongSellProxy (SellPerfume sellPerfumeFactory) {
  7. this.sellPerfumeFactory = sellPerfumeFactory;
  8. }
  9. @Override
  10. public void sellPerfume(double price) {
  11. doSomethingBeforeSell();
  12. sellPerfumeFactory.sellPerfume(price);
  13. doSomethingAfterSell();
  14. }
  15. private void doSomethingBeforeSell() {
  16. System.out.println("小红代理买香水前的操作");
  17. }
  18. private void doSomethingAfterSell() {
  19. System.out.println("小红代理购买香水后的操作");
  20. }
  21. }
  1. /**
  2. * 实际购买者
  3. */
  4. public class XiaoMing {
  5. public static void main(String[] args) {
  6. ChannelFactory factory = new ChannelFactory();
  7. XiaohongSellProxy proxy = new XiaohongSellProxy(factory);
  8. proxy.sellPerfume(199999);
  9. }
  10. }

实现代理模式,需要走一下几个步骤:

  • 定义真实对象和代理对象的公共接口(售卖香水接口)
  • 代理对象内部保存对真实目标对象的引用(小红引用提供商)
  • 访问者仅能通过代理对象访问真实目标对象,不可直接访问目标对象(小明只能通过小红去购买香水,不能直接到香奈儿提供商购买)

    代理模式的一个误区:代理对象并不提供服务,以本次的例子来说,小红并不出售香水,她只是一个中间人

4、改进-JDK Proxy

继续上面的例子,如果小明还想要买红酒,小红也刚好在法国,那么在程序中就需要创建售卖红酒的接口,售卖红酒提供商和小红也需要实现实现该接口
上面的静态代理违背了开闭原则,当有不停地有新的需求时,代理类就会越来越庞大,变得难以维护
下面使用JDK Proxy 实现动态代理,关键是先实现_InvocationHandler_接口和其方法invoke

  1. public class SellProxyFactory implements InvocationHandler {
  2. //代理的真是对象
  3. private Object realObject;
  4. public SellProxyFactory (Object realObject) {
  5. this.realObject = realObject;
  6. }
  7. @Override
  8. public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
  9. doSomethingBeforeSell();
  10. Object object = method.invoke(realObject, args);
  11. doSomethingAfterSell();
  12. return object;
  13. }
  14. private void doSomethingBeforeSell() {
  15. System.out.println("小红代理买东西前的操作");
  16. }
  17. private void doSomethingAfterSell() {
  18. System.out.println("小红代理购买东西后的操作");
  19. }
  20. }

看invoke方法可以看出就是反射里的Method类的invoke,在这里就是一个代理方法,即最后客户端请求代理时,执行的就是该方法

  1. public class Xiaoming {
  2. public static void main(String[] args) {
  3. ChannelFactory channelFactory = new ChannelFactory();
  4. SellProxyFactory sellProxyFactory = new SellProxyFactory(channelFactory);
  5. SellPerfume sellPerfume = (SellPerfume) Proxy.newProxyInstance(channelFactory.getClass().getClassLoader(), channelFactory.getClass().getInterfaces(), sellProxyFactory);
  6. sellPerfume.sellPerfume(199888.9);
  7. }
  8. }

生成代理对象需要Proxy类,它可以帮助我们生成任意一个代理对象,里面提供一个静态方法newProxyInstance,需要传入三个参数:

  • ClassLoader loader:加载动态代理类的类加载器
  • Class<?> [ ] interfaces:代理类实现的接口,可以传入多个接口
  • InvocationHandler h:指定代理类的调用处理程序,即调用接口中的方法时,会找到该代理工厂h,执行invoke()方法

以后想要扩展时,比如代购红酒,只需要增加一个出售红酒的接口和红酒提供商。这样就符合了开闭原则。

4.1、总结

JDK动态代理的使用方法

  • 代理工厂需要实现 InvocationHandler接口,调用代理方法时会转向执行invoke()方法
  • 生成代理对象需要使用Proxy 对象中的newProxyInstance()方法,返回对象可强转成传入的其中一个接口,然后调用接口方法即可实现代理

JDK动态代理的特点

  • 目标对象强制需要实现一个接口,否则无法使用JDK动态代理

5、CGLIB实现代理

CGLIB(Code generation Library)不是JDK自带的动态代理,它需要导入第三方依赖,它是一个字节码生成类库,能够在运行时动态生成代理类对Java类和Java接口扩展。
CGLIB不仅能够为Java接口做代理,而且能够为普通的Java类做代理,而JDK Proxy 只能为实现了接口的Java类做代理,所以CGLIB为Java的代理做了很好的扩展。如果需要代理的类没有实现接口,可以选择Cglib作为实现动态代理的工具。
使用步骤如下:
1)导入依赖

  1. <dependency>
  2. <groupId>cglib</groupId>
  3. <artifactId>cglib-nodep</artifactId>
  4. <version>3.3.0</version>
  5. </dependency>

还有另外一个CGLIB包,二者的区别是带有-nodep的依赖内部已经包括了ASM字节码框架的相关代码,无需额外依赖ASM

2)CGLIB代理中有两个核心的类:MethodInterceptor 接口和Enhancer类,前者是实现一个代理工厂的根接口,后者是创建动态代理对象的类

  1. public class SellProxyFactory implements MethodInterceptor {
  2. //关联真实对象,控制对真实对象的访问
  3. private Object realObject;
  4. public Object getProxyInstance(Object realObject) {
  5. this.realObject = realObject;
  6. Enhancer enhancer = new Enhancer();
  7. //设置需要增强类的类加载器
  8. enhancer.setClassLoader(realObject.getClass().getClassLoader());
  9. //设置被代理类,真实对象
  10. enhancer.setSuperclass(realObject.getClass());
  11. //设置方法拦截器,代理工厂
  12. enhancer.setCallback(this);
  13. //创建代理类
  14. return enhancer.create();
  15. }
  16. @Override
  17. public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
  18. doSomethingBeforeSell();
  19. Object object = methodProxy.invokeSuper(o, objects);
  20. doSomethingAfterSell();
  21. return object;
  22. }
  23. private void doSomethingBeforeSell() {
  24. System.out.println("代理买东西前的操作");
  25. }
  26. private void doSomethingAfterSell() {
  27. System.out.println("代理购买东西后的操作");
  28. }
  29. }

intercept0方法涉及到4个参数:

  • ·Object o:被代理对象
  • Method method:被拦截的方法
  • Objectl objects:被拦截方法的所有入参值
  • MethodProxy methodProxy:方法代理,用于调用原始的方法

在getInstance()方法中,利用Enhancer类实例化代理对象(可以看作是小红)返回给调用者小明,即可完成代理操作。

  1. public class Xiaoming {
  2. public static void main(String[] args) {
  3. SellProxyFactory sellProxyFactory = new SellProxyFactory();
  4. //获取一个代理实例
  5. ChannelFactory proxyInstance = (ChannelFactory) sellProxyFactory.getProxyInstance(new ChannelFactory());
  6. proxyInstance.sellPerfume(1999.77);
  7. }
  8. }

6、CGLIB与JDK Proxy

最明显的不同:CGLIB可以代理大部分类(第二点说到);而JDKProxy仅能够代理实现了接口的类
CGLIB采用动态创建被代理类的子类实现方法拦截,子类内部重写被拦截的方法,所以CGLlB不能代理被final关键字修饰的类和方法