设计模式之代理模式

203 阅读3分钟

使用

代理模式的使用广泛见于Spring AOP中,核心功能就是在不修改原来的类的代码的基础上,为类添加新的功能和属性。 比如用作性能分析和日志记录的切面,实质上就是一个代理类。它要代理的主题类就是AOP中的切点。

基本模式

一个接口ISubject, 实体对象RealSubject实现它。

代理对象Proxy同样实现ISubject接口,但实现的方式是通过直接调用内部持有的RealSubject对象的request()方法。

客户端或是使用者(Client), 只通过Proxy来执行对应的request()

Java实现:

public interface ISubject{
	void request();
}

public class RealSubject implements ISubject{
	@Override
	void request();
} 

public class Proxy implements ISubject{
	private RealSubject realSubject;
	@Override
	void request(){
		preRequest();
		realSubject.request();
		postRequest();
	}
}

动态代理

上面的基本模式其实是一种静态代理,每一个代理类和一个主题类一一对应。

那么如果要代理的类非常的多,并且preRequest方法和postRequest方法执行的逻辑都一样。我们还需要为每一个主题类编写一个代理类吗?

答案是不需要。

这就是动态代理。

JDK动态代理

考虑这样一个应用:两种渠道接收信件。电子邮件和邮局。 对到来的信件都要登记。 现在要利用代理模式为登记增加计数功能,对不同方式到来的信件分别统计。

类图:

  1. 抽象主题IRegist
public interface IRegist {
    void regist();
}
  1. 两个具体主题
public class fromEmail implements IRegist {
    @Override
    public void regist() {
        System.out.println("from Email");
    }
}
public class fromPost implements IRegist {
    @Override
    public void regist() {
        System.out.println("from Post");
    }
}
  1. 定义计数实现类CountInvoke
//InvocationHandler是java反射包中的接口
//实现该接口以供后面生成代理对象
public class CountInvoke implements InvocationHandler {
    private int count = 0;
    //具体主题对象
    //可以推广到任意主题对象的计数
    private Object obj;

    public CountInvoke(Object obj) {
        this.obj = obj;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) 
    	throws Throwable {
        count++;
        method.invoke(obj, args);
        return null;
    }

    public int getCount(){ return count; }
}
  1. 创建GenricProxy
public class GenericProxy {
    public static Object createProxy(Object obj, InvocationHandler invokeObj){
    	//代理obj类的所有接口方法
        Object proxy = Proxy.newProxyInstance(obj.getClass().getClassLoader(),
                    obj.getClass().getInterfaces(), invokeObj);
        return proxy;
    }
}
  1. 测试类Test
public class Test {
    public static void main(String[] args) {
        IRegist email = new fromEmail();
        IRegist post = new fromPost();
        //同一个切面实现类
        //类内部实现了invoke方法
        //可以处理两个不同的对象
        CountInvoke emailInvoke = new CountInvoke(email);
        CountInvoke postInvoke = new CountInvoke(post);

        //通过GenericProxy工厂类生成不同的proxy对象
        IRegist emailProxy = (IRegist) GenericProxy.createProxy(email,
                    emailInvoke);

        IRegist postProxy = (IRegist) GenericProxy.createProxy(post,
                postInvoke);
        //只调用了regist()
        //却实现了计数
        for (int i = 0; i < 5; i++) {
            emailProxy.regist();
        }

        for (int i = 0; i < 10; i++) {
            postProxy.regist();
        }
        //email count = 5
        System.out.println("email count = " + emailInvoke.getCount());
        //post count = 10
        System.out.println("post count = " + postInvoke.getCount());
    }
}

虚拟代理

如果需要创建一个资源消耗很大的对象,先创建一个相对较小的对象来表示。 真实对象只有在需要时才会被真正创建。 当用户请求一个“大”对象时,虚拟代理在该对象真正被创建出来之前扮演着替身的角色。 当该对象被创建出来之后,虚拟代理将用户的需求直接委托给该对象。

  1. 主题接口
public interface IItem {
    int getId();
    String getName();
    //大对象
    BigObject getBigObject();

    void itemFill();
}
  1. 具体主题
public class RealItem implements IItem{
    private int id;
    private String name;
    private BigObject bigObject;

    @Override
    public int getId() {
        return 0;
    }

    @Override
    public String getName() {
        return null;
    }

    @Override
    public BigObject getBigObject() {
        return null;
    }

    //填充大对象
    @Override
    public void itemFill() {
        bigObject = new BigObject();
    }
}
  1. 代理类
public class ProxyItem implements IItem {
    private RealItem realItem;
    //关键的标志位
    private boolean isFill;

    public ProxyItem(RealItem realItem) {
        this.realItem = realItem;
    }

    @Override
    public int getId() {
        return realItem.getId();
    }

    @Override
    public String getName() {
        return realItem.getName();
    }

    @Override
    public BigObject getBigObject() {
        return realItem.getBigObject();
    }

    @Override
    public void itemFill() {
        if(!isFill){
            realItem.itemFill();
            isFill = true;
        }
    }
}

isFill标志位的值,若考虑多线程情况需要添加volatile关键字。保证只创建并填充一次。