设计模式之代理模式 原创

发布于 2022-2-12 15:59
浏览
1收藏

春节不停更,此文正在参加「星光计划-春节更帖活动

日积月累,水滴石穿 😄

什么是代理模式

代理模式,给某对象提供一个代理对象,并由代理对象控制对原对象的引用。讲的直白一点,就是找个人来帮我干原本由我来干的事情,而且找来的这个人干的可能比我还好。

代理模式的组成

  • 抽象角色:通过接口或抽象类声明真实角色需要实现的业务方法。也就是要干的事情,比如结婚、找房。

  • 代理角色:实现抽象角色,是真实角色的代理,通过真实角色的业务逻辑方法来实现抽象方法,并可以附加自己的操作。也就是找过来的人,比如婚庆公司、房产中介。

  • 真实角色:实现抽象角色,定义真实角色所要实现的业务逻辑,供代理角色调用。真实角色也就是我,我要结婚、我要找房。

代理模式的好处

  • 功能增强
  • 保护目标对象
  • 一定程度上降低了系统的耦合度

实现代理模式的方式

  • 静态代理:由我们程序员创建一个 Java 类,作为代理类。需要代理的真实对象是已经确认了的。
  • 动态代理
    • JDK动态代理:利用反射机制动态地生成代理对象。要求目标类与代理类实现相同的接口,若目标类没有实现接口,则无法使用 JDK 动态代理。
    • Cglib动态代理:Cglib可代理接口又可以代理类。通过继承目标类,生成子类。在生成的子类中重写父类的方法,也就是重写目标类的方法,实现对其功能的增强。既然是继承,那就要求目标类不能是 final 的,方法也不能是final的。

静态代理

就拿结婚来举例吧!

  • 结婚 属于抽象角色,也就是要干的事情。
  • 婚庆公司属于代理角色,帮忙布置婚礼现场、录制、主持婚礼。
  • 结婚人,属于真实角色。结婚人就想结婚然后洞房而已,为什么还要额外做这么多事呢,所以就找一家婚庆公司。

实例

  • 提供抽象角色,里面有业务方法 marry。
/**
 * 结婚
 */
public interface Marry {

    String marry(String marryPerson);

}

  • 代理角色,实现抽象角色。
/**
 * 婚庆公司
 */
public class Wedding implements Marry{

    /**
     * 代理对象中包含真实对象,而且这真实对象是确认的
     */
    private MarryPerson target = new MarryPerson();

    public String marry(String marryPerson) {
        marryBefore();
        String marry = target.marry(marryPerson);
        marryAfter();
        return marry;
    }

    /**
     * 结婚之前
     */
    private void marryBefore(){
        System.out.println("正式结婚前:婚庆公司布置现场、主持现场");
    }

    /**
     * 结婚之后
     */
    private void marryAfter(){
        System.out.println("结婚后:婚庆公司打扫卫生、视频剪辑");
    }
}
  • 真实角色,也实现抽象角色。
public class MarryPerson implements Marry{

     public String marry(String marryPerson) {
        System.out.println("我" + marryPerson + "结婚啦");
        return marryPerson;
    }
}
  • 测试
public class MarryMain {
    public static void main(String[] args) {

        Wedding wedding = new Wedding();
        wedding.marry("程序员小杰");
    }
}
结果:
正式结婚前:婚庆公司布置现场、主持现场
我程序员小杰结婚啦
结婚后:婚庆公司打扫卫生、视频剪辑    

你在准备选择婚庆公司的时候,发现了一家更好的婚庆公司 B,婚庆公司 B 正好在搞周年庆,价格便宜,套餐选项更多,你就选择了 婚庆公司 B 主办你的婚礼。

增加代理类

public class WeddingB implements Marry{

    /**
     * 代理对象中包含真实对象
     */
    private MarryPerson target = new MarryPerson();


    public String marry(String marryPerson) {
        marryBefore();
        String marry = target.marry(marryPerson);
        marryAfter();
        return marry;
    }

    /**
     * 结婚之前
     */
    private void marryBefore(){
        System.out.println("正式结婚前:婚庆公司B布置现场、主持现场、豪车接送");
    }

    /**
     * 结婚之后
     */
    private void marryAfter(){
        System.out.println("结婚后:婚庆公司B打扫卫生、视频剪辑、再赠送床上四件套");
    }
}
  • 测试
public static void main(String[] args) {
    WeddingB wedding = new WeddingB();
    wedding.marry("程序员小杰");
}

结果:
正式结婚前:婚庆公司B布置现场、主持现场、豪车接送
我程序员小杰结婚啦
结婚后:婚庆公司B打扫卫生、视频剪辑、再赠送床上四件套

优缺点

优点

  • 简单,便于理解

  • 符合开闭原则的情况下对目标对象进行功能扩展。

