代理模式vs装饰模式 and 静态代理vs动态代理

目录:
1.代理模式定义&实现
2.装饰模式定义&实现
3.静态代理
4.动态代理:JDK动态代理、Cglib动态代理
5.动态代理使用场景
6.对比(代理模式 vs 装饰模式)and(JDK动态代理 vs Cglib动态代理)

1.代理模式定义&实现

  • 定义为其他对象提供一种代理以控制对这个对象的访问。在某些情况下,一个对象不适合或者不能直接引用另一个对象,而代理对象可以在客户端和目标对象之间起到中介的作用。

  • 组成
    抽象角色:通过接口或抽象类声明真实角色实现的业务方法。
    代理角色:实现抽象角色,是真实角色的代理,通过真实角色的业务逻辑方法来实现抽象方法,并可以附加自己的操作。
    真实角色:实现抽象角色,定义真实角色所要实现的业务逻辑,供代理角色调用。

  • 优点: 1、职责清晰; 2、高扩展性; 3、智能化;

  • 缺点: 1、由于在客户端和真实主题之间增加了代理对象,因此有些类型的代理模式可能会造成请求的处理速度变慢; 2、实现代理模式需要额外的工作,有些代理模式的实现非常复杂;

  • 使用场景:按职责来划分,通常有以下使用场景: 1、远程代理。 2、虚拟代理。 3、Copy-on-Write 代理。 4、保护(Protect or Access)代理。 5、Cache代理。 6、防火墙(Firewall)代理。 7、同步化(Synchronization)代理。 8、智能引用(Smart Reference)代理。

  • 实现
    描述:我们将创建一个 Image 接口和实现了 Image 接口的实体类RealImage。ProxyImage 是一个代理类,减少 RealImage 对象加载的内存占用。
    ProxyPatternDemo,我们的演示类使用 ProxyImage 来获取要加载的 Image 对象,并按照需求进行显示。

    image.png

    步骤1:创建一个接口-Image.java

       public interface Image {
         void display();
       }
    

步骤 2.1:创建实现接口的实体类-RealImage.java

public class RealImage implements Image {

   private String fileName;

   public RealImage(String fileName){
      this.fileName = fileName;
      loadFromDisk(fileName);
   }

   @Override
   public void display() {
      System.out.println("Displaying " + fileName);
   }

   private void loadFromDisk(String fileName){
      System.out.println("Loading " + fileName);
   }
}

步骤 2.2:创建实现接口的代理服务类-ProxyImage.java

public class ProxyImage implements Image{

   private RealImage realImage;
   private String fileName;

   public ProxyImage(String fileName){
      this.fileName = fileName;
   }

   @Override
   public void display() {
      if(realImage == null){
         realImage = new RealImage(fileName);
      }
      realImage.display();
   }
}

步骤3:当被请求时,使用 ProxyImage 来获取 RealImage 类的对象-ProxyPatternDemo.java

public class ProxyPatternDemo {
    
   public static void main(String[] args) {
      Image image = new ProxyImage("test_10mb.jpg");

      //图像将从磁盘加载
      image.display(); 
      System.out.println("");
      //图像将无法从磁盘加载
      image.display();     
   }
}

2.装饰模式定义&实现

  • 定义: 23种设计模式之一,英文叫Decorator Pattern,又叫装饰者模式。装饰模式是在不必改变原类文件和使用继承的情况下,动态地扩展一个对象的功能。它是通过创建一个包装对象,也就是装饰来包裹真实的对象。

  • 优点:装饰类和被装饰类可以独立发展,不会相互耦合,装饰模式是继承的一个替代模式,装饰模式可以动态扩展一个实现类的功能。

  • 缺点:多层装饰比较复杂。

  • 使用场景: 1、扩展一个类的功能。 2、动态增加功能,动态撤销。

  • 实现
    描述:我们将创建一个 Shape 接口和实现了 Shape 接口的实体类。然后我们创建一个实现了 Shape 接口的抽象装饰类 ShapeDecorator,并把 Shape 对象作为它的实例变量。
    RedShapeDecorator 是实现了 ShapeDecorator 的实体类。
    DecoratorPatternDemo,我们的演示类使用 RedShapeDecorator 来装饰 Shape 对象。

    image.png

    步骤1:创建shape接口-Shape.java

      public interface Shape {
         void draw();
      }
    

步骤2:创建实现shape接口的实体类-Rectangle.java、Circle.java

 public class Rectangle implements Shape {

   @Override
   public void draw() {
      System.out.println("Shape: Rectangle");
   }
}

