Java设计模式之代理模式

1,058 阅读7分钟

一、前期回顾

上一篇文章《JAVA设计模式之模板方法模式和建造者模式》谈到了设计模式中建造类的模式,我们来回顾下。模板方法模式定义了核心的算法结构,然后子类可以实现某些特定结构的算法内容,建造者模式完全把核心的算法内容交给子类去实现。我们这篇博文来学习下最常用的设计模式,代理模式。

二、代理模式的定义与实践

定义:Provide a surrogate or placeholder for another object to control access to it.

翻译:为其他对象提供一种代理以控制对这个对象的访问。

代理模式还是比较好理解,就是委托别人做事情,比如我们要租房子,委托中介去找房子,这就是代理模式。代理模式分为静态代理模式动态代理模式,我们来结合代码看看代理模式如何实现的。

2.1 静态代理

/***
定义消费者接口,查找指定区域,指定价格以下的房子*/
public interface ICustomer {
    /**查找房子,指定区域,指定价格*/
    String findHouse(String location,int price);
}
/**真正的找房消费者,所以只关注找到这个目标**/
public class YungCustomer implements ICustomer {
    @Override
    public String findHouse(String location,int price) {
        System.out.println("找到了位于["+location+"],价格"+price+"以下的房子");
        return "找到了位于["+location+"],价格"+price+"以下的房子";
    }
}

/**找房子中介类,找到合适房子再反馈给指消费者**/
public class AgencyProxy implements ICustomer{
    private ICustomer coustomer;

    public AgencyProxy(ICustomer coustomer) {
        this.coustomer = coustomer;
    }


    @Override
    public String findHouse(String location,int price) {
        String result="没有符合条件的房子";
        before();
        if (null != coustomer){
            result= coustomer.findHouse(location,price);
        }
        after();
        return result;
    }

    private void before(){
        System.out.println("开始找房子");
    }
    private void after(){
        System.out.println("结束找房子");
    }
}
/***场景类*/
public class Client {
    public static void main(String[] args) {
        ICustomer customer = new YungCustomer();
        ICustomer proxy = new AgencyProxy(customer);
        String result = proxy.findHouse("钓鱼岛附近", 2000);
        System.out.println(result);
    }
}

输出结果:

开始找房子

找到了位于[钓鱼岛附近],价格2000以下的房子

结束找房子

静态代理模式比较简单,总结就是代理类中包含实际调用者的引用,当符合条件时候,在去调用真正的对象方法。

2.2 JDK动态代理

动态代理比较复杂一点,相对于静态代理的指定代理对象,动态代理是在运行时候才知道实际代理对象。动态代理应用比较广泛,我们用的最多的框架Spring中 AOP就采用了动态代理。我们来把上面的代码改造成动态代理。

/**动态代理handler**/
public class MyInvocationHandler implements InvocationHandler {
    private Object object;

    public MyInvocationHandler(Object object) {
        this.object = object;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        before();
        Object result= method.invoke(object,args);
        after();
        return result;
    }
    private void before(){
        System.out.println("开始找房子");
    }
    private void after(){
        System.out.println("结束找房子");
    }
}

public class Client {
    public static void main(String[] args) {
        // 获取动态代理对象。
        ICustomer customer = (ICustomer) Proxy.newProxyInstance(Client.class.getClassLoader()
                , new Class[]{ICustomer.class}
                , new MyInvocationHandler(new YungCustomer()));
        customer.findHouse("钓鱼岛附近", 2000);
    }
}

输出结果:


开始找房子

找到了位于[钓鱼岛附近],价格2000以下的房子

结束找房子

动态代理要求代理的类必须要有接口,同时实现InvocationHandler的接口,实现接口的invoke方法即可,在invoke方法内可以在真正执行方法的前后添加想做的事情,比如说记录日志,通知消息等等,这就是AOP的思想,在不改变原有业务代码的情况下,添加功能。但是jdk动态代理的缺点就是代理的类必须要有接口才行,为了弥补这个缺点,出现了一款不需要接口就能被代理的第三方库,CGLIB库。

2.3.CGLIB动态代理

2.3.1 CGLIB介绍

CGLIB是一个强大的、高性能的代码生成库。它被广泛使用在基于代理的AOP框架(例如Spring AOP和dynaop)提供方法拦截。在实现内部,CGLIB库使用了ASM这一个轻量但高性能的字节码操作框架来转化字节码,产生新类。

