设计模式之代理模式

时间:2019-01-19
本文章向大家介绍设计模式之代理模式,主要包括设计模式之代理模式使用实例、应用技巧、基本知识点总结和需要注意事项,具有一定的参考价值,需要的朋友可以参考一下。

1 静态代理

有些时候,我们想做一些事但是自己没有资源或者自己做不好,就会想着花点钱请专业的人帮我们做。这是一种代理模式。比如我们要买车,都会去4S店,让他们帮我们办理繁琐的手续。抽象成代码如下:
买车的行为抽象成接口Something:

public interface Something {
	void buyCar();
}

买车的人Person类:

public class Person implements Something {
	private String name;
	public Person(String name){
		this.name = name;
	}
	
	@Override
	public void buyCar() {
		System.out.println(name + "是用户,要买车...");
	}
}

实现Something接口,表明要买车。

代理用户买车的4S店Agent类:

public class Agent implements Something {
	List<Person> personList;
	public Agent(List<Person> personList){
		this.personList = personList;
	}
	@Override
	public void buyCar() {
		System.out.println("我是中介,我可以帮助用户购车...");
		for (Person person : personList) {
			person.buyCar();
		}
		System.out.println("我是中介,买车流程结束...");
	}
}

代理类也实现接口Something。

客户端Client:

public class Client {
	public static void main(String[] args) {
		Person person = new Person("xiaoMing");
		Person person2 = new Person("xiaoLi");
		List<Person> personList = new ArrayList<Person>();
		personList.add(person);
		personList.add(person2);
 		Agent agent = new Agent(personList);
		agent.buyCar();
	}
}

运行结果:

我是中介,我可以帮助用户购车...
xiaoMing是用户,要买车...
xiaoLi是用户,要买车...
我是中介,买车流程结束...

上面的整个过程就是代理模式的一种。因为代理类Agent包含目标类Person的引用,在编译时已经确定代理的目标类,因此称为静态代理模式。其UML图如下所示:

静态代理角色:
行为抽象接口Something:抽象接口,代表要完成的任务。
目标类Person:买主类Person是实际想买车的,称为目标类,实现Something接口;
代理类Agent:代理类Agent代理目标类执行买车的过程,代理类含有目标类的对象,并实现Something接口的目的就是为了使代理类拥有目标类的功能,可用于替换目标类,即使用目标类的地方都可以使用代理类。
PS:此处是否可以把代理类内目标类的引用换成接口的引用?大家可以思考下。

由于在编译器就确定了代理的目标,因此每一个目标类都需要一个代理类来对应,在实际情况中则显得冗余。究其根本原因在于代理类和目标类共同实现一个接口,因此要把代理类和目标类解耦。解耦常用的方法是动态代理。

2 动态代理

动态代理,顾名思义就是动态生成目标类的代理类;如何动态的生成是关键。
此处,则需要用到java反射reflect包中的API;因此动态代理也成称为JDK代理。
动态代理的过程如下:
接口Something和目标类Person依旧不变。
代理类变成代理工厂:

public class ProxyFactory {
	private Object object;
	public ProxyFactory(Object object) {
		this.object = object;
	}
	
	public Object getProxyInstance(){
		return Proxy.newProxyInstance(object.getClass().getClassLoader(), 
				                      object.getClass().getInterfaces(), 
									  new InvocationHandler() {
			@Override
			public Object invoke(Object proxy, Method method, Object[] args)
					throws Throwable {
				Object returnValue = method.invoke(object, args);
				return returnValue;
			}
		});
	}
}

客户端Client:

public class Client {
	public static void main(String[] args) {
		Something person = new Person("xiaoShao");
		System.out.println(person.getClass());
		Something personProxy = (Something)new ProxyFactory(person).getProxyInstance();
		System.out.println(personProxy.getClass());
		personProxy.buyCar();
	}
}

运行结果:

class com.design.proxy.Person
class com.sun.proxy.$Proxy0
xiaoShao是用户,要买车...