public class Circle implements Shape {

   @Override
   public void draw() {
      System.out.println("Shape: Circle");
   }
}

步骤3:创建实现shape接口的抽象装饰类-ShapeDecorator.java
把 Shape 对象作为ShapeDecorator的实例变量

public abstract class ShapeDecorator implements Shape {
   protected Shape decoratedShape;

   public ShapeDecorator(Shape decoratedShape){
      this.decoratedShape = decoratedShape;
   }

   public void draw(){
      decoratedShape.draw();
   }    
}

步骤4:创建扩展了ShapeDecorator 类的实体装饰类-RedShapeDecorator.java

public class RedShapeDecorator extends ShapeDecorator {

   public RedShapeDecorator(Shape decoratedShape) {
      super(decoratedShape);        
   }

   @Override
   public void draw() {
      decoratedShape.draw();           
      setRedBorder(decoratedShape);
   }

   private void setRedBorder(Shape decoratedShape){
      System.out.println("Border Color: Red");
   }
}

步骤 5:使用 RedShapeDecorator 来装饰 Shape 对象-DecoratorPatternDemo.java

public class DecoratorPatternDemo {
   public static void main(String[] args) {

      Shape circle = new Circle();

      Shape redCircle = new RedShapeDecorator(new Circle());

      Shape redRectangle = new RedShapeDecorator(new Rectangle());
      System.out.println("Circle with normal border");
      circle.draw();

      System.out.println("\nCircle of red border");
      redCircle.draw();

      System.out.println("\nRectangle of red border");
      redRectangle.draw();
   }
}

3.静态代理

  • 定义:所谓静态代理也就是在程序运行前就已经存在代理类的字节码文件,代理类和委托类的关系在运行前就确定了。
  • 实现:静态代理的实现参考代理模式的实现;
  • 优点:业务类只需要关注业务逻辑本身,保证了业务类的重用性。这是代理的共有优点。
  • 缺点
      1、静态代理模式并没有做到事务的重用;
      2、代理对象的一个接口只服务于一种类型的对象,如果要代理的方法很多,势必要为每一种方法都进行代理,静态代理在程序规模稍大时就无法胜任了;
      假设dao有100个类,100个proxy,接口中有多少方法,在proxy层就得实现多少方法,有多少方法就要开启和提交多少事务;
      一个真实角色必须对应一个代理角色,如果大量使用会导致类的急剧膨胀;
      3、如果一个proxy实现了多个接口,如果其中的一个接口发生变化(添加了一个方法),那么proxy也要做相应改变;

4.动态代理

4.1.JDK动态代理

Java动态代理类位于java.lang.reflect包下,一般主要涉及到以下两个类:一个是 InvocationHandler(Interface)、另一个则是 Proxy(Class),这一个类和接口是实现我们动态代理所必须用到的。

InvocationHandler(Interface)
InvocationHandler是负责连接代理类和委托类的中间类必须实现的接口,它自定义了一个 invoke 方法,用于集中处理在动态代理类对象上的方法调用,通常在该方法中实现对委托类的代理访问。
InvocationHandler 的核心方法
Object invoke(Object proxy, Method method, Object[] args)

  • proxy 该参数为代理类的实例
  • method 被调用的方法对象
  • args 调用method对象的方法参数

该方法也是InvocationHandler接口所定义的唯一的一个方法,该方法负责集中处理动态代理类上的所有方法的调用。调用处理器根据这三个参数进行预处理或分派到委托类实例上执行。

Proxy(Class)
Proxy是 Java 动态代理机制的主类,它提供了一组静态方法来为一组接口动态地生成代理类及其对象。
Proxy 的静态方法
static InvocationHandler getInvocationHandler(Object proxy)
该方法用于获取指定代理对象所关联的调用处理器
static Class getProxyClass(ClassLoader loader, Class[] interfaces)
该方法用于获取关联于指定类装载器和一组接口的动态代理类的类对象
static boolean isProxyClass(Class cl)
该方法用于判断指定类对象是否是一个动态代理类
static Object newProxyInstance(ClassLoader loader, Class[] interfaces,InvocationHandler h)

  • loader 指定代理类的ClassLoader加载器
  • interfaces 指定代理类要实现的接口
  • h: 表示的是当我这个动态代理对象在调用方法的时候,会关联到哪一个InvocationHandler对象上
    该方法用于为指定类装载器、一组接口及调用处理器生成动态代理类实例

