澳门新濠城唯一网址代理模式vs装饰模式,初识代理模式

发布时间:2019-10-06  栏目:编程  评论:0 Comments

代理模式(Proxy Pattern)也称为委托模式。

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

java代理模式,java代理

我们生活中处处都有代理模式的影子。对于程序员来说最常接触的就是代理上网了,连上代理服务器,就可以看到墙外的世界;通过中介租房,也是一种代理。

1.代理模式定义&实现

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

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

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

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

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

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

    澳门新濠城唯一网址 1image.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; } @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; } 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(); }}

事例

小张是一个普普通通的码农,每天勤勤恳恳地码代码。某天中午小张刚要去吃饭,一个电话打到了他的手机上。“是XX公司的小张吗?我是YY公司的王AA”。“哦,是王总啊,有什么事情吗?”。沟通过后,小张弄明白了,原来客户有个需求,刚好负责这方面开发的是小张,客户就直接找到了他。不过小张却没有答应客户的请求,而是让客户找产品经理小李沟通。

是小张着急去吃面而甩锅吗?并不是,只是为了使故事可以套到代理模式上。我们先看一下代理模式的定义:
* 为其他对象提供一种代理,以控制对这个对象的访问。(Provide a surrogate
or placeholder for another object to control access to it)

对照定义,码农小张可以映射为其他对象,产品经理小李为小张的代理。我们通过JAVA代码,表述上面事例。

代理模式定义: 为其他对象提供一种代理以控制对这个对象的访问。

2.装饰模式定义&实现

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

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

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

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

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

    澳门新濠城唯一网址 2image.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也要做相应改变;

1.抽象角色

基于面向对象的思想,首先定义一个码农接口,它有一个实现用户需求的方法。

public interface ICoder {

    public void implDemands(String demandName);
}

代理模式的组成

澳门新濠城唯一网址,4.动态代理

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

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

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

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

ProxyProxy是 Java
动态代理机制的主类,它提供了一组静态方法来为一组接口动态地生成代理类及其对象。Proxy
的静态方法
static InvocationHandler getInvocationHandler(Object proxy)该方法用于获取指定代理对象所关联的调用处理器static Class getProxyClass(ClassLoader loader, Class[] interfaces)该方法用于获取关联于指定类装载器和一组接口的动态代理类的类对象static boolean isProxyClass该方法用于判断指定类对象是否是一个动态代理类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.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 =  Proxy.newProxyInstance(RealSubject.class.getClassLoader(), RealSubject.class.getInterfaces(), new ProxySubject(new RealSubject; subject.sayHello(); //查看subject对象的类型 System.out.println(subject.getClass().getName; }}

实现步骤: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; }}

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;//设置需要代理的类 enhancer.setCallback;//设置回调方法 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; }}

2.真实角色

我们假设小张是JAVA程序员,定义一个JAVA码农类,他通过JAA语言实现需求。

public class JavaCoder implements ICoder{

    private String name;

    public JavaCoder(String name){
        this.name = name;
    }

    @Override
    public void implDemands(String demandName) {
        System.out.println(name + " implemented demand:" + demandName + " in JAVA!");
    }
}
  • 抽象角色: 通过接口或抽象类声明真实角色实现的业务方法。
  • 真实角色:
    实现抽象角色,定义真实角色所要实现的业务逻辑,供代理角色调用。
  • 代理角色:实现抽象角色,是真实角色的代理,通过真实角色的业务逻辑方法来实现抽象方法,并可以附加自己的操作。

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; proxy.createApp("Kevin Test"); } } 

例子2-Spring-AOP:

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

3.代理角色

委屈一下产品经理,将其命名为码农代理类,同时让他实现ICoder接口。

public class CoderProxy implements ICoder{

    private ICoder coder;

    public CoderProxy(ICoder coder){
        this.coder = coder;
    }

    @Override
    public void implDemands(String demandName) {
        coder.implDemands(demandName);
    }
}

上面一个接口,两个类,就实现了代理模式。Are you kidding
me?这么简单?是的,就是这么简单。
我们通过一个场景类,模拟用户找产品经理增加需求。

public class Customer {

    public static void main(String args[]){
        //定义一个java码农
        ICoder coder = new JavaCoder("Zhang");
        //定义一个产品经理
        ICoder proxy = new CoderProxy(coder);
        //让产品经理实现一个需求
        proxy.implDemands();
    }
}

 

运行程序,结果如下:

Zhang implemented demand:Add user manageMent in JAVA!

 

产品经理充当了程序员的代理,客户把需求告诉产品经理,并不需要和程序员接触。看到这里,有些机智的程序员发现了问题。你看,产品经理就把客户的需求转达了一下,怪不得我看产品经理这么不爽。

产品经理当然不只是转达用户需求,他还有很多事情可以做。比如,该项目决定不接受新增功能的需求了,对修CoderProxy类做一些修改:

public class CoderProxy implements ICoder{

    private ICoder coder;

    public CoderProxy(ICoder coder){
        this.coder = coder;
    }

