设计模式系列:代理模式

一、问题引入

现在有这样一个场景:假设我们想邀请一位明星,那么一般我们不是直接联系明星,而是联系明星的经纪人,通过经纪人建立和明星的合作。在这里,明星就是一个目标对象,他只要负责活动演出就行了,而其他琐碎的事情就交给他的代理人(经纪人)来解决。这就是代理模式在现实中的一个应用场景。

同样地,假如我们想要租房,我们不可能每个小区每个住户去敲门问业主是否要出租,这样是不可行的,太浪费时间和精力了。我们只是想要租房,为什么要额外做这么多事呢?很简单,我直接通过中介公司来租房,由中介帮我们找房源,帮我们办理租房流程,签合同。我们只负责挑选自己喜欢的房源,然后付钱入住就可以了。这也是代理模式在现实中的一个应用。

二、模式定义

代理(Proxy)是一种设计模式,又叫委托模式,提供了对目标对象另外的访问方式,即通过代理对象去访问目标对象,由代理对象控制对原对象的访问。代理模式通俗来讲就是我们生活中常见的中介。这样做的好处是:可以在目标对象实现的基础上,增加额外的功能,即扩展目标对象的功能。同时还能起到隔离作用:在某些情况下,一个客户类不想或者不能直接引用一个委托对象,而代理类对象可以在客户类和委托对象之间起到中介的作用,方法就是代理类和委托类实现相同的接口。

开闭原则,增加功能:代理类除了是委托类的中介之外,还可以通过给代理类增加额外的功能来扩展委托类的功能,这样做我们只需要修改代理类而不需要再修改委托类,符合代码设计的开闭原则。代理类主要负责为委托类预处理消息、过滤消息、把消息转发给委托类,以及事后对返回结果进行处理等。代理类本身并不真正实现服务,而是同过调用委托类的相关方法,来提供特定的服务。真正的业务功能还是由委托类来实现,但是可以在业务功能执行的前后加入一些公共服务。例如,我们想给项目加入缓存、日志等功能,我们就可以使用代理类来完成,而没必要打开已经封装好的委托类。

代理模式可以分为静态代理和动态代理。静态代理是由程序员创建或特定工具自动生成源代码,在对其编译。在程序员运行之前,代理类.class文件就已经被创建了。动态代理是在程序运行时通过反射机制动态创建的。动态代理可以分为jdk动态代理和cglib动态代理。

1、静态代理

主题接口,具体类和代理类都需要实现的接口,里面的接口方法是要被代理的方法。如下:

//主题接口
public interface Subject {
    /**
     * 接口方法
     */
    public void request();
}

具体的实现类,是要被代理的类。如下:

//具体实现类
public class Concrete implements Subject {
    /**
     * 具体的业务逻辑实现
     */
    @Override
    public void request() {
        //业务处理逻辑
    }
}

代理类,相当于中介。如下:

//代理类
public class Proxy implements Subject {

    /**
     * 要代理的实现类
     */
    private Subject subject = null;

    public Proxy(Subject subject) {
        this.subject = subject;
    }

    /**
     * 实现接口方法
     */
    @Override
    public void request() {
        log.info("其他业务处理");
        
        this.subject.request();
        
        log.info("其他业务处理");
    }
}

测试类,客户端调用类。如下:

public class Client {
    public static void main(String[] args) {
        Subject subject = new ConcreteSubject();
        Proxy proxy = new Proxy(subject);
        proxy.request();
    }
}

2、JDK动态代理

定义一个和代理类相关联的InvacationHandler,如下:

//定义和代理类相关联的InvacationHandler
public class ProxyInvocationHandler<T> implements InvocationHandler
{
    //invocationHandler持有的被代理对象
    T target;
    
    public ProxyInvocationHandler(T target) {
        this.target = target;
    }
    
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable
    {
        log.info("其他业务处理");

        Object result = method.invoke(target, args);
        
        return result;
    }
}

测试类,客户端调用类。如下:

//调用测试类
public class Client {
    public static void main(String[] args) {
        Subject subject = new ConcreteSubject();
        
        //创建一个与代理对象相关联的InvocationHandler
        InvocationHandler invocationHandler = new ProxyInvocationHandler<Subject>(subject);
        
        //创建一个代理对象proxy,代理对象的每个执行方法都会替换执行Invocation中的invoke方法
        Subject proxy = (Subject)Proxy.newProxyInstance(Subject.class.getClassLoader(), new Class<?>[]{Subject.class}, invocationHandler);
        
        //执行方法代理方法
        proxy.request();
    }
}

3、cglib动态代理

JDK实现动态代理需要实现类通过接口定义业务方法,对于没有接口的类,如何实现动态代理呢,这就需要CGLib了。CGLib采用了非常底层的字节码技术,其原理是通过字节码技术为一个类创建子类,并在子类中采用方法拦截的技术拦截所有父类方法的调用,顺势织入横切逻辑。但因为采用的是继承,所以不能对final修饰的类进行代理。JDK动态代理与CGLib动态代理均是实现Spring AOP的基础。

定义代理类,如下:

public class CglibProxy implements MethodInterceptor {

    private Object target;
    
    public Object getInstance(final Object target) {
        this.target = target;
        Enhancer enhancer = new Enhancer();
        enhancer.setSuperclass(this.target.getClass());
        enhancer.setCallback(this);
        return enhancer.create();
    }

    public Object intercept(Object object, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
        log.info("其他业务处理");
        Object result = methodProxy.invoke(object, args);

        return result;
    }
}

定义测试类,如下:

public class CglibProxyTest {
    public static void main(String[] args){
    
        Subject subject = new ConcreteSubject();
        
        CglibProxy cglibProxy = new CglibProxy();
        
        Subject subjectProxy = (Subject) cglibProxy.getInstance(subject);
        
        subjectProxy.request();
    }
}

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

三、总结

代理解决的问题:当两个类需要通信时,引入第三方代理类,将两个类的关系解耦,让我们只了解代理类即可。代理模式还可以让我们完成与另一个类之间的关系的统一管理。但是切记,代理类和委托类要实现相同的接口,因为代理真正调用的还是委托类的方法。

代理模式使用了编程中的一个思想:不要随意去修改别人已经写好的代码或者方法,如果需改修改,可以通过代理的方式来扩展该方法。

 

参考资料:

1、https://www.cnblogs.com/leeego-123/p/10995975.html

2、https://www.cnblogs.com/daniels/p/8242592.html

 

posted @ 2020-08-04 17:27  Alan6  阅读(161)  评论(0编辑  收藏  举报