使用Java 动态代理的两个重要步骤:

  • 通过实现 InvocationHandler 接口创建自己的调用处理器;
  • 通过为Proxy类的newProxyInstance方法指定代理类的ClassLoader 对象和代理要实现的interface以及调用处理器InvocationHandler对象 来创建动态代理类的对象;

实现步骤:
1.JDK动态代理技术首先要求我们目标对象需要实现一个接口

     public interface Subject {
        void sayHello();
    }

2.接下来就是我们需要代理的真实对象,即目标对象:

package proxy;

/**
 * 目标对象,即需要被代理的对象
 */
public class RealSubject implements Subject{
    public void sayHello() {
        System.out.println("hello world");
    }
}

3.这是一个真实的对象,我们希望在不更改原有代码逻辑的基础上增强该类的sayHello方法,利用JDK动态代理技术需要我们实现InvocationHandler接口中的invoke方法:

package proxy;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;

public class ProxySubject implements InvocationHandler {
    private Object target;

    public ProxySubject(Object target) {
        // 重要(优化点:简化客户端)
        // todo 绑定委托对象并返回一个代理类,这里只做了绑定对象;调用的地方就可以不用调用newProxyInstance
        // return Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(), this);

        this.target = target;
    }

    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("调用前");
        Object object = method.invoke(target, args);
        System.out.println("调用后");
        return object;
    }
}
第15行,在invoke方法中可以看到,在调用目标对象的方法前后我们对方法进行了增加,这其实就是AOP中Before和After通知的奥义所在。

4.加入测试代码:

import java.lang.reflect.Proxy;

public class Test {
    public static void main(String[] args) {
        Subject subject = (Subject) Proxy.newProxyInstance(RealSubject.class.getClassLoader(), RealSubject.class.getInterfaces(), new ProxySubject(new RealSubject()));
        subject.sayHello();

        //查看subject对象的类型
        System.out.println(subject.getClass().getName());
    }
}

4.2.Cglib动态代理

实现步骤:
1.通过CGLib来创建一个代理需要引入jar包,其pom.xml依赖如下所示:

    <dependency>
      <groupId>cglib</groupId>
      <artifactId>cglib</artifactId>
      <version>3.2.4</version>
    </dependency>

2.前面提到了CGLib动态代理技术不需要目标对象实现自一个接口:

package cglibproxy;

/**
 * 目标对象(需要被代理的类)
 * Created by Kevin on 2017/11/6.
 */
public class RealSubject {
    public void sayHello() {
        System.out.println("hello");
    }
}

3.下面我们就使用CGLib代理这个类:

package cglibproxy;

import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;

import java.lang.reflect.Method;

/**
 * 代理类
 */
public class ProxySubject implements MethodInterceptor {
    private Enhancer enhancer = new Enhancer(); // 该类用于生成代理对象

    public Object getProxy(Class clazz) {
        enhancer.setSuperclass(clazz);//设置需要代理的类
        enhancer.setCallback(this);//设置回调方法
        return enhancer.create();    //用于创建无参的目标对象代理类,对于有参构造器则调用Enhancer.create(Class[] argumentTypes, Object[] arguments),第一个参数表示参数类型,第二个参数表示参数的值。
    }

    @Override
    public Object intercept(Object object, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
        System.out.println("调用前");
        Object result = methodProxy.invokeSuper(object, args);
        System.out.println("调用后");
        return result;
    }
}
可以看到同样是需要实现一个接口——MethodIntercept,并且实现一个和invoke类似的方法——intercept。

4.加入测试代码:

package cglibproxy;

public class Main {
    public static void main(String[] args) {
        RealSubject subject = (RealSubject) new ProxySubject().getProxy(RealSubject.class);
        subject.sayHello();
        System.out.println(subject.getClass().getName());
    }
}

5.动态代理使用场景

代理的使用场景很多,struts2中的 action 调用, hibernate的懒加载, spring的 AOP无一不用到代理。总结起来可分为以下几类:

  1. 在原方法执行之前和之后做一些操作,可以用代理来实现(比如记录Log,做事务控制等)。
  2. 封装真实的主题类,将真实的业务逻辑隐藏,只暴露给调用者公共的主题接口。
  3. 在延迟加载上的应用。

