Java动态代理总结

a) 什么是Proxy Design Pattern?

在说Java动态代理之前,先复习下代理模式(Proxy Pattern)。如下类图所示:

Java动态代理总结

一旦有了代理,它将代替被代理类去参与程序的业务逻辑。代理和被代理都实现了同样的接口,并且代理类会hold一个被代理类,这样当在代理上调用业务方法的时候,代理可以把真正的核心逻辑仍然让被代理类去完成。

 

b) Java的动态代理(Dynamic Proxy) 是怎么回事?

简单说动态代理是Java提供的一种机制,它是在运行的时候基于一个或多个接口(Interface)创造出实现该接口或多个接口的代理类实例。值得再加强一提的是,Dynamic Proxy是基于接口 的,它能为单个或者多个接口创建一个代理类实例。这个机制主要有两个类参与实现:

    - java.lang.reflect.Proxy

    - java.lang.reflect.InvocationHandler

 

1. java.lang.reflect.Proxy

Proxy是一个实现了Serializable的具体类,它里面的方法主要就是用来根据必要的条件创建出指定接口或者是多个接口的代理类。下面就是其提供的常用的public方法:

private final static Class[] constructorParams =
	{ InvocationHandler.class };
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 = getProxyClass(loader, interfaces);

	/*
	 * Invoke its constructor with the designated invocation handler.
	 */
	try {
	    Constructor cons = cl.getConstructor(constructorParams);
	    return (Object) cons.newInstance(new Object[] { h });
	} catch (NoSuchMethodException e) {
	    throw new InternalError(e.toString());
	} catch (IllegalAccessException e) {
	    throw new InternalError(e.toString());
	} catch (InstantiationException e) {
	    throw new InternalError(e.toString());
	} catch (InvocationTargetException e) {
	    throw new InternalError(e.toString());
	}
    }

 

从这个方法的参数可以知道,要成功创建一个Proxy Instance,要具备以下条件:

1) ClassLoader ,一般使用当前对象的就可以了
2) Class<?>[] ,接口数组,这就是生成的代理类最后实现的接口
3) InvocationHandler ,配备一个实现了这个接口的类实例是一定 需要的,当在生成好的代理类实例上调用接口中的方法时,这些方法调用会被派发到这个InvocationHandler中去,下面将分作一节详细说它。

 

2. java.lang.reflect.InvocationHandler

这是一个接口,里面只有一个方法如下:

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

  上面说到了,当在动态产生好的代理类实例上调用接口方法时,这个方法调用会被转到这个invoke方法里面来,就连Object中的hashCode, equal还有toString方法也躲不过,对它们的调用会被通通转到invoke里面来。这样在实际使用中,我们让InvocationHandler的实现类hold住一个被代理的类,然后就可以在invoke的里面对被代理的类方法进行所谓的拦截了,即使在被代理类的业务方法调用前或后另作一些其他的工作了。

 

这里结合最开始的类图给出代码举例。

首先是Client

public class Client {
    public static void main(String[] args) {
        Subject concreteSubject = new ConcreteSubject();
        Class[] interfaceNeedsImp = new Class[] {Subject.class};
        InvocationHandler handler = new SubjectProxy(concreteSubject);
        ClassLoader cl = Subject.class.getClassLoader();

        //Here the type casting you must use the interface
        //if using the concrete class, shxt will happen cause
        //the returned instance is the instance of the generated proxy class
        Subject proxySubject = (Subject) java.lang.reflect.Proxy.newProxyInstance(cl,
                interfaceNeedsImp,
                handler);

        proxySubject.greet();
        System.out.println();
        proxySubject.request();
    }
}

 值得一提的是注意转型那里,要用接口来转,用具体类转要出错。

 

Subject接口

//弄了两个方法表明是业务方法
public interface Subject {
    Subject request();
    void greet();
}
 

ConcreteSubject的代码

public class ConcreteSubject implements Subject {
    public void greet() {
        System.out.println("    greet() of ConcreteSubject invoked");
    }
    public Subject request() {
        System.out.println("    request() of ConcreteSubject invoked");
        return this;
    }
}

 

SubjectProxy的代码

 

public class SubjectProxy implements InvocationHandler {
    private Subject aConcreteSubject;
    public SubjectProxy(Subject target) {
        aConcreteSubject = target;
    }
    public Object invoke(Object proxy, Method method, Object[] args)
            throws Throwable {
        System.out.println("Proxy prints this before " + method.getName()
                                      + ".() of ConcreteSubject");
        Object o = method.invoke(aConcreteSubject, args);
        if (o instanceof Subject) {
            System.out.println("Proxy prints this after concrete " + method.getName()
                                      + ".() of ConcreteSubject");
            return proxy;
        }
        System.out.println("Proxy prints this after concrete" + method.getName()
                                      + ".() of ConcreteSubject");
        return o;
    }
}

 这里这个类和类图中传统的Proxy Pattern有出入了,这个SubjectProxy没有去实现Subject接口,而是实现了InvocationHandler。在Client类中,Proxy会把SubjectProxy和它需要实现的Subject做绑定的,也就是下面几句Client的代码,小小的重复贴一下之前在Client中的几行关键代码如下:

Subject proxySubject = (Subject) java.lang.reflect.Proxy.newProxyInstance(cl,
                interfaceNeedsImp, //一群等待被实现的"接口"同志们
                handler);

 这差不多就是Java的动态代理机制了。下面是程序的输出,一目了然,ConcreteSubject的方法都被拦截了:

 

 

Proxy prints this before greet.() of ConcreteSubject
    greet() of ConcreteSubject invoked
Proxy prints this after concretegreet.() of ConcreteSubject

Proxy prints this before request.() of ConcreteSubject
    request() of ConcreteSubject invoked
Proxy prints this after concrete request.() of ConcreteSubject

 

额外需要注意的:

 

 

1. 网上有好多人在讨论InvocationHandler中invoke方法的第一个参数proxy怎么用?

答案是有时候接口方法会把当前接口引用作为返回值返回,参考上面例子中的request()业务方法。它返回了Subject引用,如果在代理中拦截到该方法不做特别处理的话,返回出去的就是ConcerteSubject的引用,若想继续使用SubjectProxy到底,就可以判断一下返回参数proxy。所以在上面例子里的SubjectProxy中有下面的代码段:

if (o instanceof Subject) {
            System.out.println("Proxy prints this after concrete " + method.getName()
                                        + ".() of ConcreteSubject");
            return proxy;
        }

 

2. java.lang.reflect.Proxy 的源代码写得挺精彩。它使用到了loaderToCache的一个WeakHashMap,使程序在做缓存的同时不会泄漏内存,值得学习。

 

3. 当代理需要实现多个接口,并且多个接口中有些方法duplicate了,那顺序会起决定作用,在Class[]中最前面的那个接口会被认为是方法的提供者!