设计模式之代理模式

 

一、定义

在代理模式(Proxy Pattern)中,创建一个类代表另一个类的功能,提供了对目标对象另外的访问方式;即通过代理对象访问目标对象.这样做的好处是:可以在目标对象实现的基础上,增强额外的功能操作,即扩展目标对象的功能.

例如,你是一个小朋友在读幼儿园,这个时候你的小伙伴联系你去玩,因为你没有手机,所以就需要联系你的爸爸,然后你爸爸再通知你去玩。你爸爸就像是你的代理对象

二、意图

为其他对象提供一种代理以控制对这个对象的访问。侧重点为控制

三、优缺点

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

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

四、与其他设计的模式的对比

1、装饰模式和代理模式的区别

装饰器模式关注于在一个对象上动态的添加方法,然而代理模式关注于控制对对象的访问。

用代理模式,代理类(proxy class)可以对它的客户隐藏一个对象的具体信息。因此,当使用代理模式的时候,我们常常在一个代理类中创建一个对象的实例。

当我们使用装饰器模式的时候,我们通常的做法是将原始对象作为一个参数传给装饰者的构造器。

2、外观模式和代理模式的区别

代理与外观的主要区别在于,代理对象代表一个单一对象而外观对象代表一个子系统,代理的客户对象无法直接访问对象,由代理提供单独的目标对象的访问,而通常外观对象提供对子系统各元件功能的简化的共同层次的调用接口。代理是一种原来对象的代表,其他需要与这个对象打交道的操作都是和这个代表交涉的。

3、适配器模式和代理模式的区别

适配器模式改变所考虑的对象的接口,代理模式不能改变所代理对象的接口。

五、代理模式的分类

1、静态代理  2、动态代理  

静态代理是由程序员创建或特定工具自动生成源代码,再对其编译。在程序运行前,代理类的.class文件就已经存在了。动态代理是在程序运行时,通过运用反射机制动态的创建而成。

六、静态代理

1、包含角色

  • 抽象主题角色(Subject):声明了目标对象和代理对象的共同接口,这样一来在任何可以使用目标对象的地方都可以使用代理对象。
  • 具体主题角色(RealSubject):也称为委托角色或者被代理角色。定义了代理对象所代表的目标对象。
  • 代理主题角色(Proxy):也叫委托类、代理类。代理对象内部含有目标对象的引用,从而可以在任何时候操作目标对象;代理对象提供一个与目标对象相同的接口,以便可以在任何时候替代目标对象。代理对象通常在客户端调用传递给目标对象之前或之后,执行某个操作,而不是单纯地将调用传递给目标对象

2、代码结构

package proxy;

/**
 * @Package: proxy
 * @ClassName: Subject
 * @Author: tanp
 * @Description: 抽象主题角色(Subject):声明了目标对象和代理对象的共同接口,这样一来在任何可以使用目标对象的地方都可以使用代理对象
 * @Date: 2020/11/9 11:38
 */
public interface Subject {
    public void operrate();
}


package proxy;

/**
 * @Package: proxy
 * @ClassName: RealSubject
 * @Author: tanp
 * @Description: 具体主题角色(RealSubject):也称为委托角色或者被代理角色。定义了代理对象所代表的目标对象
 * @Date: 2020/11/9 11:39
 */
public class RealSubject implements Subject{
    @Override
    public void operrate() {
        System.out.println("do something");
    }
}

package proxy;

/**
 * @Package: proxy
 * @ClassName: Pro
 * @Author: tanp
 * @Description: 代理主题角色(Proxy):也叫委托类、代理类。代理对象内部含有目标对象的引用
 * @Date: 2020/11/9 11:40
 */
public class Proxy implements Subject{

    private Subject subject = null;

    @Override
    public void operrate() {
        if(subject == null){
            subject = new RealSubject();
        }
        //实际上调用的就是具体主题角色的方法
        subject.operrate();
    }
}


//使用
package proxy;

/**
 * @Package: proxy
 * @ClassName: DemoCient
 * @Author: tanp
 * @Description: ${description}
 * @Date: 2020/11/9 11:43
 */
public class DemoCient {
    public static void main(String[] args) {
        Subject subject = new Proxy();
        subject.operrate();
    }
}

3、静态代理总结

静态代理在使用时,需要定义接口或者父类,被代理对象与代理对象一起实现相同的接口或者是继承相同父类

