由《Head First 设计模式》的代理模式到Java动态代理

1.背景

书中提出要为已经存在的糖果机编写一份监视器代码,糖果机提供获取各种状态的接口,监视器则通过这些接口监视糖果机的状态。但是糖果机是一个远程对象,监视器要通过一些手段来获取这些数据。

2.过程分析

为了给监视器提供一个统一的接口,书中提出了远程代理的概念。就是在和监视器在一个地址空间里,创建一个代理对象,这个对象和远程的糖果机有着一样的接口,这样监视器操作远程糖果机就和操作本地代理一样,至于代理是如何通过网络访问到远程糖果机的并不需要监视器关心。大致的设计类图如下:

Main

在Proxy中通过一系列方式来操作被代理的RealSubject,将Proxy提供给外部使用,外部使用Proxy就和RealSubject一样,因为两者有着同样的接口,但是最终表现就是RealSubject的功能增强了。最常见的事例就是在web开发中,需要对一些方法进行事务控制,例如一次数据库的save操作,就需要在save方法开始和结束的地方加入事务控制的代码;或者是需要知道一个方法调用所耗费的时间,就需要在方法的开始和结束的地方加入获取时间的代码。一个良好的设计要求这些额外且又有相同功能的代码是不能和业务代码耦合在一起的,不然系统就很没有灵活性。试想下每添加一个需要事务控制的方法就要复制一些相同的代码进去,或者在测试环节下需要知道一个方法的耗费时间,在正式环境下这些代码又得去除,那么采用直接耦合的方式将会是一场噩梦。下面请看示例代码:

Subject接口:

public interface Subject {
	void doSomething(String thing);
	
	void doSometingMore();
}

RealSubject实现类:

public class RealSubject implements Subject {

	@Override
	public void doSomething(String thing) {
		
		System.out.println("In realsubject do  " + thing);
	}

	@Override
	public void doSometingMore() {
		System.out.println("In realsubject do something more " );
		
	}

}
Proxy代理类:

public class Proxy implements Subject {
	private Subject target;
	
	public Proxy(Subject target){
		this.target = target;
	}
	
	@Override
	public void doSomething(String thing) {
		doBefore();
		target.doSomething(thing);
		doAfter();
		
	}

	@Override
	public void doSometingMore() {
		doBefore();
		target.doSometingMore();
		doAfter();
	}
	private void doBefore(){
		System.out.println("In proxy do before");
	}
	
	private void doAfter(){
		System.out.println("In proxy do after");
	}
	
	public static void main(String [] args){
		Subject subject = new Proxy(new RealSubject());
		subject.doSomething("hello");
		subject.doSometingMore();
	}


}

输出结果:

In proxy do before
In realsubject do  hello
In proxy do after
In proxy do before
In realsubject do something more 
In proxy do after



可以认为RealSubject对应于实际应用中只实现业务相关功能的那些类。在Proxy中便添加事务管理,或者日志记录等代码,一般需要在多个类的的多个方法上添加这些代码,通过这样的方式可以方便地将业务代码和事务管理等代码进行解耦,并得到复用。这就是所谓的静态代理,也正式书中介绍的代理模式。这种方式的缺陷就是需要针对不同的接口实现不同的代理类,一种代理只服务于一种接口,就会造成会有很多的代理类;而且需要对接口中所有的方法编写代理,如果接口有很多方法的话势必会影响开发效率;一旦接口发生改变,不仅实现类需要修改,代理类也要进行修改。为了解决这些缺陷,实现业务类和委托类的正真解耦,Java提供了动态代理的技术。

3.动态代理

所谓动态代理顾名思义就是动态生成代理类,至于如何动态生成,上代码:

public class ProxyHandler implements InvocationHandler {
	private Object proxied;
	
	public ProxyHandler( Object proxied )   
	  {   
	    this.proxied = proxied;   
	  }   
	
	@Override
	public Object invoke(Object proxy, Method method, Object[] args)
			throws Throwable {
		doSomethingBefore(args);
		method.invoke(proxied, args);
		doSomethingAfter(args);
		return null;
	}
	
	private void doSomethingBefore(Object[] args){
		String things = "";
		if (args != null) {
			for (Object o : args) {
				things = things + (String) o;
			}
		}
		System.out.println("In proxy do before  "+ things);
	}

	private void doSomethingAfter(Object[] args){
		String things = "";
		if (args != null) {
			for (Object o : args) {
				things = things + (String) o;
			}
		}
		System.out.println("In proxy do after  "+ things);
	}
	