例子1-日志处理器:

 //接口
 public interface AppService {  
  public boolean createApp(String name);  
}
//接口实现类
public class AppServiceImpl implements AppService {  
public boolean createApp(String name) {  
    System.out.println("App["+name+"] has been created.");  
    return true;  
}  
} 
//日志处理器
public class LoggerInterceptor implements InvocationHandler {//注意实现这个Handler接口  
private Object target;//目标对象的引用,这里设计成Object类型,更具通用性  
public LoggerInterceptor(Object target){  
    this.target = target;  
}  
public Object invoke(Object proxy, Method method, Object[] arg)  
        throws Throwable {  
    System.out.println("Entered "+target.getClass().getName()+"-"+method.getName()+",with arguments{"+arg[0]+"}");  
    Object result = method.invoke(target, arg);//调用目标对象的方法  
    System.out.println("Before return:"+result);  
    return result;  
}  
} 
//外部调用
public class Main {  
public static void main(String[] args) {  
    AppService target = new AppServiceImpl();//生成目标对象  
    //接下来创建代理对象  
    AppService proxy = (AppService) Proxy.newProxyInstance(  
            target.getClass().getClassLoader(),  
            target.getClass().getInterfaces(), new LoggerInterceptor(target));  
    proxy.createApp("Kevin Test");  
}  
}  

例子2-Spring-AOP:

SpringAOP动态代理策略是:
1、如果目标对象实现了接口,默认情况下会采用JDK的动态代理实现AOP 
2、如果目标对象实现了接口,可以强制使用CGLIB实现AOP 
3、如果目标对象没有实现了接口,必须采用CGLIB库,spring会自动在JDK动态代理和CGLIB之间转换

6.对比(代理模式 vs 装饰模式)and(JDK动态代理 vs Cglib动态代理)

6.1.JDK动态代理 vs Cglib动态代理

标题 JDK动态代理 Cglib动态代理
1 目标类和代理类实现了共同的接口 目标类是代理类的父类,不需要接口,可以直接是类;
用CGLib生成的代理类重写了父类的各个方法;
因为采用的是继承,所以不能对final修饰的类进行代理;
2 拦截器必须实现InvocationHandler接口,而这个接口中invoke方法体的内容就是代理对象方法体的内容; 拦截器必须实现MethodInterceptor接口,而接口中的intercept方法就是代理类的方法体,使用字节码增强机制创建代理对象的;
3 JDK动态代理机制是委托机制,不需要以来第三方的库,只要要JDK环境就可以进行代理,动态实现接口类,在动态生成的实现类里面委托为hanlder去调用原始实现类方法; CGLib 必须依赖于CGLib的类库,使用的是继承机制,是被代理类和代理类继承的关系,所以代理类是可以赋值给被代理类的,如果被代理类有接口,那么代理类也可以赋值给接口;
4 java动态代理是利用反射机制生成一个实现代理接口的匿名类,在调用具体方法前调用InvokeHandler来处理 而cglib动态代理是利用asm开源包,对代理对象类的class文件加载进来,通过修改其字节码生成子类来处理。
6.2. 代理模式 vs 装饰模式
代理模式 装饰模式
代理类(proxy class)和真实处理的类(real class)都实现同一个接口 装饰者(decorator)和被装饰者(decoratee)都实现同一个接口
都可以很容易地在真实对象的方法前面或者后面加上自定义的方法 都可以很容易地在真实对象的方法前面或者后面加上自定义的方法
关注于控制对对象的访问 关注于在一个对象上动态的添加方法
代理类(proxy class)可以对它的客户隐藏一个对象的具体信息。因此,当使用代理模式的时候,我们常常在一个代理类中创建一个对象的实例 当我们使用装饰器模式的时候,我们通常的做法是将原始对象作为一个参数传给装饰者的构造器

使用代理模式,代理和真实对象之间的的关系通常在编译时就已经确定了,而装饰者能够在运行时递归地被构造。


感谢网友的分享:
https://blog.csdn.net/yansong_8686/article/details/50691467
https://www.cnblogs.com/yulinfeng/p/7811965.html
http://www.cnblogs.com/huhx/p/dynamicTheoryAdvance.html
http://www.cnblogs.com/dooor/p/5326759.html
https://blog.csdn.net/fengyuzhengfan/article/details/49586277
https://juejin.im/post/5a3284a75188252970793195
http://blog.jobbole.com/104433/

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,524评论 5 460
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,869评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,813评论 0 320
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,210评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,085评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,117评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,533评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,219评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,487评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,582评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,362评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,218评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,589评论 3 299
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,899评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,176评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,503评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,707评论 2 335

推荐阅读更多精彩内容