针对代理工厂ProxyFactory有以下几点说明:
① 之所以称为代理工厂,是因为此处的代理类与目标类完全解耦,任何实现Something接口的目标类的代理都可以通过ProxyFactory生成,故就把此类当做生产代理类的工厂,需要的时候就可以来取。
② ProxyFactory类中使用java.lang.reflect.Proxy的newProxyInstance(arg…)方法返回一个代理的对象;方法中的三个参数分别为:类加载器、接口类型和代理实例需要执行的任务。也正是这些参数确定了代理类的类别,具体如何实现则是使用反射,关于反射后续会讲到。
③ 根据上面三个参数可知,生成目标类的代理类需要目标类的加载器和目标类的接口类型,因此动态动态代理是针对实现接口的类生成代理类的,故也称接口代理。关于类加载器后续会讲到。
动态代理的UML图:

可知,目标类和代理工厂是在客户端才有依赖的,是一个完全解耦的结果。
虽然动态代理克服了静态代理模式的缺点,能够动态的生成所有类的代理类,避免了代理类的冗余度,但同时有一个缺点,那就是目标类必须要有一个接口。在实际项目中,没有接口的类也需要生成代理,此时静态代理和动态代理都不好使。解决这个问题的方法是cglib代理。

3 CGLIB代理

CGLIB代理也称子类代理,它是在内存中构建一个子类对象从而实现对目标对象功能的扩展。
首先看看是如何实现的吧
没有接口的目标类Target:

public class Target {
	public void doSomething(){
		System.out.println("do something...");
	}
}

代理工厂类:

public class ProxyFactory implements MethodInterceptor {

	private Object object;
	public ProxyFactory(Object object) {
		this.object = object;
	}
	
	public Object getProxyInstance(){
		// 工具类
		Enhancer enhancer = new Enhancer();
		// 设置父类,即目标类的Class对象
		enhancer.setSuperclass(object.getClass());
		// 设置回调函数
		enhancer.setCallback(this);
		// 返回代理对象,子类
		return enhancer.create();
	}
	
	@Override
	public Object intercept(Object obj, Method method, Object[] args,
			MethodProxy proxy) throws Throwable {
		Object target = method.invoke(object, args);
		return target;
	}
}

客户端类:

public class Client {
	public static void main(String[] args) {
		Target target = new Target();
		Target targetPrxoy = (Target)new ProxyFactory(target).getProxyInstance();
		targetPrxoy.doSomething();
	}
}

代理工厂ProxyFactory类实现net.sf.cglib.proxy.MethodInterceptor接口,实现拦截方法intercept(),并使用Enhancer工具类来生成代理类。在使用cglib代理需要注意以下几点:
① net.sf.cglib.proxy.MethodInterceptor接口位于spring-core.jar包中。
② 拦截方法intercept()是用于执行代理类中的方法,如果目标对象的方法为final/static,那么就不会被拦截;
③ cglib是在内存中创建目标类的子类来实现代理,因此目标类不能为final类型;

关于CGLIB知识后续会讲到。
CGLIB代理的UML类图:

4 代理模式的使用场景

代理模式本身分为三种,可以根据不同的情况选择不同的模式。三种模式对比如下:

模式 实现原理 优点 缺点 使用场景
静态代理 代理类和目标类实现同一个接口,且代理类包含目标类的引用,使得代理类可替换目标类 从代理模式上来讲,代理模式通过代理类访问目标类,能访问目标类的地方都能访问代理类,在不方便使用目标类的情况下可以用代理类。 代理类内部含有目标类的对象,因此代理类与目标类一一对应;当目标类较多时,代理类则显得冗余。 业务简单的场景,目标类稳定的场景。
动态代理 通过目标类的类加载器和接口类型,运行期生成一个代理类 动态代理解决了静态代理的问题。通过代理工厂生成代理类,使得代理工厂与目标类解耦。 动态代理适用于实现接口的目标类,但实际情况中很多目标类都没有实现接口,这限制了动态代理的使用。 适用于为实现接口的目标类生成代理类。spring框架中AOP都有使用。
CGLIB代理 在内存中生成目标类的字节码 CGLIB代理解决了动态代理的问题。通过CGLIB包为没有接口的目标类生成代理类。 不能为final类型的类生成代理类;对于static/final类型的方法无法拦截并执行。 适用于为没有接口的目标类生成代理类。spring框架中AOP都有使用。

动态代理和CGLIB代理互相补充。

5 参考资料

http://www.cnblogs.com/cenyu/p/6289209.html
https://blog.csdn.net/maoyuanming0806/article/details/80186248