可以做到在不修改目标对象的功能前提下,对目标功能扩展。但是也包含着缺点,那就是代理对象需要与目标对象实现一样的接口,所以会有很多代理类,类太多.同时,一旦接口增加方法,目标对象与代理对象都要维护.

七、动态代理

动态代理是指在运行时,动态生成代理类。即,代理类的字节码将在运行时生成并载入当前的ClassLoader。

1.相比静态类的好处

首先,不需要为真是主题写一个形式上完全一样的封装类,假如主题接口中的方法很多,为每一个接口写一个代理方法也是非常烦人的事,如果接口有变动,则真实主题和代理类都要修改,不利于系统维护;

其次,使用一些动态代理的生成方法甚至可以在运行时指定代理类的执行逻辑,从而大大提升系统的灵活性。

2、常用动态代理方式

Jdk动态代理

CGLIB动态代理

8、Jdk动态代理

在运行时,动态生成代理类。即,代理类的字节码将在运行时生成并载入当前的ClassLoader。

1、代码

package proxy;

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

/**
 * @Package: proxy
 * @ClassName: JdkProxy
 * @Author: tanp
 * @Description: 动态代理模式通过使用反射,可以在运行期决定加载哪个类,避免了一个类对应一个代理的问题;
 * 同时,通过统一的invoke方法,统一了代理类对原函数的处理过程,使用动态代理很大程度上减少了重复的代码,降低了维护的复杂性和成本
 * @Date: 2020/11/10 8:49
 */
public class JdkProxy implements InvocationHandler {


    private Object object = null;

    public Object newInstance(Object object){
        this.object = object;
        return Proxy.newProxyInstance(this.object.getClass().getClassLoader(),this.object.getClass().getInterfaces(),this);
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("指定动态处理器,执行目标对象的方法时,会触发事件处理器的方法");
        method.invoke(object, args);
        return null;
    }
}

package proxy;

/**
 * @Package: proxy
 * @ClassName: DemoCient
 * @Author: tanp
 * @Description: ${description}
 * @Date: 2020/11/9 11:43
 */
public class DemoCient {
    public static void main(String[] args) {
        //静态代理
        Subject subject = new Proxy();
        subject.operrate();

        //jdk动态代理
        Subject subject1 = (Subject)new JdkProxy().newInstance(new RealSubject());
        subject1.operrate();
    }
}

输出

2、代码讲解

Jdk通过java.lang.reflect.Proxy包来支持动态代理,在Java中要创建一个代理对象,必须调用Proxy类的静态方法newProxyInstance,该方法的原型如下:
Object Proxy.newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler handler) throws IllegalArgumentException

参数意义为:

  • ClassLoader loader,:表示类加载器,对于不同来源(系统库或网络等)的类需要不同的类加载器来加载。指定当前目标对象使用类加载器,获取加载器的方法是固定的
  • Class<?>[] interfaces,:表示接口或对象的数组,目标对象实现的接口的类型,使用泛型方式确认类型
  • InvocationHandler :事件处理,执行目标对象的方法时,会触发事件处理器的方法,会把当前执行目标对象的方法作为参数传入

3、总结

    在上面的的编程中,我们定义一个实现InvocationHandler接口的调用处理器对象,然后将它作为创建代理类实例的参数。这样就得到了代理对象。

真实对象本身的实例化在调用处理器对象内部完成,实例化时需要的参数也应该及时传入调用处理器对象中。这样一来就完成了代理对象对真实对象的包装,而代理对象需要执行的额外操作也在invoke方法中处理。

其后,在客户端中,如果需要使用真实对象时,就可以用代理对象来替代它了(有时需要类型强制转化)。

5、不传入实现接口的demo

该demo有些类似与mybatis的感觉,只传入一个mapper实现类,mybatis底层有默认的公共接口去实现mapper中的方法

package proxy.mybatis;

/**
 * @Author: tanp
 * @Description: 类似mybatis的实现mapper接口业务的公共调用方法
 * @Date: 2020/12/31 10:34
 */
public class MapperMethod {

    private int type;

    public MapperMethod(int type) {
        this.type = type;
    }

    public int execute(Object[] arg){
        if (type == 1){
            return insert(arg);
        }
        if (type == 2) {
            return update(arg);
        }
        return insert(arg);
    }
    public int insert(Object[] arg){
        System.out.println("新增,参数:" + arg[0]);
        return 1;
    }

    public int update(Object[] arg){
        return 1;
    }
}


package proxy.mybatis;