	public static void main(String [] args){
		RealSubject target = new RealSubject();
		Subject proxy = (Subject)java.lang.reflect.Proxy.newProxyInstance(Subject.class.getClassLoader(),
				new Class[]{Subject.class}, new ProxyHandler(target));
		proxy.doSomething("haha");
		proxy.doSometingMore();
	}
	
}
输出结果:

In proxy do before  haha
In realsubject do  haha
In proxy do after  haha
In proxy do before  
In realsubject do something more 
In proxy do after  



Java的动态代理机制为我们动态地生成了代理类,至于如何做到的,我们可以大致做以下猜想:

回想静态代理创建的过程,首先要实现某个接口,创建代理类。查看Proxy静态方法newProxyInstance的源代码:

public static Object newProxyInstance(ClassLoader loader,
					  Class<?>[] interfaces,
					  InvocationHandler h)
	throws IllegalArgumentException
    {
	if (h == null) {
	    throw new NullPointerException();
	}

	/*
	 * Look up or generate the designated proxy class.
	 */
        Class<?> cl = getProxyClass0(loader, interfaces); // stack walk magic: do not refactor

	/*
	 * Invoke its constructor with the designated invocation handler.
	 */
	try {
            final Constructor<?> cons = cl.getConstructor(constructorParams);
            final InvocationHandler ih = h;
            SecurityManager sm = System.getSecurityManager();
            if (sm != null && ProxyAccessHelper.needsNewInstanceCheck(cl)) {
                // create proxy instance with doPrivilege as the proxy class may
                // implement non-public interfaces that requires a special permission
                return AccessController.doPrivileged(new PrivilegedAction<Object>() {
                    public Object run() {
                        return newInstance(cons, ih);
                    }
                });
            } else {
                return newInstance(cons, ih);
            }
	} catch (NoSuchMethodException e) {
	    throw new InternalError(e.toString());
	} 
    }


Proxy的getProxyClass方法调用ProxyGenerator的 generateProxyClass方法产生ProxySubject.class的二进制数据,至于是如何产生的可以查看相关源代码。将产生的class文件进行反编译,可以看到如下代码片段:

import java.lang.reflect.*;   
public final class ProxySubject extends Proxy   
    implements Subject   
{   
    private static Method m1;   
    private static Method m0;   
    private static Method m3;   
    private static Method m2;   
    public ProxySubject(InvocationHandler invocationhandler)   
    {   
        super(invocationhandler);   
    }   
    public final boolean equals(Object obj)   
    {   
        try  
        {   
            return ((Boolean)super.h.invoke(this, m1, new Object[] {   
                obj   
            })).booleanValue();   
        }   
        catch(Error _ex) { }   
        catch(Throwable throwable)   
        {   
            throw new UndeclaredThrowableException(throwable);   
        }   
    }   
    public final int hashCode()   
    {   
        try  
        {   
            return ((Integer)super.h.invoke(this, m0, null)).intValue();   
        }   
        catch(Error _ex) { }   
        catch(Throwable throwable)   
        {   
            throw new UndeclaredThrowableException(throwable);   
        }   
    }   
    public final void doSomething()   
    {   
        try  
        {   
            super.h.invoke(this, m3, null);   
            return;   
        }   
        catch(Error _ex) { }   
        catch(Throwable throwable)   
        {   
            throw new UndeclaredThrowableException(throwable);   
        }   
    }......   

由此可以知道在动态生成的代理类中是利用Java的反射机制,获取到被代理类的所有Method,然后以Method做为实参调用ProxyHandler.invoke方法,而invoke方法里的代码是自己实现的,通过这种巧妙的方式来实现动态代理。机制写的不是很详细,可以自行查看源代码深入了解。


4.总结

一个典型的动态代理创建对象过程可分为以下四个步骤:
1、通过实现InvocationHandler接口创建自己的调用处理器 IvocationHandlerhandler = new ProxyHandler(args);
2、通过为Proxy类指定ClassLoader对象和一组interface创建动态代理类
Class clazz = Proxy.getProxyClass(classLoader,new Class[]{args});
3、通过反射机制获取动态代理类的构造函数,其参数类型是调用处理器接口类型
Constructor constructor = clazz.getConstructor(new Class[]{InvocationHandler.class});
4、通过构造函数创建代理类实例,此时需将调用处理器对象作为参数被传入
Interface Proxy = (Interface)constructor.newInstance(new Object[] (handler));
为了简化对象创建过程,Proxy类中的newInstance方法封装了2~4,只需两步即可完成代理对象的创建。
生成的ProxySubject继承Proxy类实现Subject接口,实现的Subject的方法实际调用处理器的invoke方法,而invoke方法利用反射调用的是被代理对象的的方法(Object result=method.invoke(proxied,args))

 

参考:

1.彻底理解JAVA动态代理

2.Java 动态代理机制分析及扩展

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值