架构师之路:设计模式-代理模式

定义

定义:代理(Proxy)是一种设计模式,代理对象代表目标的对象去完成对应的事情,在需要目标对象的时候再让目标对象去执行。生活中的中介等角色就是代理模式中的代理对象。

代理模式的好处

以买房为例,买的是二手房,大家都知道买房想找到一个满意的房子是很累的工作,我们需要到处看房,采集多家房源的信息做对比。但是如果我们有一个很好地代理对象的话,我们是不是就会轻松很多(事实上这个很好地代理对象不好找),假设我们现在有一个很好地中介,中介非常了解我的需求了,我就可以授权个这个中介去帮我们找房,当中介找到满足我们需求的房子的情况下,并且在有多个房源的情况下进行了比较分析,这个时候中介得出了各个房源的比较结果后告知我们可以买这个房子了,我们是不是找找房这个事情上就很轻松了?
这里中介就是我的代理对象,代理我去执行找房这个动作,在通过筛选后调用我的的找房动作完成我的找房事业。
代理模式的好处【中介给我带来的好处】:

  • 降低代理对象的业务代码【我只需要关心这套房是不是适合我,我不用关心着套房为什么不适合我,譬如房主有债务,房屋可能会出现纠纷,这些是这个中介去帮我规避了的(事实上并不是每个中介都这么良心)】
  • 减少依赖,降低耦合性【我不用知道房主是谁,房主也不用知道我是谁】
  • 保护被代理对象【只有中介知道我在买房,我的信息不会透露个其他人(当然现实不是这样的)】
  • 懒加载被代理对象【如果我不想参与到找房的过程中,只需要在中介找好房后在叫我即可】

代理模式的缺点【我找中介所付出的】;

  • 降低请求处理速度【中介先帮我找房,然后推荐给我,让我同意,这个过程肯定没有我找到房直接同意来的快(别说什么我找房没中介快这种话)】
  • 实现代理需要额外的工作【中介需要了解我的需求,我需要给中介费】

业务场景

常见的代理有:

  • 远程代理(Remote Proxy):对一个位于不同的地址空间对象提供一个局域代表对象,如RMI中的stub【我人在四川,让中介帮我在上海买套房】
  • 虚拟代理(Virtual Proxy):根据需要将一个资源消耗很大或者比较复杂的对象,延迟加载,在真正需要的时候才创建【中介帮我找房的时候其实就可以看成一个虚拟的我去找房,我比较忙,他只需要在确定房源的时候喊我】
  • 保护代理(Protect or Access Proxy):控制对一个对象的访问权限。【中介去找房,我不用告诉每个房东我在找房】
  • 智能引用(Smart Reference Proxy):提供比目标对象额外的服务和功能。【中介可以用他们的职业技能帮我筛选房源】

代码示例

静态代理

  • 静态代理在使用时,需要定义接口或者父类,被代理对象与代理对象一起实现相同的接口或者是继承相同父类.
  • 关键:在编译期确定代理对象,在程序运行前代理类的.class文件就已经存在了。
  • 比如:在代理对象中实例化被代理对象或者将被代理对象传入代理对象的构造方法

接口:

/**
 * <br>类描述:购房接口
 * <br>author: lwl [email protected]	2019/11/26 16:43
 *
 * @ClassName HousePurchase
 * @see #
 * @since {修改人、修改时间、修改事由}
 */
public interface HousePurchase {
    /**
     * 购房流程第一步:搜集房源
     */
    String scan();
    /**
     * 购房流程第一步:确定房源
     */
    String sure(String ip);
    /**
     * doOtherThing();
     */
}

被代理对象:

/**
 * <br>类描述:购房的人
 * <br>author: lwl [email protected]	2019/11/26 16:46
 *
 * @ClassName HousePurchasePeople
 * @see #
 * @since {修改人、修改时间、修改事由}
 */
public class HousePurchasePeople implements HousePurchase{
    String name;
    public HousePurchasePeople(String name){
        this.name = name;
    }
    @Override
    public String scan() {
        return null;
    }

    @Override
    public String sure(String ip) {
        System.out.println(name+"确定了房源"+ip);
        return ip;
    }


}

代理对象:

/**
 * <br>类描述:购房中介
 * <br>author: lwl [email protected]	2019/11/26 16:49
 *
 * @ClassName HousePurchasePeopleProxy
 * @see #
 * @since {修改人、修改时间、修改事由}
 */
public class HousePurchasePeopleProxy implements HousePurchase{
    //购房人
    public HousePurchasePeople purchasePeople;
    HousePurchasePeopleProxy(HousePurchasePeople people){
        this.purchasePeople = people;
    }
    public String scan(){
        String ip = "***小区***栋***单元1401";
        System.out.println("中介帮您从一堆房源里面找到了满足你要求的房源:"+ip);
        return ip;
    }
    @Override
    public String sure(String ip) {
        purchasePeople.sure(ip);
        return ip;
    }
}

执行:

public static void main(String[] args) {
        //静态代理,构建代理对象时指定被代理对象
        HousePurchasePeopleProxy proxy = new HousePurchasePeopleProxy(new HousePurchasePeople("張三"));
        //代理对象去浏览房源
        String ip = proxy.scan();
        //代理对象确定房源
        proxy.sure(ip);
    }

在这里插入图片描述
上面整个找房的过程中都是中介去找的,中介只需要在确定房源的时候调用张三的方法即可完成张三找房动作。
但是很明显静态代理不好的地方就是需要在开始就要指定被代理对象,这个中介就是给你张三找房的才行。这个时候来了个李四,这个中介就没办法帮李四找房了。而且因为代理对象也实现被代理的对象的接口,所以代理对象需要实现被代理对象的方法,这样会照成代码臃肿。

