设计模式之静态代理&动态代理

    1.静态代理:

    我们先看一个这样的生活场景:当你找老板们去审批一个报销单的时候,往往是直接找不到老板本人,通常是通过老大的秘书是帮忙代劳的,大家都知道秘书和老板走的最近的,她可以帮老板们完成一些工作,这些工作是老大授权给他们的。我们通常找秘书相对也比较方便一点。这里其实就是我们代理的一个基本的原型:外界的请求不和真实的实际对象打交道,而是和真实的对象的代理打交道,通过代理对象来传递请求的对象的诉求和返回对应的结果给请求对象。下面我们按照老板和秘书的情况来举例看看静态代理怎么做。场景:员工找秘书请求老板批年假的申请。

   一般情况下,静态代理必须要有如下几个部分组成:抽象行为接口(批年假),真实对象(老板:具备批年假的权限),代理对象(老板的代言人,拥有老板赋予的部分权限,比如说批年假),客户端(员工)。

创建一个静态代理的步骤如下:

第一步:创建一个抽象的的行为接口

AbstractAction.java

package com.java.staticproxy;

/**
 * 
 * @author bluesky
 * 抽象行为类,定义对应的抽象方法
 */
public abstract class AbstractAction  {
	
	public abstract String request();

}

第二步:真实类(被代理类,上面说到的老板角色类)

RealSubject.java

package com.java.staticproxy;
/**
 * 
 * @author bluesky
 * 真实类,实现抽象接口所定义的方法,这里是动作的真正执行者
 */
public class RealSubject extends AbstractAction {

	@Override
	public String request() {
		return "同意年假申请";
	}

}

第三步:代理类(秘书角色),代理类需要同样实现抽象类所实现的方法,同时需要持有一个对真实类的引用(如秘书持有一个对老板类的应用,这个应用可以看做是老板所授予的对应特权给了秘书)。

ProxySubject.java

package com.java.staticproxy;

public class ProxySubject extends AbstractAction {

	RealSubject boss ;//代理对象通过一个对真是对象的应用来完成代理的动作
	
	public ProxySubject() {
		boss = new RealSubject();
	}
	
	public void preReq(){
		System.out.println("审批是否符合要求");
	}
	
	@Override
	public String request() {
	  preReq();
	  String ret =   boss.request();
	  System.out.println(ret);
	  postReq();
	  return ret;
	}

	public void postReq(){
		System.out.println("备案、结束");
	}
}


上面的preReq 和postReq表示的是在处理真实客户端的request的请求的时候代理类提前和最后做的一些动作,比如说秘书提前审查这个client客户端是否拥有年假的权限,有的话是否能申请这么多天?发现OK,审批可以了,然后进行一些备案啊等操作。这里就发现很多工作就灵活多了,秘书就能代替老板做很多事情了。

最后一步:客户端(员工请假请求)

Client.java

package com.java.staticproxy;

public class Client {

	/**
	 * @param args
	 */
	public static void main(String[] args) {
		// TODO Auto-generated method stub
		
		AbstractAction request = new ProxySubject();//这里用到了多态,看出来了没?
		request.request();

	}

}

OK,至此一个静态代理模式的基本上的结构都完成了。输出结果如下:

审批是否符合要求
同意年假申请
备案、结束

静态代理相对来说比较简单,但是这种方式有一定的局限性:

1)必须知道真实代理的角色,代理类越多,真实代理类跟着越多,真实代理类需要时限就真实存在。会造成代理类的不断庞大。一般场景用到的不是非常多,但是不代表不重要。还是要理解通透。


如何解决代理类过多的问题?动态代理。动态代理相对比较灵活,他是在运行时才决定所需要代理的类,。下面一起看看动态代理。

动态代理:

JAVA 动态代理位于

java.lang.reflect 包下面,主要涉及到两个类:
1)Interface InvocationHandler 类,这个类里面最重要的一个方法:
invoke(Object proxy,Method method, Object[] args) ,
官方api的描述是:
Processes a method invocation on a proxy instance and returns the result。处理一个代理实例的调用,同时返回结果。
参数proxy 表示代理类,method表述所需要调用的方法,args表述方法所调用的参数。
2)Proxy :代理类,Proxy的主要作用是在后期一次性调用该类的静态方法:
newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h)来一次性生成代理,该静态函数返回的对象就是所谓的代理类,此代理类用来调用被代理类(接口)里面所调用的方法。就是类似上面静态代理的subjectProxy方法.

