代理一詞含義十分寬泛,例如金融領域的股票發行代理、營銷領域的銷售代理、以及計算機領域中的代理設計模式等。儘管代理一詞被使用的領域如此普遍,可是代理一詞的大體的抽象含義是類似的或者說是相同的。代理是一個被委託人委託其執行以下活動:參加活動、行駛權力、執行任務等。這樣理解的話,計算機中某個對象或組件的代理就很是好理解了。html
計算機領域中代理的概念是一個十分重要的概念,常見的有代理服務器、代理設計模式等。在軟件開發發展成熟的今天,每一個工程的代碼量也愈來愈龐大,帶來的一個問題就是一次小小的需求修改就會引發很大的變化,從而可能引進新的BUG.
所以程序員對需求修改都深惡痛絕,而代理設計模式在某種程度上能夠緩解這種問題。代理設計模式能夠實如今不破壞原有代碼的狀況下,對原有代碼添加額外的功能,從而實現以低的侵入完成原有系統功能的擴展,這種設計也符合里氏替換原則(對修改關閉,對擴展開放)。java
編程語言中的代理分爲靜態代理和動態代理程序員
像大多數其餘語言同樣,Java能夠輕鬆的實現靜態代理。具體來說有兩種形式:express
abstract class AbstractProxy {
AbstractProxy real;
public AbstractProxy() {
// TODO Auto-generated constructor stub
}
public AbstractProxy(AbstractProxy real) {
this.real = real;
};
//被代理的方法
public abstract void foolbar(String str);
}
//某個被代理類
class RealClass extends AbstractProxy {
public RealClass() {
super();
}
public RealClass(AbstractProxy real) {
super(real);
// TODO Auto-generated constructor stub
}
@Override
public void foolbar(String str) {
// TODO Auto-generated method stub
System.out.println("out>>" + str);
}
}
final AbstractProxy realObj = new RealClass();
AbstractProxy proxy = new AbstractProxy() {
@Override
public void foolbar(String str) {
// TODO Auto-generated method stub
System.out.println("you are proxied by me!");
realObj.foolbar(str);
}
};
該形式能夠實現一個代理,看似是在運行時生成的一個匿名內部類,可是經過測試發現匿名內部類是在編譯時期生成的類,這個能夠經過匿名內部類類文件來觀察,所以其屬於靜態代理。這種形式的代理看起來不太正常,並且一個代理類只能代理一個接口或者一個抽象類,若是代理多個就必須新增長多個匿名內部類。編程
//被代理的接口
interface IReal {
public void doSomeThing(String str);
}
//某個被代理的類
class RealClass implements IReal {
@Override
public void doSomeThing(String str) {
// TODO Auto-generated method stub
System.out.println("doSomeThing " + str);
}
}
class ProxyClass implements IReal {
IReal realObj;
public ProxyClass(IReal realObj) {
this.realObj = realObj;
}
@Override
public void doSomeThing(String str) {
// TODO Auto-generated method stub
System.out.println("you are proxied by me!");
realObj.doSomething();
}
}
RealClass realObj = new RealClass();
RealClass proxy = new ProxyClass(realObj);
這種形式的代理類型須要在編譯時期肯定,所以屬於靜態類型。從某種程度上來看,這種形式和裝飾器模式、適配器模式的設計思路類似。缺點同第一種形式同樣,若是多個須要代理多個接口就須要重寫代理類,讓其實現多個被代理的接口;同時在類型轉換的時候也會很麻煩。設計模式
java
的動態代理是運行時動態的根據須要被代理的接口列表interfaces生成一個代理類,該代理類實現了接口列表interfaces
中的全部方法,而後在方法的內部實際是講該方法的調用轉發給了實現了InvocationHandler
接口的對象,顧名思義,該對象讓包含代理時被代理方法的代理邏輯。api
用法舉例:編寫一個代理類實現攔截某個被代理方法
具體的使用代碼:數組
interface IProxied1 {
public void proxiedMethod1(String str);
}
interface IProxied2 {
public void proxiedMethod2(String str);
}
class Proxied implements IProxied1, IProxied2 {
@Override
public void proxiedMethod2(String str) {
// TODO Auto-generated method stub
System.out.println("proxiedMethod2 " + str);
}
@Override
public void proxiedMethod1(String str) {
// TODO Auto-generated method stub
System.out.println("proxiedMethod1 " + str);
}
}
class Interceptor implements InvocationHandler {
Object proxied;
public Interceptor() {
// TODO Auto-generated constructor stub
}
public Interceptor(Object proxied) {
// TODO Auto-generated constructor stub
this.proxied = proxied;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// TODO Auto-generated method stub
System.out.println("I am watching...");
//判斷是否攔截
if(method.getName().equals("proxiedMethod1")) {
System.out.println("you are intercepted!!!!!");
return null;
}
return method.invoke(proxied, args);
}
}
執行代碼緩存
public class DynamicProxyDemo {
public static void main(String[] str) {
IProxied1 proxiedObj = new Proxied();
Object proxyObj = Proxy.newProxyInstance(IProxied1.class.getClassLoader(), new Class<?>[]{IProxied1.class, IProxied2.class}, new Interceptor(proxiedObj));
((IProxied1)proxyObj).proxiedMethod1("Hello, World!");
System.out.println("-------");
((IProxied2)proxyObj).proxiedMethod2("Hello, World!");
}
}
輸出以下:bash
I am watching... you are intercepted!!!!! ------- I am watching... proxiedMethod2 Hello, World!
通常來說,靜態代理是硬編碼去實現一個代理類,若是須要被代理的接口有變更,則須要從新編碼代理類;靜態綁定的過程將代理類的代理邏輯和代理類的生成綁定到一塊兒了,
因此修改起來不是很方便(解耦不完全)。其實咱們最關注的是代理類的代理邏輯,所以若是將代理的生成自動化(由於代理類的生成的規則是general
的,能夠泛化。先實現被代理的接口、而後方法轉發,就是這麼簡單。),
而將代理邏輯分離出來,全部的代理邏輯所有發生在這裏;經過這樣的解耦,代碼可維護性會加強、侵入性會減少,這就是動態代理的思想。
具體來說區別以下圖:
靜態代理的代理類多處出現代理邏輯的代碼,而且同時靜態代理的代理類須要本身硬編碼。
動態代理的代理類相似於一個方法路由,對被代理對象的任何被代理方法的調用,都會被該路由轉發到InvocationHandler代理邏輯處理類中,從而將代理類的生成和代理類的代理邏輯分開。
Java
動態代理生成的代理類是直接在內存中按照class
文件格式生成了一個二進制文件,而後類加載器加載該二進制類文件,最後實例化一個代理類的實例。
前面已經介紹了Java
動態代理的基本用法,主要涉及到以下幾個類和方法以下:(JDK7
)
java.lang.reflect.Proxy
public static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h) throws IllegalArgumentException
public static Class<?> getProxyClass(ClassLoader loader, Class<?>... interfaces) throws IllegalArgumentException
sun.misc.ProxyGenerator
public static byte[] generateProxyClass(final String name, Class[] interfaces)
java.lang.reflect.InvocationHandler
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable;
具體源代碼以下:
/** * Returns an instance of a proxy class for the specified interfaces * that dispatches method invocations to the specified invocation * handler. This method is equivalent to: * <pre> * Proxy.getProxyClass(loader, interfaces). * getConstructor(new Class[] { InvocationHandler.class }). * newInstance(new Object[] { handler }); * </pre> * * <p>{@code Proxy.newProxyInstance} throws * {@code IllegalArgumentException} for the same reasons that * {@code Proxy.getProxyClass} does. * * @param loader the class loader to define the proxy class * @param interfaces the list of interfaces for the proxy class * to implement * @param h the invocation handler to dispatch method invocations to * @return a proxy instance with the specified invocation handler of a * proxy class that is defined by the specified class loader * and that implements the specified interfaces * @throws IllegalArgumentException if any of the restrictions on the * parameters that may be passed to {@code getProxyClass} * are violated * @throws NullPointerException if the {@code interfaces} array * argument or any of its elements are {@code null}, or * if the invocation handler, {@code h}, is * {@code null} */
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 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());
}
}
/** * Returns the {@code java.lang.Class} object for a proxy class * given a class loader and an array of interfaces. The proxy class * will be defined by the specified class loader and will implement * all of the supplied interfaces. If a proxy class for the same * permutation of interfaces has already been defined by the class * loader, then the existing proxy class will be returned; otherwise, * a proxy class for those interfaces will be generated dynamically * and defined by the class loader. * * <p>There are several restrictions on the parameters that may be * passed to {@code Proxy.getProxyClass}: * * <ul> * <li>All of the {@code Class} objects in the * {@code interfaces} array must represent interfaces, not * classes or primitive types. * * <li>No two elements in the {@code interfaces} array may * refer to identical {@code Class} objects. * * <li>All of the interface types must be visible by name through the * specified class loader. In other words, for class loader * {@code cl} and every interface {@code i}, the following * expression must be true: * <pre> * Class.forName(i.getName(), false, cl) == i * </pre> * * <li>All non-public interfaces must be in the same package; * otherwise, it would not be possible for the proxy class to * implement all of the interfaces, regardless of what package it is * defined in. * * <li>For any set of member methods of the specified interfaces * that have the same signature: * <ul> * <li>If the return type of any of the methods is a primitive * type or void, then all of the methods must have that same * return type. * <li>Otherwise, one of the methods must have a return type that * is assignable to all of the return types of the rest of the * methods. * </ul> * * <li>The resulting proxy class must not exceed any limits imposed * on classes by the virtual machine. For example, the VM may limit * the number of interfaces that a class may implement to 65535; in * that case, the size of the {@code interfaces} array must not * exceed 65535. * </ul> * * <p>If any of these restrictions are violated, * {@code Proxy.getProxyClass} will throw an * {@code IllegalArgumentException}. If the {@code interfaces} * array argument or any of its elements are {@code null}, a * {@code NullPointerException} will be thrown. * * <p>Note that the order of the specified proxy interfaces is * significant: two requests for a proxy class with the same combination * of interfaces but in a different order will result in two distinct * proxy classes. * * @param loader the class loader to define the proxy class * @param interfaces the list of interfaces for the proxy class * to implement * @return a proxy class that is defined in the specified class loader * and that implements the specified interfaces * @throws IllegalArgumentException if any of the restrictions on the * parameters that may be passed to {@code getProxyClass} * are violated * @throws NullPointerException if the {@code interfaces} array * argument or any of its elements are {@code null} */
//根據一組接口列表interfaces,返回一個由類加載器loader加載的實現了全部interfaces的類對象
//若是實現接口列表interfaces的類已經被加載過了;則直接返回緩存的類對象
public static Class<?> getProxyClass(ClassLoader loader,
Class<?>... interfaces)
throws IllegalArgumentException
{
if (interfaces.length > 65535) {
throw new IllegalArgumentException("interface limit exceeded");
}
Class<?> proxyClass = null;
/* collect interface names to use as key for proxy class cache */
String[] interfaceNames = new String[interfaces.length];
// for detecting duplicates
Set<Class<?>> interfaceSet = new HashSet<>();
//驗證接口列表的接口對於參數給定的類加載器loader是不是可見的;
//同時檢查接口列表interfaces的合法性(無重複的接口、必須是接口類型)
for (int i = 0; i < interfaces.length; i++) {
/* * Verify that the class loader resolves the name of this * interface to the same Class object. */
String interfaceName = interfaces[i].getName();
Class<?> interfaceClass = null;
try {
interfaceClass = Class.forName(interfaceName, false, loader);
} catch (ClassNotFoundException e) {
}
if (interfaceClass != interfaces[i]) {
throw new IllegalArgumentException(
interfaces[i] + " is not visible from class loader");
}
/* * Verify that the Class object actually represents an * interface. */
if (!interfaceClass.isInterface()) {
throw new IllegalArgumentException(
interfaceClass.getName() + " is not an interface");
}
/* * Verify that this interface is not a duplicate. */
if (interfaceSet.contains(interfaceClass)) {
throw new IllegalArgumentException(
"repeated interface: " + interfaceClass.getName());
}
interfaceSet.add(interfaceClass);
interfaceNames[i] = interfaceName;
}
/* * Using string representations of the proxy interfaces as * keys in the proxy class cache (instead of their Class * objects) is sufficient because we require the proxy * interfaces to be resolvable by name through the supplied * class loader, and it has the advantage that using a string * representation of a class makes for an implicit weak * reference to the class. */
List<String> key = Arrays.asList(interfaceNames);
/* * Find or create the proxy class cache for the class loader. */
Map<List<String>, Object> cache;
synchronized (loaderToCache) {
cache = loaderToCache.get(loader);
if (cache == null) {
cache = new HashMap<>();
loaderToCache.put(loader, cache);
}
/* * This mapping will remain valid for the duration of this * method, without further synchronization, because the mapping * will only be removed if the class loader becomes unreachable. */
}
/* * Look up the list of interfaces in the proxy class cache using * the key. This lookup will result in one of three possible * kinds of values: * null, if there is currently no proxy class for the list of * interfaces in the class loader, * the pendingGenerationMarker object, if a proxy class for the * list of interfaces is currently being generated, * or a weak reference to a Class object, if a proxy class for * the list of interfaces has already been generated. */
synchronized (cache) {
/* * Note that we need not worry about reaping the cache for * entries with cleared weak references because if a proxy class * has been garbage collected, its class loader will have been * garbage collected as well, so the entire cache will be reaped * from the loaderToCache map. */
do {
//cache 的key是接口名數組生成的list;value是一個代理類對象的弱引用
Object value = cache.get(key);
if (value instanceof Reference) {
proxyClass = (Class<?>) ((Reference) value).get();
}
if (proxyClass != null) {
// proxy class already generated: return it
return proxyClass;
//若是是一個標誌符號,則說明有另外的線程正在建立代理類;則該線程掛起等待
} else if (value == pendingGenerationMarker) {
// proxy class being generated: wait for it
try {
cache.wait();
} catch (InterruptedException e) {
/* * The class generation that we are waiting for should * take a small, bounded time, so we can safely ignore * thread interrupts here. */
}
continue;//被喚醒後;繼續check是否存在類對象
} else {
/* * No proxy class for this list of interfaces has been * generated or is being generated, so we will go and * generate it now. Mark it as pending generation. */
//不存在代理類的時候,須要本身去生成代理類;生成代理類以前先置一個狀態標誌對象pendingGenerationMarker
cache.put(key, pendingGenerationMarker);
break;
}
} while (true);
}
try {
String proxyPkg = null; // package to define proxy class in
/* * Record the package of a non-public proxy interface so that the * proxy class will be defined in the same package. Verify that * all non-public proxy interfaces are in the same package. */
for (int i = 0; i < interfaces.length; i++) {
int flags = interfaces[i].getModifiers();
if (!Modifier.isPublic(flags)) {
String name = interfaces[i].getName();
int n = name.lastIndexOf('.');
String pkg = ((n == -1) ? "" : name.substring(0, n + 1));
if (proxyPkg == null) {
proxyPkg = pkg;
} else if (!pkg.equals(proxyPkg)) {
throw new IllegalArgumentException(
"non-public interfaces from different packages");
}
}
}
if (proxyPkg == null) { // if no non-public proxy interfaces,
proxyPkg = ""; // use the unnamed package
}
{
/* * Choose a name for the proxy class to generate. */
long num;
synchronized (nextUniqueNumberLock) {
num = nextUniqueNumber++;
}
String proxyName = proxyPkg + proxyClassNamePrefix + num;
/* * Verify that the class loader hasn't already * defined a class with the chosen name. */
/* * Generate the specified proxy class. */
//重點在這裏;調用ProxyGenerator的靜態方法去生成一個代理類,獲得類文件的字節數組
byte[] proxyClassFile = ProxyGenerator.generateProxyClass(
proxyName, interfaces);
try {
//用類加載器加載二進制類文件,獲得代理類對象
proxyClass = defineClass0(loader, proxyName,
proxyClassFile, 0, proxyClassFile.length);
} catch (ClassFormatError e) {
/* * A ClassFormatError here means that (barring bugs in the * proxy class generation code) there was some other * invalid aspect of the arguments supplied to the proxy * class creation (such as virtual machine limitations * exceeded). */
throw new IllegalArgumentException(e.toString());
}
}
// add to set of all generated proxy classes, for isProxyClass
proxyClasses.put(proxyClass, null);
} finally {
/* * We must clean up the "pending generation" state of the proxy * class cache entry somehow. If a proxy class was successfully * generated, store it in the cache (with a weak reference); * otherwise, remove the reserved entry. In all cases, notify * all waiters on reserved entries in this cache. */
synchronized (cache) {
if (proxyClass != null) {
//加入緩存
cache.put(key, new WeakReference<Class<?>>(proxyClass));
} else {
cache.remove(key);
}
cache.notifyAll();//無論建立代理類是否成功,喚醒在cache上面等待的線程
}
}
return proxyClass;
}
/** * Generate a class file for the proxy class. This method drives the * class file generation process. */
//根據獲得的方法信息、類信息;按照JVM的規範動態的生成一個代理類的二進制文件
//該過程比較複雜涉及到許多JVM的指令
private byte[] generateClassFile() {
/* ============================================================ * Step 1: Assemble ProxyMethod objects for all methods to * generate proxy dispatching code for. */
/* * Record that proxy methods are needed for the hashCode, equals, * and toString methods of java.lang.Object. This is done before * the methods from the proxy interfaces so that the methods from * java.lang.Object take precedence over duplicate methods in the * proxy interfaces. */
addProxyMethod(hashCodeMethod, Object.class);
addProxyMethod(equalsMethod, Object.class);
addProxyMethod(toStringMethod, Object.class);
/* * Now record all of the methods from the proxy interfaces, giving * earlier interfaces precedence over later ones with duplicate * methods. */
for (int i = 0; i < interfaces.length; i++) {
Method[] methods = interfaces[i].getMethods();
for (int j = 0; j < methods.length; j++) {
addProxyMethod(methods[j], interfaces[i]);
}
}
/* * For each set of proxy methods with the same signature, * verify that the methods' return types are compatible. */
for (List<ProxyMethod> sigmethods : proxyMethods.values()) {
checkReturnTypes(sigmethods);
}
/* ============================================================ * Step 2: Assemble FieldInfo and MethodInfo structs for all of * fields and methods in the class we are generating. */
try {
methods.add(generateConstructor());
for (List<ProxyMethod> sigmethods : proxyMethods.values()) {
for (ProxyMethod pm : sigmethods) {
// add static field for method's Method object
fields.add(new FieldInfo(pm.methodFieldName,
"Ljava/lang/reflect/Method;",
ACC_PRIVATE | ACC_STATIC));
// generate code for proxy method and add it
methods.add(pm.generateMethod());
}
}
methods.add(generateStaticInitializer());
} catch (IOException e) {
throw new InternalError("unexpected I/O Exception");
}
if (methods.size() > 65535) {
throw new IllegalArgumentException("method limit exceeded");
}
if (fields.size() > 65535) {
throw new IllegalArgumentException("field limit exceeded");
}
/* ============================================================ * Step 3: Write the final class file. */
/* * Make sure that constant pool indexes are reserved for the * following items before starting to write the final class file. */
cp.getClass(dotToSlash(className));
cp.getClass(superclassName);
for (int i = 0; i < interfaces.length; i++) {
cp.getClass(dotToSlash(interfaces[i].getName()));
}
/* * Disallow new constant pool additions beyond this point, since * we are about to write the final constant pool table. */
cp.setReadOnly();
ByteArrayOutputStream bout = new ByteArrayOutputStream();
DataOutputStream dout = new DataOutputStream(bout);
try {
/* * Write all the items of the "ClassFile" structure. * See JVMS section 4.1. */
// u4 magic;
dout.writeInt(0xCAFEBABE);
// u2 minor_version;
dout.writeShort(CLASSFILE_MINOR_VERSION);
// u2 major_version;
dout.writeShort(CLASSFILE_MAJOR_VERSION);
cp.write(dout); // (write constant pool)
// u2 access_flags;
dout.writeShort(ACC_PUBLIC | ACC_FINAL | ACC_SUPER);
// u2 this_class;
dout.writeShort(cp.getClass(dotToSlash(className)));
// u2 super_class;
dout.writeShort(cp.getClass(superclassName));
// u2 interfaces_count;
dout.writeShort(interfaces.length);
// u2 interfaces[interfaces_count];
for (int i = 0; i < interfaces.length; i++) {
dout.writeShort(cp.getClass(
dotToSlash(interfaces[i].getName())));
}
// u2 fields_count;
dout.writeShort(fields.size());
// field_info fields[fields_count];
for (FieldInfo f : fields) {
f.write(dout);
}
// u2 methods_count;
dout.writeShort(methods.size());
// method_info methods[methods_count];
for (MethodInfo m : methods) {
m.write(dout);
}
// u2 attributes_count;
dout.writeShort(0); // (no ClassFile attributes for proxy classes)
} catch (IOException e) {
throw new InternalError("unexpected I/O Exception");
}
return bout.toByteArray();
}
經過反編譯生成的動態代理類的文件,能夠獲得
final class $Proxy0
extends Proxy
implements IReal
{
private static Method m1;
private static Method m3;
private static Method m2;
private static Method m0;
public $Proxy0(InvocationHandler paramInvocationHandler)
{
super(paramInvocationHandler);
}
public final boolean equals(Object paramObject)
{
try
{
return ((Boolean)this.h.invoke(this, m1, new Object[] { paramObject })).booleanValue();
}
catch (Error|RuntimeException localError)
{
throw localError;
}
catch (Throwable localThrowable)
{
throw new UndeclaredThrowableException(localThrowable);
}
}
public final void doSomeThing(String paramString)
{
try
{
this.h.invoke(this, m3, new Object[] { paramString });//方法的轉發;反射調用InvocationHandler的invoke方法
return;
}
catch (Error|RuntimeException localError)
{
throw localError;
}
catch (Throwable localThrowable)
{
throw new UndeclaredThrowableException(localThrowable);
}
}
public final String toString()
{
try
{
return (String)this.h.invoke(this, m2, null);
}
catch (Error|RuntimeException localError)
{
throw localError;
}
catch (Throwable localThrowable)
{
throw new UndeclaredThrowableException(localThrowable);
}
}
public final int hashCode()
{
try
{
return ((Integer)this.h.invoke(this, m0, null)).intValue();
}
catch (Error|RuntimeException localError)
{
throw localError;
}
catch (Throwable localThrowable)
{
throw new UndeclaredThrowableException(localThrowable);
}
}
static
{
try
{
m1 = Class.forName("java.lang.Object").getMethod("equals", new Class[] { Class.forName("java.lang.Object") });
m3 = Class.forName("trick.IReal").getMethod("doSomeThing", new Class[] { Class.forName("java.lang.String") });
m2 = Class.forName("java.lang.Object").getMethod("toString", new Class[0]);
m0 = Class.forName("java.lang.Object").getMethod("hashCode", new Class[0]);
return;
}
catch (NoSuchMethodException localNoSuchMethodException)
{
throw new NoSuchMethodError(localNoSuchMethodException.getMessage());
}
catch (ClassNotFoundException localClassNotFoundException)
{
throw new NoClassDefFoundError(localClassNotFoundException.getMessage());
}
}
}
java
動態代理的應用在框架中十分普遍,例如Spring
框架用動態代理來實現AOP
、Struts2
框架利用動態代理實現攔截器。
AOP
中的代理邏輯點
又稱爲切面的切入點(cut point)
。另外,實現AOP
概念的方式是動態代理,但動態代理的形式有不少種,JDK
提供的這種只是其中一種,還有涉及到類加載器加載類前、加載類後植入字節碼等形式。
[1] 完全理解JAVA動態代理
[2] JDK動態代理實現原理
[3]AOP動態代理的實現機制