10分钟了解 代理模式与java中的动态代理


前言

   代理模式又分为静态代理与动态代理,其中动态代理是Java各大框架中运用的最为广泛的一种模式之一,下面就用简单的例子来说明静态代理与动态代理。

场景

   李雷是一个唱片公司的大老板,很忙,但是经常有一些鸡毛蒜皮的小事总不能自己亲自去跑腿吧,大老板哪里有这么多时间,于是他找了一个助手叫做韩梅梅,韩梅梅就负责帮老板去处理这些事情。下面就模拟韩梅梅代理老板去处理一些小事的情况。

静态代理

代码

  • 老板类
public class Boss {
	
	private String name;
	
	private Boss(String name){
		this.name = name;
	}
	
	public void ok(){
		System.out.println(name +"说: 我觉得OK! :)");
	}
	
	public void notOk(){
		System.out.println(name + "说: 我觉得不行! :(");
	}
	
	public void normal(){
		System.out.println(name + "说: 我觉得很普通! XD");
	}

}

  • 助理类
public class Secretary {
	
	private Boss boss;
	
	private String name;

	public Secretary(String secreataryName,Boss boss){
		name = secreataryName;
		this.boss = boss;
	}
	
	public void ok(){
		boss.ok();
	}
	
	public void notOk(){
		boss.notOk();
	}
	
	public void normal(){
		boss.normal();
	}
}
  • 客户端
public class HanMMClient {
	public static void main(String[] args) {
		
		Boss boss = new Boss("李雷");
		Secretary proxy = new Secretary("韩梅梅", boss);
		
		proxy.ok();
		proxy.notOk();
		proxy.normal();
	}
}

这样一个最初版本的代理模式就完成了,客户端中并没有与老板直接接触就通过韩梅梅代理成功的调用了老板的三个方法。
可以发现 代理类韩梅梅与老板实现了相同的方法,因此将他们抽象成为一个Iboss接口,再分别实现,这样的静态代理模式就趋于完整了。

  • IBoss 接口
public interface Iboss {

	void ok();
	
	void notOk();
	
	void normal();
}
  • 客户端
public class HanMMClient {
	public static void main(String[] args) {
		
		Iboss boss = new Boss("李雷");
		Iboss proxy = new Secretary("韩梅梅",boss);
		
		proxy.ok();
		proxy.notOk();
		proxy.normal();
	}
}


运行一下,结果良好 😃

李雷说: 我觉得OK! :)
李雷说: 我觉得不行! :(
李雷说: 我觉得很普通! XD

  • 结构图

  • 静态代理的特点在于,是提前编写好的,编译期间就生成了代理,并且一个主题类对应一个代理类。

新的需求

   现在唱片公司有两个老板,李雷A与李雷B,他们分别对应的两个代理是韩梅梅A与韩梅梅B,现在要求韩梅梅们在代理的时候要声明自己是谁,这样确保是在正确的人在代理。
   乍一看,这还不简单,我在两个代理类的每个方法前都加上声明语句就好了。可是假设整个唱片集团需要100个代理,他们都要加声明该怎么办呢?难道实现100个代理类然后每个方法前面加声明吗?这样忒蠢了点,所以动态代理应运而生。

动态代理

  上面的需求很容易让人想到Spring中的AOP,事实上aop就是实现动态代理的一个很典型的例子。

  • 动态代理的特点是在于代理类在运行期间生成,一个代理类可以对应多个主题类

   动态生成代理类需要运用到Proxy这个类,api文档这样描述的

Proxy provides static methods for creating dynamic proxy classes and instances, and it is also the superclass of all dynamic proxy classes created by those methods. 

翻译一下,就是Proxy类提供可以创造动态类和实例的静态方法,同时也是这些被创造的类和实例的父类。
在这里需要使用其中的newProxyInstance方法来创造代理实例

public static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces,  InvocationHandler handler)  throws IllegalArgumentException

Returns an instance of a proxy class for the specified interfaces that dispatches method invocations to the specified invocation handler

再翻译一下,返回指定接口的代理类的实例,这些接口将方法调用分派给指定的调用处理程序。
说明白点就是,这个newProxyInstance可以根据参数动态生成一个代理类,下面是参数的解释

public static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces,  InvocationHandler handler)  throws IllegalArgumentException

loader: 类加载器,这个不用多说了,涉及到运行时操作必然要涉及到反射,通过类加载器装载类

interfaces: 一个Interface对象的数组,代表给代理对象提供一组接口实现,如果我提供了一组接口给它,那么这个代理对象就宣称实现了该接口,这样就能调用这组接口中的方法了

handler: 这个参数代表的是动态代理对象在调用方法的时候,会将方法转发到哪一个invocationHandler对象身上

通过这三个参数,类加载器装载,实现接口,以及调用方法的处理,就可以动态的生成一个代理类。

上面三个参数第三个参数时InvocationHandler对象,那这个是什么呢?
依旧是查看api文档