动态代理的接口和实现接口的真实代理类和静态代理基本上,差别主要是在代理类的生成上不一样,静态代理是一个被代理类就有一个代理类,而动态代理是为了减少代理类而产生的,只有在需要的时候再产生代理类,相对比较灵活。
动态代理的创建步骤如下:
1)创建一个实现InvocationHandler的方法,这个类必须实现invoke方法,否则对应的代理类方法就无法调用起来。
2)创建被代理类的接口和代理类
3)通过Proxy类的静态方法:newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h)一次性生成一个代理。
4)通过代理调用对用的方法。
例子:
第一步:创建一个实现InvocationHandler的方法,这个类必须实现invoke方法,否则对应的代理类方法就无法调用起来。
DynamicSubject.java。
对类里面的对应的描述放在类里面了,请大家自己学习,不清楚的可以下面沟通。

package com.java.dynamicproxy;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;

/**
 * 
 * 该代理类的内部属性是Object类型,实际使用的时候通过该类的构造方法传递进来一个对象
 * 此外,该类还实现了invoke方法,该方法中的method.invoke其实就是调用被代理对象的将要
 * 执行的方法,方法参数是sub,表示该方法从属于sub,通过动态代理类,我们可以在执行真实对象的方法前后
 * 加入自己的一些额外方法。 
 *
 * 这个类其实类似于前面说的秘书类,但是仔细观察这个类很特殊的地方是这个类里面没有一点和realSub相关的信息,初始化部分都是
 * 用的是object的类型,invoke里面的都是通过main里面一次性生成代理实例传递过来的。这样就可以做到多个被代理类通过一个代理类
 * 动态的来实现代理,避免了一个被代理类就有一个代理类与之对应的冗余设计。
 */
public class DynamicSubject implements InvocationHandler {
	/**
	 * 这里用来传递真实的代理对象进来。但是这里不指定realSub,
	 * 因为动态代理里面是在运行时从Client客户端Proxy.newInstance里面生成代理的时候才指定
	 */
	Object sub;
	
	public DynamicSubject(Object obj) {
       this.sub = obj;
	}

	//Object proxy:代理实例本身,一般情况下用不上
	//method 所代理的那个方法对象
	//method方法所承载的参数
	@Override
	public Object invoke(Object proxy, Method method, Object[] args)
			throws Throwable {

		System.out.println("资格审核中");//可以自己加入一些准备等附加操作
		System.out.println("before invoke: "+method);
		
		//这里打印出来的其实和Client里面Proxy.newProxyInstance生成的对象是一样的
		System.out.println("Object proxy: "+ proxy.getClass().getName());
		
		//这个invoke方法必须调用,否则将不会调用到对应的Subject类的方法。
		Object ret = method.invoke(sub, args);
		
		System.out.println("done");
		System.out.println("after invoke: "+method);
		
		return ret;
	}

}

第二步: 创建被代理类的接口和代理类
这里其实有两部分,一个是被代理类,另外一个是被代理类所实现的接口。其中被代理类需要实现被代理类所实现的接口,好拗口,呵呵。

接口:Subject.java

package com.java.dynamicproxy;

public interface Subject {
	
	public String request();

}

被代理类:RealSubject.java

package com.java.dynamicproxy;

public class RealSubject implements Subject {

	@Override
	public String request() {
		System.out.println("同意年假申请");
		return "同意年假申请";
	}

}


第三步和第四步是放在一起的,都直接在测试函数里面去操作即可。

Client.java

package com.java.dynamicproxy;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Proxy;

public class Client {

	/**
	 * @param args
	 */
	public static void main(String[] args) {
		
		RealSubject realSub = new RealSubject();
		InvocationHandler dynamicProxy = new DynamicSubject(realSub);
		
		//如下代码一次性生成代理。你给他传入什么真实对象,他就能代理什么。比如说你这里传入的realSub2,那他就代理的realSub2所实现接口。
		//代理本身不做实际操作,实际操作都会转移到invocationhandler里面去处理。
		//第一个参数:类加载器,代理类所对应的类加载器。
		//第二个参数:代理类所实现的接口,就是RealSubject所实现的接口。 the list of interfaces for the proxy class to implement
              //第三个参数:the invocation handler to dispatch method invocations to
              // 这里的其实就是dynamicProxy ,因为dynamciProxy实现了invocationHandler 接口。
		//Subject sub = (Subject)Proxy.newProxyInstance(dynamicProxy.getClass().getClassLoader(), realSub.getClass().getInterfaces(), dynamicProxy);
		Subject sub = (Subject)Proxy.newProxyInstance(dynamicProxy.getClass().getClassLoader(), new Class<?>[]{Subject.class}, dynamicProxy);
		String ret = (String)sub.request();//调用被代理类的函数,调用该函数之后调用就会直接传递给InvocationHandler dynamicProxy 去执行对应的invoke方法去执行,并返回结果。
		
		System.out.println("代理结果是:"+ret);
		
		//这里的sub.getClass和DynamicSubject里面的
		//public Object invoke(Object proxy, Method method, Object[] args)
		//第一个参数是一样的,object proxy是从这个sub传递过去的。
		System.out.println(sub.getClass());
		
		System.out.println("=====================================华丽丽的分割线===============================");
		

	}

}