缺点

  • 需要为每一个目标对象都创建一个或多个代理类,增加了维护成本以及开发成本。
  • 抽象角色有所改动会影响到代理角色。

动态代理

JDK代理

使用 JDK 动态代理就不需要手动创建代理类,利用反射机制动态地生成代理对象。要求目标类与代理类实现相同的接口,若目标类没有实现接口,则无法使用 JDK 动态代理。

小杰还是使用结婚来举例吧!进行改造。

  • MarryProxy

创建类 MarryProxy并实现InvocationHandler接口。

public class MarryProxy implements InvocationHandler {

    //内部还是要包含 目标对象,由外部进行传入
    private Object target;

    public MarryProxy(Object target) {
        this.target = target;
    }

    //重写 invoke 方法
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        before();
        //使用反射调用方法 
        //传入 目标对象 与 方法参数
        Object invoke = method.invoke(target, args);
        System.out.println("invoke 方法返回值:" + invoke);
        after();
        return invoke;
    }


    /**
     * 结婚之前
     */
    private void before(){
        System.out.println("正式结婚前:婚庆公司B布置现场、主持现场、豪车接送");
    }

    /**
     * 结婚之后
     */
    private void after(){
        System.out.println("结婚后:婚庆公司B打扫卫生、视频剪辑、再赠送床上四件套");
    }
}
  • 测试类
public static void main(String[] args) {
   //1、创建目标对象 MarryPerson
   Marry person = new MarryPerson();
   //2、创建 InvocationHandler 对象 并传入目标对象
   MarryProxy marryInvocationHandler = new MarryProxy(person);
    
    //3、创建代理对象 ,并将代理对象强转为 Marry 接口,不能转为 MarryPerson 类
    //MarryPerson 与 代理对象都是实现 Marry 接口,两者是并列关系
   Marry proxy = (Marry) Proxy.newProxyInstance(Marry.class.getClassLoader(),
     new Class[] {Marry.class}, marryInvocationHandler);
    
   //4、调用 marry 方法并传入参数 最终会调用 InvocationHandler 接口的 invoke 方法
   String s = proxy.marry("程序员小杰");
   System.out.println("调用 marry 返回值 = " + s);
}

结果:
正式结婚前:婚庆公司B布置现场、主持现场、豪车接送
我程序员小杰结婚啦
invoke 方法返回值:程序员小杰
结婚后:婚庆公司B打扫卫生、视频剪辑、再赠送床上四件套
调用 marry 返回值 = 程序员小杰

可以发现代理类对象由反射进行产生,并不需要再由程序员手动进行创建了。

在上面我们需要注意几个方法。小杰分别介绍一下:

  • newProxyInstance
 public static Object newProxyInstance(ClassLoader loader,
                                          Class<?>[] interfaces,
                                          InvocationHandler h)

newProxyInstance方法属于Proxy类的静态方法,Proxy类位于 java.lang.reflect包,该方法有三个参数,含义分别如下:

  • loader:类加载器,将类加载到 JVM 中。可以使用反射得到,比如:MarryPerson.class.getClassLoader()
  • interfaces:目标类实现的接口,可以传入多个。可以通过反射获取,比如:MarryPerson.class.getInterfaces()或者new Class[]{Marry.class}。这里一定传入接口哦!否则会出 Xxx is not an interface异常。
  • h:代理类需要完成的逻辑。

newProxyInstance方法返回值为:返回代理对象。

设计模式之代理模式-开源基础软件社区

  • invoke
 public Object invoke(Object proxy, Method method, Object[] args)

InvocationHandler接口位于java.lang.reflect包中,接口中就一个 invoke方法,该方法有三个参数,含义分别如下:

  • proxy:JDK 动态创建的代理对象。
  • method:代理类执行的方法。
  • args:需要传给方法的参数。

invoke方法返回值的含义为:目标方法的返回值。

JDK动态代理的要求还是挺严格的,必须基于接口创建代理类。如果我就是不想写接口怎么办,有什么方式可以产生代理类吗?那这时候就需要使用 Cglib动态代理方式了,接下来小杰就来介绍介绍它。

Cglib代理

Cglib是第三方工具库,Cglib可代理接口又可以代理类。通过继承目标类,生成子类。在生成的子类中重写父类的方法,也就是重写目标类的方法,实现对其功能的增强。既然是继承,那就要求目标类不能是 final 的,方法也不能是final的,如果是 final那也没办法进行继承。

  • 需要加入第三方依赖:
<dependency>
    <groupId>cglib</groupId>
    <artifactId>cglib-nodep</artifactId>
    <version>3.1</version>
</dependency>
  • 创建 MarryCglibProxy类并继承MethodInterceptor
public class MarryCglibProxy implements MethodInterceptor {
    private Object target;

    public MarryCglibProxy(Object target) {
        this.target = target;
    }