2.3.2 CGLIB的使用

既然是第三方库,我们需要添加maven依赖

<!-- https://mvnrepository.com/artifact/cglib/cglib -->
		<dependency>
			<groupId>cglib</groupId>
			<artifactId>cglib</artifactId>
			<version>3.2.9</version>
		</dependency>

CGLIB也和jdk动态代理一样,需要设置回调方法,在JDK动态代理中我们要实现 InvocationHandler,而在CGLIB中我们需要实现net.sf.cglib.proxy.MethodInterceptor接口作为回调接口。我们先看代码

/**实现回调接口**/
public class MyMthondInterceptor implements MethodInterceptor {
    @Override
    public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
        before();
        Object result = methodProxy.invokeSuper(o,objects);
        after();
        return result;
    }

    private void before(){
        System.out.println("开始找房子");
    }
    private void after(){
        System.out.println("结束找房子");
    }
}

public class Client {
    public static void main(String[] args) {
        //创建回调实例
        MyMthondInterceptor interceptor=new MyMthondInterceptor();
        //CGLIB创建实例
        Enhancer enhancer = new Enhancer();
        //设置需要代理的类
        enhancer.setSuperclass(YungCustomer.class);
        //设置回调类
        enhancer.setCallback(interceptor);
        //获取代理对象
        YungCustomer customer= (YungCustomer) enhancer.create();
        //执行方法
        customer.findHouse("钓鱼岛",1000);
    }
}

输出结果:

开始找房子
找到了位于[钓鱼岛],价格1000以下的房子
结束找房子

这里CGLIB实现结果和JDK动态代理完全一样。上面实现回调方法的时候需要注意,推荐使用methodProxy.invokeSuper(o,objects);,这里调用的是CJLIB库的方法,如果使用method.invoke(o,args);需要注意的是,这里使用的就是JDK的动态代理了,同时invoke的object必须是传入的代理实例,而不是方法中形参object,否则会导致死循环调用。同时考虑到性能,还是建议使用第一种调用方式。

CGLIB库还提供了很多其他的特性,比如回调方法过滤等等。有兴趣的同学可以自行研究。

三、代理模式的优点与缺点

优点

1.职责清晰

真实的角色实现实际的业务逻辑,不用关心其他非核心的业务逻辑。业务是业务,辅助功能是辅助功能,职责非常清晰。比如要实现日志功能,不用耦合在实际的业务代码中,只要做一个代理即可。

2.良好的扩展性

由于核心的业务逻辑已经封装好了,后面要增强业务功能,也可以使用代理模式代理增加功能即可。

缺点

1.类的臃肿

对于静态代理来说,如果过多的使用静态代理会带来类臃肿。一般会在接口协议转换中使用比较多代理模式。 2.复杂性 对于动态代理来说,设计到回调方法的实现,特别是CGLIB中的使用,还是带来了一定的复杂性。

3.性能

对于动态代理来说,都是运行期进行字节码操作,所以还是带来了一些性能损耗,但是这不能作为不使用动态代理的理由,任何东西都是有两面性的。

四、代理模式的使用场景

1.方法增强;比如增加日志,事务等功能。

2.远程RPC调用;现在很多分布式系统RPC调用都是采用了代理模式。

3.协议等的转换;比如需要适用另外一套协议接口,会使用代理模式,先转换为老协议,然后再调用实际的类去执行。

4.懒加载;有些框架会在开始的时候使用代理类来替换实际类,等到真正要使用该类的时候才进行加载,从而达到了懒加载的效果。

五、总结

本篇博客介绍了代理模式,代理模式分为静态代理和动态代理,动态代理又分为jdk动态代理和CGLIB动态代理。JDK动态代理必须要有接口才能使用,CGLIB弥补了这个缺陷,可以直接对类进行代理。同时CGLIB动态代理性能相对于JDK动态代理要优秀。

六、参考

《设计模式之禅》

七、推荐阅读

JAVA设计模式之模板方法模式和建造者模式

Java设计模式之工厂方法模式与抽象工厂模式

Java设计模式之单例模式

JAVA设计模式之开篇

带你走进java集合之ArrayList

带你走进java集合之HashMap

Java锁之ReentrantLock(一)

Java锁之ReentrantLock(二)