InvocationHandler is the interface implemented by the invocation handler of a proxy instance. 

Each proxy instance has an associated invocation handler. When a method is invoked on a proxy instance, the method invocation is encoded and dispatched to the invoke method of its invocation handler.

翻译 每一个动态代理类都必须要实现InvocationHandler这个接口,并且每个代理类的实例都关联到了一个handler,当我们通过代理对象调用一个方法的时候,这个方法的调用就会被转发为由InvocationHandler这个接口的 invoke 方法来进行调用
该接口只有一个方法,就是invoke,每次调用代理类的方法的时候,就会被转发到这里来处理,所以叫handler啦

Object invoke(Object proxy, Method method, Object[] args) throws Throwable

proxy:  生成的代理实例对象

method: 要调用真实对象的某个方法的Method对象

args:  真实对象某个方法时接受的参数

介绍完这两个类之后,下面编写我们的动态代理代码

  • 老板行为接口
public interface Iboss {

	void ok();
	
	void notOk();
	
	void normal();
}
  • 老板类
public class Boss implements Iboss{
	
	private String name;
	
	public Boss(String name){
		this.name = name;
	}
	
	@Override
	public void ok(){
		System.out.println(name +"说: 我觉得OK! :)");
	}
	
	@Override
	public void notOk(){
		System.out.println(name + "说: 我觉得不行! :(");
	}
	
	@Override
	public void normal(){
		System.out.println(name + "说: 我觉得很普通! XD");
	}

}
  • 动态代理类
public class DynamicProxy implements InvocationHandler{

	private Object subject;

	private String secretaryName;

	public DynamicProxy(Object subObject, String secretaryName) {
		this.subject = subObject;
		this.secretaryName = secretaryName;
	}

	@Override
	public Object invoke(Object proxy, Method method, Object[] methodParams)
			throws Throwable {
		
		//这里打印一下方法,看看究竟是不是接口里的方法呢
		System.out.println("Method :" + method);
		
		//before method
		System.out.printf("我是%s代理,我为老板代言!\n",secretaryName);
		
		method.invoke(subject, methodParams);
		
		//after method
		System.out.println("代理完了,谢谢!\n");
		
		return null;
	}

}

  • 客户端调用代码
public class HanMMClient {
	public static void main(String[] args) {

		Iboss realSubject = new Boss("李雷");

		/* 实例化 类加载器 clasLoader 参数,这里只要是appClassLoader级别的装载都可以装载 */
		ClassLoader loader = realSubject.getClass().getClassLoader();

		/* 实例化 代理类要实现的接口组 Interfaces 参数 */
		Class<?>[] interfaces = realSubject.getClass().getInterfaces();

		/* 实例化 调用方法的方法转发处理  handler参数 */
		DynamicProxy handler = new DynamicProxy(realSubject, "韩梅梅");

		Iboss subject = (Iboss) Proxy.newProxyInstance(loader,interfaces,handler);

		System.out.println("运行期间动态生成的代理类 类名是 : " + subject.getClass().getName()+"\n");

		subject.ok();
		subject.notOk();
		subject.normal();
	}
}
  • 运行结果
运行期间动态生成的代理类 类名是 : com.sun.proxy.$Proxy0

Method :public abstract void com.matrix.repository.Iboss.ok()
我是韩梅梅代理,我为老板代言!
李雷说: 我觉得OK! :)
代理完了,谢谢!

Method :public abstract void com.matrix.repository.Iboss.notOk()
我是韩梅梅代理,我为老板代言!
李雷说: 我觉得不行! :(
代理完了,谢谢!

Method :public abstract void com.matrix.repository.Iboss.normal()
我是韩梅梅代理,我为老板代言!
李雷说: 我觉得很普通! XD
代理完了,谢谢!

运行结果良好:)

控制台输出的第一行我打印了一下动态生成的代理类的类名,可以看到这个类是在proxy下的,肯定的嘛,毕竟你调用的方法就叫newProxyInstance方法,后面的$和数字代表他的标号,123456一直往下排
后面的控制台输出 Method 哪一行输出结果可以看到和猜想的一样,是通过提供的第二个参数,也就是接口组里的方法来定位的,然后通过执行
Method.invoke(subject,methodParms)
这一行,通过反射去调用了被代理类的方法,也就是说,起到调用效果的是这一行,和平常的new出对象然后调用是不一样的,这也就是为什么可以在这里加上before Method和after Method或者各种其他操作的原因。

相信你看到这里应该明白了什么是动态代理,同时aop的实现方式是怎么样的也不用说了吧?

总结

动态代理平常直接使用的不多,因为平常操作都是基于框架与组件之上的,但是如果你想要真正的了解其中的原理或者编写属于自己的框架,那么动态代理就十分重要啦_

posted @ 2017-10-03 20:35  祈求者-  阅读(833)  评论(0编辑  收藏  举报