    public Object intercept(Object o, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
        before();
        Object invoke = method.invoke(target, args);
        System.out.println("intercept 方法返回值:" + invoke);
        after();
        return invoke;
    }

    /**
     * 结婚之前
     */
    private void before(){
        System.out.println("正式结婚前:婚庆公司B布置现场、主持现场、豪车接送");
    }

    /**
     * 结婚之后
     */
    private void after(){
        System.out.println("结婚后:婚庆公司B打扫卫生、视频剪辑、再赠送床上四件套");
    }
}

代理接口

 public static void main(String[] args) {
   //会在 D:\gongj\demo1 目录下生成 Cglib的源码,如果可以反编译工具打开
   System.setProperty(DebuggingClassWriter.DEBUG_LOCATION_PROPERTY,
     "D:\\gongj\\demo1");
   //1、创建目标对象 MarryPerson
   Marry person = new MarryPerson();
   //2、创建 MethodInterceptor 对象 并传入目标对象
   MarryCglibProxy marryCglibProxy = new MarryCglibProxy(person);
   //3、创建代理对象 ,并将代理对象强转为 Marry 对象
   //传入 Marry 接口
   Marry proxy = (Marry) Enhancer.create(Marry.class, marryCglibProxy);
   //4、调用 marry 方法并传入参数 最终会调用 MethodInterceptor 接口的 intercept 方法
   String s = proxy.marry("程序员小杰");
   System.out.println("调用 marry 返回值 = " + s);
 }

结果:
  正式结婚前:婚庆公司B布置现场、主持现场、豪车接送
  我程序员小杰结婚啦
  intercept 方法返回值:程序员小杰
  结婚后:婚庆公司B打扫卫生、视频剪辑、再赠送床上四件套
  调用 marry 返回值 = 程序员小杰
  • 源码

设计模式之代理模式-开源基础软件社区

代理实现类

public static void main(String[] args) {
   //会在 D:\gongj\demo1 目录下生成 Cglib 的源码,如果可以反编译工具打开
  System.setProperty(DebuggingClassWriter.DEBUG_LOCATION_PROPERTY,
   "D:\\gongj\\demo1");
  //1、创建目标对象 MarryPerson
  Marry person = new MarryPerson();
  //2、创建 MethodInterceptor 对象 并传入目标对象
  MarryCglibProxy marryCglibProxy = new MarryCglibProxy(person);
  //3、创建代理对象 ,并将代理对象强转为 Marry 对象,也可以强转为 MarryPerson 对象
  // person.getClass():传入 MarryPerson 实现类
  Marry proxy = (Marry) Enhancer.create(person.getClass(), marryCglibProxy);
  //4、调用 marry 方法并传入参数 最终会调用 MethodInterceptor 接口的 intercept 方法
  String s = proxy.marry("程序员小杰");
  System.out.println("调用 marry 返回值 = " + s);
}
结果:
正式结婚前:婚庆公司B布置现场、主持现场、豪车接送
我程序员小杰结婚啦
intercept 方法返回值:程序员小杰
结婚后:婚庆公司B打扫卫生、视频剪辑、再赠送床上四件套
调用 marry 返回值 = 程序员小杰
  • 源码

    设计模式之代理模式-开源基础软件社区

Cglib代理JDK 代理是很相似的,都要实现代理器接口完成。还是需要注意几个方法。小杰分别介绍一下:

  • create
public static Object create(Class type, Callback callback)

create方法属于Enhancer类的静态方法,Enhancer类位于 net.sf.cglib.proxy包,该方法有两个参数,含义分别如下:

  • type:指定目标类的类型。可以传入接口或者类,如果传入的是接口,代理类的源码是实现了该接口和Factory接口,如果传入的是类,源码是继承了类并实现了Factory接口。
  • callback:回调,意思是我们提供一个方法,它会在合适的时候调用我们提供的方法。Callback 是一个接口,并不包含方法的声明,我们通常使用它的子接口 MethodInterceptor

create方法返回值的为:返回代理对象。

设计模式之代理模式-开源基础软件社区

  • MethodInterceptor
public Object intercept(Object o, Method method, Object[] args, MethodProxy methodProxy)

可以发现该方法的参数跟 JDK动态代理的参数差不多,就是多了一个参数 methodProxy,其余参数的意义是一致的。

  • methodProxy:方法代理,不需要过于关心。

其实不论是使用 JDK动态代理实现 InvocationHandler 还是使用Cglib动态代理实现MethodInterceptor,我们都只需要关心 methodargs两个参数。

©著作权归作者所有,如需转载,请注明出处,否则将追究法律责任
已于2022-2-15 10:31:45修改
3
收藏 1
回复
举报
回复
添加资源
添加资源将有机会获得更多曝光,你也可以直接关联已上传资源 去关联
    相关推荐