Java的動態代理(dynamic proxy)

什麼是動態代理(dynamic proxy)

動態代理(如下稱代理),利用Java的反射技術(Java Reflection),在運行時建立一個實現某些給定接口的新類(也稱「動態代理類」)及其實例(對象)html

(Using Java Reflection to create dynamic implementations of interfaces at runtime)。java

代理的是接口(Interfaces),不是類(Class),更不是抽象類。express

 

動態代理有什麼用

解決特定問題:一個接口的實如今編譯時沒法知道,須要在運行時才能實現編程

實現某些設計模式:適配器(Adapter)或修飾器(Decorator)設計模式

面向切面編程:如AOP in Springapi

 

建立動態代理

利用Java的Proxy類,調用Proxy.newProxyInstance(),建立動態對象十分簡單。數組

InvocationHandler handler = new MyInvocationHandler(...);
Class proxyClass = Proxy.getProxyClass(Foo.class.getClassLoader(), new Class[] { Foo.class });

Foo f
= (Foo) proxyClass. getConstructor(new Class[] { InvocationHandler.class }). newInstance(new Object[] { handler }); //或更簡單
Foo f = (Foo) Proxy.newProxyInstance(Foo.class.getClassLoader(), new Class[] { Foo.class }, handler);

Proxy.newProxyInstance()方法有三個參數:oracle

1. 類加載器(Class Loader)app

2. 須要實現的接口數組this

3. InvocationHandler接口。全部動態代理類的方法調用,都會交由InvocationHandler接口實現類裏的invoke()方法去處理。這是動態代理的關鍵所在。

 

InvocationHandler接口

接口裏有一個invoke()方法。基本的作法是,建立一個類,實現這個方法,利用反射在invoke()方法裏實現需求:

public class MyInvocationHandler implements InvocationHandler{

  public Object invoke(Object proxy, Method method, Object[] args)
  throws Throwable {
    //do something "dynamic"
  }
}

invoke()方法一樣有三個參數:

1. 動態代理類的引用,一般狀況下不須要它。但可使用getClass()方法,獲得proxy的Class類從而取得實例的類信息,如方法列表,annotation等。

2. 方法對象的引用,表明被動態代理類調用的方法。從中可獲得方法名,參數類型,返回類型等等

3. args對象數組,表明被調用方法的參數。注意基本類型(int,long)會被裝箱成對象類型(Interger, Long)

 

動態代理例子

1. 模擬AOP

public interface IVehical {

    void run();
    
}

//concrete implementation
public class Car implements IVehical{

    public void run() {
    System.out.println("Car is running");
    }

}

//proxy class
public class VehicalProxy {

    private IVehical vehical;

    public VehicalProxy(IVehical vehical) {
    this.vehical = vehical;
    }

    public IVehical create(){
    final Class<?>[] interfaces = new Class[]{IVehical.class};
    final VehicalInvacationHandler handler = new VehicalInvacationHandler(vehical);
    
    return (IVehical) Proxy.newProxyInstance(IVehical.class.getClassLoader(), interfaces, handler);
    }
    
    public class VehicalInvacationHandler implements InvocationHandler{

    private final IVehical vehical;
    
    public VehicalInvacationHandler(IVehical vehical) {
        this.vehical = vehical;
    }

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

        System.out.println("--before running...");
        Object ret = method.invoke(vehical, args);
        System.out.println("--after running...");
        
        return ret;
    }
    
    }
}

public class Main {
    public static void main(String[] args) {
    
    IVehical car = new Car();
    VehicalProxy proxy = new VehicalProxy(car);
    
    IVehical proxyObj = proxy.create();
    proxyObj.run();
    }
}
/*
 * output:
 * --before running...
 * Car is running
 * --after running...
 * */

能夠看出,對IVehical接口的調用,會交由Proxy的invoke方法處理,並在不改變run()的源代碼下,新增了動態的邏輯(before running/after running),這正式AOP所作的。