    @Override
    public void implDemands(String demandName) {
        if(demandName.startsWith("Add")){
            System.out.println("No longer receive 'Add' demand");
            return;
        }
        coder.implDemands(demandName);
    }
}

 

这样,当客户再有增加功能的需求时,产品经理就直接回绝了,程序员无需再对这部分需求做过滤。

优点:

6.对比(代理模式 vs 装饰模式)and(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文件加载进来,通过修改其字节码生成子类来处理。

总结

我们对上面的事例做一个简单的抽象:

澳门新濠城唯一网址 3

代理模式包含如下角色:

  • Subject:抽象主题角色。可以是接口,也可以是抽象类。
  • RealSubject:真实主题角色。业务逻辑的具体执行者。
  • ProxySubject:代理主题角色。内部含有RealSubject的引用,负责对真实角色的调用,并在真实主题角色处理前后做预处理和善后工作。

代理模式优点:

  • 职责清晰
    真实角色只需关注业务逻辑的实现,非业务逻辑部分,后期通过代理类完成即可。
  • 高扩展性
    不管真实角色如何变化,由于接口是固定的,代理类无需做任何改动。
  1. 职责清晰。真实的角色就是实现实际的业务逻辑,不用关心其他非本职责的事务,通过后期的代理完成一件完成事务,附带的结果就是编程简洁清晰。
  2. 代理对象可以在客户端和目标对象之间起到中介的作用,这样起到了中介的作用和保护了目标对象的作用。
  3. 高扩展性
6.2. 代理模式 vs 装饰模式
代理模式 装饰模式
代理类(proxy class)和真实处理的类(real class)都实现同一个接口 装饰者(decorator)和被装饰者(decoratee)都实现同一个接口
都可以很容易地在真实对象的方法前面或者后面加上自定义的方法 都可以很容易地在真实对象的方法前面或者后面加上自定义的方法
关注于控制对对象的访问 关注于在一个对象上动态的添加方法
代理类(proxy class)可以对它的客户隐藏一个对象的具体信息。因此,当使用代理模式的时候,我们常常在一个代理类中创建一个对象的实例 当我们使用装饰器模式的时候,我们通常的做法是将原始对象作为一个参数传给装饰者的构造器

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

感谢网友的分享:

动态代理

前面讲的主要是静态代理。那么什么是动态代理呢?

假设有这么一个需求,在方法执行前和执行完成后,打印系统时间。这很简单嘛,非业务逻辑,只要在代理类调用真实角色的方法前、后输出时间就可以了。像上例,只有一个implDemands方法,这样实现没有问题。但如果真实角色有10个方法,那么我们要写10遍完全相同的代码。有点追求的码农,肯定会对这种方法感到非常不爽。有些机智的小伙伴可能想到了用AOP解决这个问题。非常正确。莫非AOP和动态代理有什么关系?没错!AOP用的恰恰是动态代理。

代理类在程序运行时创建的代理方式被称为动态代理。也就是说,代理类并不需要在Java代码中定义,而是在运行时动态生成的。相比于静态代理,
动态代理的优势在于可以很方便的对代理类的函数进行统一的处理,而不用修改每个代理类的函数。对于上例打印时间的需求,通过使用动态代理,我们可以做一个“统一指示”,对所有代理类的方法进行统一处理,而不用逐一修改每个方法。下面我们来具体介绍下如何使用动态代理方式实现我们的需求。

与静态代理相比,抽象角色、真实角色都没有变化。变化的只有代理类。因此,抽象角色、真实角色,参考ICoder和JavaCodr。

在使用动态代理时,我们需要定义一个位于代理类与委托类之间的中介类,也叫动态代理类,这个类被要求实现InvocationHandler接口:

public class CoderDynamicProxy implements InvocationHandler{
     //被代理的实例
    private ICoder coder;

    public CoderDynamicProxy(ICoder _coder){
        this.coder = _coder;
    }

    //调用被代理的方法
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println(System.currentTimeMillis());
        Object result = method.invoke(coder, args);
        System.out.println(System.currentTimeMillis());
        return result;
    }
}

 

当我们调用代理类对象的方法时,这个“调用”会转送到中介类的invoke方法中,参数method标识了我们具体调用的是代理类的哪个方法,args为这个方法的参数。

我们通过一个场景类,模拟用户找产品经理更改需求。

public class DynamicClient {

     public static void main(String args[]){
            //要代理的真实对象
            ICoder coder = new JavaCoder("Zhang");
            //创建中介类实例
            InvocationHandler  handler = new CoderDynamicProxy(coder);
            //获取类加载器
            ClassLoader cl = coder.getClass().getClassLoader();
            //动态产生一个代理类
            ICoder proxy = (ICoder) Proxy.newProxyInstance(cl, coder.getClass().getInterfaces(), handler);
            //通过代理类,执行doSomething方法;
            proxy.implDemands("Modify user management");
        }
}

执行结果如下:

1501728574978
Zhang implemented demand:Modify user management in JAVA!
1501728574979

通过上述代码,就实现了,在执行委托类的所有方法前、后打印时间。还是那个熟悉的小张,但我们并没有创建代理类,也没有时间ICoder接口。这就是动态代理。

留下评论

网站地图xml地图