执行结果:

资格审核中
before invoke: public abstract java.lang.String com.java.dynamicproxy.Subject.request()
Object proxy: $Proxy0
同意年假申请
done
after invoke: public abstract java.lang.String com.java.dynamicproxy.Subject.request()
代理结果是:同意年假申请
class $Proxy0
=====================================华丽丽的分割线===============================

注意看Object proxy: $Proxy0 和class $Proxy0这两个打印,一个是在DynamicSubject 里面

System.out.println("Object proxy: "+ proxy.getClass().getName());的打印的,另外一个是在测试main函数里面调用<pre name="code" class="java">System.out.println(sub.getClass());打印出来的,可以看到,一次性生成的代理Subject sub 和传递到<pre name="code" class="java">public Object invoke(Object proxy, Method method, Object[] args) 里面的Object proxy 参数其实是一个东西。

 
 

当一次生生成的代理对象sub调用sub.request()之后,对应的处理流程会传递到实现了InvocationHandler的dynamicSubject对象当中的invoke方法中去,从打印的结果来看,也的确是执行到那块了。invoke方法里面调用了method.invoke(Object obj,Object args...)方法,这里的obj我们再最开始的时候用的是Object类型,和任何实际代理类无关,在需要什么样的代理类的时候就传递什么样的对象进去即可。args就是method所需要调用的参数。这里的object是在

InvocationHandler dynamicProxy = new DynamicSubject(realSub);传递进去的,就是realSubject对象,所以代理的是它。


OK,下面再看看一个动态代理类的另外一个案例,就能看到动态代理的相对灵活性了。还是按照上面说的创建一个动态代理类的步骤来做:

第一步:创建一个实现InvocationHandler的方法,这个类必须实现invoke方法,否则对应的代理类方法就无法调用起来。

CommonInvocationHandler.java

package com.java.dynamicproxy;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;

public class CommonInvocationHandler implements InvocationHandler {

	private Object foos;
	
	public CommonInvocationHandler(Object obj) {
           this.foos = obj;
	}
	
	public CommonInvocationHandler()
	{
		
	}
	
	
	public void setFoo(Object foo) {
		this.foos = foo;
	}

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

}

第二步: 创建被代理类的接口和代理类

被代理类为Foo.java,这里的代理类有两个,一个是FooImp1.java,另外一个是FooImp2.java
Foo.java
package com.java.dynamicproxy;

public interface Foo {

	void doAction();
	
}

FooImp1.java

package com.java.dynamicproxy;


public class FooImp1 implements Foo {

	@Override
	public void doAction() {
		
		System.out.println("in FooImpl doAction!");

	}

}

FooImp2.java

package com.java.dynamicproxy;

public class FooImp2 implements Foo {

	@Override
	public void doAction() {
		
		System.out.println("in FooImpl2 doAction!");

	}

}

第三步:生成代理和测试:
TestFoo.java

package com.java.dynamicproxy;

import java.lang.reflect.Proxy;

public class TestFoo {

	/**
	 * @param args
	 */
	public static void main(String[] args) {
		
		Foo f = null;
		
		CommonInvocationHandler commanhander = new CommonInvocationHandler();
		
		Foo f1 = (Foo) new FooImp1();
		commanhander.setFoo(f1);
		
		f = (Foo)Proxy.newProxyInstance(Foo.class.getClassLoader(), new Class<?>[]{Foo.class}, commanhander);

		f.doAction();
		System.out.println("========分割线,开始生成第二个代理了======================");
		commanhander.setFoo(new FooImp2());
		
		f = (Foo)Proxy.newProxyInstance(Foo.class.getClassLoader(), new Class<?>[]{Foo.class}, commanhander);

		f.doAction();
	}

}

执行结果:
in FooImpl doAction!
========分割线,开始生成第二个代理了======================
in FooImpl2 doAction!


这里前面几个都不用看,原理应该都明白了,我们看最后面一个测试类部分,一开始一次性生成代理,调用到commanhander对象中,此时commanhander的对象是用impl1初始化的,所以调用的是FooImpl1的doAction()函数,紧跟着commandhander调用setFoo函数将传入到自身的真是代理类变成FooImpl2,此后再生成代理,执行doAction()函数执行的就是FooImpl2的doAction了。上面的结果就不奇怪了。

动态代理相对来说比静态代理难的多,这块难而且重要,像spring等框架当中都是经常用的,这块还是有必要要掌握住。需要理解原理性的东西,建议大家的学习方式去针对上面的流程然后去读官方的API文档,不建议国人的中文翻译文档,这块每个人理解不一样,翻译过来的解释方式很可能对你的理解很不适合。


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值