/**
 * @Author: tanp
 * @Description: 类似mybatis的mapper接口
 * @Date: 2020/12/31 10:33
 */
public interface BaseMapper {
    /**
     * <insert></insert>
     * <update></update>
     * @Description
     * @Date XML 10:41
     * @Author tanp
     */
    int insert(int id);

}



package proxy.mybatis;

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

/**
 * @ClassName: MyBatisProxy
 * @Author: tanp
 * @Description: jdk动态代理类
 * @Date: 2020/12/31 10:30
 */
public class MyBatisProxy implements InvocationHandler {

    private MapperMethod mapperMethod;

    public static <T>  T newInstance(Class<T> tClass) {
        MapperMethod mapperMethod = new MapperMethod(1);
        return (T) Proxy.newProxyInstance(tClass.getClassLoader(), new Class[]{ tClass }, new MyBatisProxy(mapperMethod));
    }


    public MyBatisProxy(MapperMethod mapperMethod) {
        this.mapperMethod = mapperMethod;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        //如果传进来是一个已实现的具体类(本次演示略过此逻辑)
        if (Object.class.equals(method.getDeclaringClass())) {
            try {
                return method.invoke(this, args);
            } catch (Throwable t) {
                t.printStackTrace();
            }
            //如果传进来的是一个接口(核心)
        } else {
            System.out.println("执行前");
            Object invoke = mapperMethod.execute(args);
            System.out.println("执行后");
            return invoke;
        }
        return null;
    }
}


package proxy.mybatis;

/**
 * @ClassName: Main
 * @Author: tanp
 * @Description: main方法模拟调用
 * @Date: 2020/12/31 10:33
 */
public class Main {

    public static void main(String[] args) {
        BaseMapper baseMapper = MyBatisProxy.newInstance(BaseMapper.class);
        baseMapper.insert(6);
    }
}


在上面的代码中,我们就可以看到mapper接口是没有传统意义上的接口的

9、CGLIB动态代理

Jdk的动态代理依靠接口实现,如果有些类并没有实现接口,则不能使用jdk代理,这就要用到CGLIB代理了。CGLIB是针对类来实现的,他的原理是对指定的目标类生成一个子类,并覆盖其中方法实现增强,但因为采用的是继承,所以不能对final修饰的类进行代理。

需要注意的是,使用cglib动态代理需要引入需要用到cglib-nodep的jar包

1、代码

package proxy;

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

import java.lang.reflect.Method;

/**
 * @Package: proxy
 * @ClassName: CglibProxy
 * @Author: tanp
 * @Description: ${description}
 * @Date: 2020/11/10 9:31
 */
public class CglibProxy implements MethodInterceptor {

    private Object object;

    public Object getInstance(Object object){
        this.object = object;

        //工具类,Cglib中的加强器,用来创建动态代理
        Enhancer enhancer = new Enhancer();
        //设置要创建动态代理的类
        enhancer.setSuperclass(this.object.getClass());
        //3.设置回调函数,这里相当于是对于代理类上所有方法的调用,都会调用Callback,而Callback则需要实现intercept()方法进行拦截
        enhancer.setCallback(this);
        //4.创建子类(代理对象)
        return enhancer.create();
    }

    @Override
    public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
        System.out.println("指定CGLIB动态处理器,执行目标对象的方法时,会触发事件处理器的方法");
        return methodProxy.invoke(object, objects);
    }
}

package proxy;

/**
 * @Package: proxy
 * @ClassName: DemoCient
 * @Author: tanp
 * @Description: ${description}
 * @Date: 2020/11/9 11:43
 */
public class DemoCient {
    public static void main(String[] args) {
        //静态代理
        Subject subject = new Proxy();
        subject.operrate();

        //jdk动态代理
        Subject subject1 = (Subject)new JdkProxy().newInstance(new RealSubject());
        subject1.operrate();

        //cglib动态代理
        RealSubject realSubject = (RealSubject)new CglibProxy().getInstance(new RealSubject());
        realSubject.operrate();
    }
}

结果

2、总结

 CGLIB创建的动态代理对象比JDK创建的动态代理对象的性能更高,但是CGLIB创建代理对象时所花费的时间却比JDK多得多。所以对于单例的对象,因为无需频繁创建对象,用CGLIB合适,反之使用JDK方式要更为合适一些。同时由于CGLib由于是采用动态创建子类的方法,对于final修饰的方法无法进行代理。

 

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值