深刻講,Proxy.newInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h)作了如下幾件事.
(1)根據參數loader和interfaces調用方法 getProxyClass(loader, interfaces)建立代理類$Proxy.
$Proxy0類實現了interfaces的接口,並繼承了Proxy類.
(2)實例化$Proxy0並在構造方法中把BusinessHandler傳過去,接着$Proxy0調用父類Proxy的構造器,爲h賦值,以下:
class Proxy{
   InvocationHandler h=null;
   protected Proxy(InvocationHandler h) {
    this.h = h;
   }
   ...
}

 

另外,若是將invoke()方法代碼改爲以下:

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

        System.out.println("--before running...");
//        Object ret = method.invoke(vehical, args);
        ((IVehical)proxy).run();
        System.out.println("--after running...");
        
        return null;
    }

結果會是由於run()方法會引起invoke(),而invoke()又執行run(),如此下去變成死循環,最後棧溢出

因此invoke 接口中的proxy參數不能用於調用所實現接口的某些方法(見參考4)。

 

2. 利用動態代理實現設計模式,修飾器和適配器:

見參考5

 

3. 在項目中,可使用動態代理,獲取配置文件,很是方便且有優點:

@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Value {

    /**
     * The actual value expression: e.g. "#{systemProperties.myProp}".
     */
    String value();
}

/**
 * config interfaces, map the config properties file:
 * db.url = 
 * db.validation = true
 * db.pool.size = 100
 */
public interface IConfig {
    
    @Value("db.url")
    String dbUrl();
    
    @Value("db.validation")
    boolean isValidated();
    
    @Value("db.pool.size")
    int poolSize();
    
}

//proxy class
public final class ConfigFactory {

    private ConfigFactory() {}

    public static IConfig create(final InputStream is) throws IOException{
    
    final Properties properties = new Properties();
    properties.load(is);
    
    return (IConfig) Proxy.newProxyInstance(IConfig.class.getClassLoader(),
        new Class[] { IConfig.class }, new PropertyMapper(properties));
    
    }

    public static final class PropertyMapper implements InvocationHandler {

    private final Properties properties;

    public PropertyMapper(Properties properties) {
        this.properties = properties;
    }

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

        final Value value = method.getAnnotation(Value.class);

        if (value == null) return null;
          
          String property = properties.getProperty(value.value());
          if (property == null) return (null);
          
          final Class<?> returns = method.getReturnType();
          if (returns.isPrimitive())
          {
            if (returns.equals(int.class)) return (Integer.valueOf(property));
            else if (returns.equals(long.class)) return (Long.valueOf(property));
            else if (returns.equals(double.class)) return (Double.valueOf(property));
            else if (returns.equals(float.class)) return (Float.valueOf(property));
            else if (returns.equals(boolean.class)) return (Boolean.valueOf(property));
          }
        
        return property;
    }

    }
}

public static void main(String[] args) throws FileNotFoundException, IOException {
    
    IConfig config = ConfigFactory.create(new FileInputStream("config/config.properties"));
    String dbUrl = config.dbUrl();
    boolean isLoginValidated = config.isValidated();
    int dbPoolSize = config.poolSize();

    }

利用動態代理載入配置文件,並將每個配置映射成方法,方便咱們使用追蹤。

最後,有個小挑戰就是,將動態代理類$Proxy還原出來,暫時還沒作。請看參考3

 

參考:

http://docs.oracle.com/javase/7/docs/api/java/lang/reflect/Proxy.html

http://tutorials.jenkov.com/java-reflection/dynamic-proxies.html

http://hi.baidu.com/malecu/item/9e0edc115cb597a1feded5a0

http://www.cnblogs.com/duanxz/archive/2012/12/03/2799504.html

http://www.ibm.com/developerworks/cn/java/j-jtp08305.html

相關文章
相關標籤/搜索