动态代理

动态代理有以下特点:

  • 在运行期,通过反射机制创建一个实现了一组给定接口的新类
  • 在运行时生成的class,必须提供一组interface给它,然后该class就宣称它实现了这些 interface。该class的实
    例可以当作这些interface中的任何一个来用。但是这个Dynamic Proxy其实就是一个Proxy,
    它不会替你作实质性的工作,在生成它的实例时你必须提供一个handler,由它接管实际的工 作。
  • 动态代理也叫做:JDK代理,接口代理
  • 接口中声明的所有方法都被转移到调用处理器一个集中的方法中处理(InvocationHandler.invoke)。这样,在接口方法数量比较多的时候,我们可以进行灵活处理,而不需要像静态代理那样每一个方法进行中转。而且动态代理的应用使我们的类职责更加单一,复用性更强

JDK中生成代理对象的API
 代理类所在包:java.lang.reflect.Proxy
 JDK实现代理只需要使用newProxyInstance方法,但是该方法需要接收三个参数,完整的写法是:

 static Object newProxyInstance(ClassLoader loader, Class [] interfaces, InvocationHandler handler)

注意该方法是在Proxy类中是静态方法,且接收的三个参数依次为:

  • ClassLoader loader:指定当前目标对象使用类加载器,用null表示默认类加载器
  • Class [] interfaces:需要实现的接口数组
  • InvocationHandler handler: 调用处理器,执行目标对象的方法时,会触发调用处理器的方法,从而把当前执行目标对象的方法作为

java.lang.reflect.InvocationHandler:这是调用处理器接口,它自定义了一个 invoke 方法,用于集中处理在动态代理类对象上的方法调用,通常在该方法中实现对委托类的代理访问。

// 该方法负责集中处理动态代理类上的所有方法调用。第一个参数既是代理类实例,第二个参数是被调用的方法对象
// 第三个方法是调用参数。
Object invoke(Object proxy, Method method, Object[] args)

动态代理工厂:

public class ProxyFactory {
    private Object target;
    public ProxyFactory(Object target){
        this.target = target;
    }

    public Object getProxyInstance(){
        return java.lang.reflect.Proxy.newProxyInstance(
                target.getClass().getClassLoader(),
                target.getClass().getInterfaces(),
                new java.lang.reflect.InvocationHandler() {
                    @Override
                    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                        System.out.println("执行动作前");
                        //执行目标对象方法
                        Object returnValue = method.invoke(target, args);
                        System.out.println("执行动作后");
                        return returnValue;
                    }
                }
        );
    }
}

测试代码

public class App {
    public static void main(String[] args) {
        HousePurchase people = new HousePurchasePeople("张三");
        System.out.println(people.getClass());

        HousePurchase proxy = (HousePurchase) new ProxyFactory(people).getProxyInstance();
        System.out.println(proxy.getClass());

        proxy.sure("1111");
    }
}

执行结果

在这里插入图片描述
在这里我们可以看到,执行sure操作是由代理去调用的,而不是people对象直接执行的,这个是不是和spring的aop非常想,arround?对的,spring的Aop原理就是动态代理,动态的在业务代码块外面包裹其他处理代码。这里我们只写了一个代理类,并没有限定代理对象,这个代理还可以代理别的对象,这是不是就是动态代理呢?

Cglib代理

  • 适用于被代理的对象未实现任何接口;
  • 上面的静态代理和动态代理模式都是要求目标对象实现一个接口或者多个接口,但是有时候目标对象只是一个单独的对象,并没有实现任何的接口,这个时候就可以使用构建目标对象子类的方式实现代理,这种方法就叫做:Cglib代理
  • Cglib代理,也叫作子类代理,它是在内存中构建一个子类对象从而实现对目标对象功能的扩展.
    • Cglib是一个强大的高性能的代码生成包,它可以在运行期扩展java类与实现java接口.它广泛的被许多AOP的框架使用,例如Spring AOP和synaop,为他们提供方法的interception(拦截)
    • Cglib包的底层是通过使用字节码处理框架ASM来转换字节码并生成新的子类.
    • 代理的类不能为final,否则报错;目标对象的方法如果为final/static,那么就不会被拦截,即不会执行目标对象额外的业务方法.

cglib不是jdk代理,这里就没有做代码测试了,可参考Java的三种代理模式

AOP(AspectOrientedProgramming):

将日志记录,性能统计,安全控制,事务处理,异常处理等代码从业务逻辑代码中划分出来,通过对这些行为的分离,我们希望可以将它们独立到非业务逻辑的方法中,进而改变这些行为的时候不影响业务逻辑的代码—解耦。
** 在Spring的AOP编程中: **

  • 如果加入容器的目标对象有实现接口,用JDK代理
  • 如果目标对象没有实现接口,用Cglib代理**

代理模式与装饰者模式的区别

UML类图基本没区别,都是实现同一个接口,一个类包装另一 个类。 两者的定义:

装饰器模式:能动态的新增或组合对象的行为
在不改变接口的前提下,动态扩展对象的功能

代理模式:为其他对象提供一种代理以控制对这个对象的访问
在不改变接口的前提下,控制对象的访问

装饰模式是“新增行为”,而代理模式是“控制访问”。关键就是我们如何判断是“新增行 为”还是“控制访问”。你在一个地方写装饰,大家就知道这是在增加功能,你写代理,大 家就知道是在限制。

参考

设计模式——proxy代理模式

发布了41 篇原创文章 · 获赞 22 · 访问量 5万+

猜你喜欢

转载自blog.csdn.net/qq1049545450/article/details/103258041