1. 程式人生 > >【proxyConfig原始碼分析】【proxyTargetClass】【exposeProxy】

【proxyConfig原始碼分析】【proxyTargetClass】【exposeProxy】

本文轉載自shysheng:spring proxyConfig原始碼分析


我們知道,ProxyConfig是所有產生Spring AOP代理物件的基類,它是一個配置源,主要為其AOP代理物件工廠實現類提供基本的配置屬性。 它一共包含5個屬性,本文主要就是分析這5個屬性在產生代理物件過程中的具體作用。

ProxyConfig包含的5個屬性如下:

public class ProxyConfig implements Serializable {

    //標記是否直接對目標類進行代理,而不是通過介面產生代理
    //或者說,標記是否使用CGLIB動態代理,true,表示使用CGLIB的方式產生代理物件,false,表示使用JDK動態代理
    private boolean proxyTargetClass = false;

    // 標記是否進行優化。啟動優化通常意味著在代理物件被建立後,增強的修改將不會生效,因此預設值為false。
    // 如果exposeProxy設定為true,即使optimize為true也會被忽略。
    private boolean optimize = false;
    
    // 標記是否需要阻止通過該配置建立的代理物件轉換為Advised型別,預設值為false,表示代理物件可以被轉換為Advised型別
    boolean opaque = false;

    // 標記代理物件是否應該被aop框架通過AopContext以ThreadLocal的形式暴露出去。
    // 當一個代理物件需要呼叫它自己的另外一個代理方法時,這個屬性將非常有用。預設是是false,以避免不必要的攔截。
    boolean exposeProxy = false;

    // 標記該配置是否需要被凍結,如果被凍結,將不可以修改增強的配置。
    // 當我們不希望呼叫方修改轉換成Advised物件之後的代理物件時,這個配置將非常有用。
    private boolean frozen = false;
...
}

1. proxyTargetClass

這個屬性用來標誌是否直接對目標類進行代理,而不是代理特定的介面,預設值為false。
假設目標類如下,它實現了TargetInterface介面:

public class Target implements TargetInterface{
    public void exeTarget() {
        System.out.println("execute target.");
    }
}

前置增強為:

public class BeforeAdvice implements MethodBeforeAdvice {

    public void before(Method method, Object[] objects, Object o) throws Throwable {
        System.out.println("before advice.");
    }
}

配置檔案如下:

<bean id="beforeAdvice" class="com.youzan.shys.advice.seq.BeforeAdvice" />
<bean id="target" class="com.youzan.shys.advice.seq.Target" />

<bean id="proxyTargetTest" class="org.springframework.aop.framework.ProxyFactoryBean"
      p:proxyInterfaces="com.youzan.shys.advice.seq.TargetInterface"
      p:target-ref="target"
      p:interceptorNames="beforeAdvice"/>

測試類為:

public class ProxyConfigTest {
    public static void main(String[] args) {
        String configPath = "advice/proxyconfig/beans.xml";
        ApplicationContext context = new ClassPathXmlApplicationContext(configPath);

        proxyTargetTest(context);
    }

    public static void proxyTargetTest(ApplicationContext context) {
        TargetInterface target = (TargetInterface) context.getBean("proxyTargetTest");
        target.exeTarget();
        System.out.println(target.getClass().getName());
    }
}

此時輸出的代理類名稱為:

com.sun.proxy.$Proxy4

可見代理物件時通過jdk動態代理的方式產生的。如果我們在配置檔案中增加如下配置:

p:proxyTargetClass="true"

此時將通過cglib的方式產生代理物件,代理名稱也變為:

com.youzan.shys.advice.seq.Target$$EnhancerBySpringCGLIB$$d27626f8

2. optimize

optimize用來標記是否需要對代理物件採取效能優化措施,預設值為false。
如果將optimize設定為true,那麼在生成代理物件之後,如果對代理配置進行了修改,已經建立的代理物件也不會獲取修改之後的代理配置。
另外需要注意的一點是,optimize屬性與exposeProxy屬性是不能相容的,如果exposeProxy為true,那麼即使optimize被設定為true,該配置也不會生效。

3. opaque

這個屬性用來標記是否阻止將被建立的代理物件轉換為Advised型別,以便去查詢代理的狀態或是對代理物件的部分屬性進行修改,預設值為false,即允許將代理物件轉換為Advised型別。Advised介面其實就代表了被代理的物件,它持有了代理物件的一些屬性,通過它可以對生成的代理物件的一些屬性進行人為干預。
此時配置檔案和測試類分別為:

 <bean id="opaqueTest" class="org.springframework.aop.framework.ProxyFactoryBean"
          p:proxyInterfaces="com.youzan.shys.advice.seq.TargetInterface"
          p:target-ref="target"
          p:interceptorNames="beforeAdvice"/>
 public class ProxyConfigTest {
       public static void main(String[] args) {
           String configPath = "advice/proxyconfig/beans.xml";
           ApplicationContext context = new ClassPathXmlApplicationContext(configPath);
           opaqueTest(context);
       }
   
       private static void opaqueTest(ApplicationContext context) {
           Advised target = (Advised) context.getBean("opaqueTest");
           System.out.println(target.isProxyTargetClass());
       }
   }

修改opaque屬性為true

p:opaque="true"

此時再次執行測試類將丟擲如下異常:

Exception in thread "main" java.lang.ClassCastException: com.sun.proxy.$Proxy4 cannot be cast to org.springframework.aop.framework.Advised
    at com.youzan.shys.advice.proxyconfig.ProxyConfigTest.opaqueTest(ProxyConfigTest.java:39)
    at com.youzan.shys.advice.proxyconfig.ProxyConfigTest.main(ProxyConfigTest.java:23)

顯然,由於opaque屬性被設定為true,轉換為Advised型別將會丟擲cast異常。

4. exposeProxy

exposeProxy用來標記是否允許將代理物件以ThreadLocal的形式暴露給aop框架,以便通過AopContext對代理物件進行重複利用。這麼講起來似乎還是很虛,我們同樣通過一個簡單例子一看便知。
為了更好的說明問題,我們在目標類中新增一個方法innelCall(),即此時目標類變為:

public class Target implements TargetInterface{
    public void exeTarget() {
        System.out.println("execute target.");
    }

    public void innerCall() {
        System.out.println("innel call.");
    }
}

同時配置檔案更新為:

<bean id="exposeTest" class="org.springframework.aop.framework.ProxyFactoryBean"
      p:proxyInterfaces="com.youzan.shys.advice.seq.TargetInterface"
      p:target-ref="target"
      p:interceptorNames="beforeAdvice"/>

此時如果我們在測試類中通過代理物件分別直接呼叫目標類的兩個方法,顯然兩者都可以被增強。那麼如果我們將目標類修改為在exeTarget()內部呼叫innerCall()方法,此時目標類的兩個方法都能被增強嗎?

public class Target implements TargetInterface{
    public void exeTarget() {
        System.out.println("execute target.");
        innerCall();
    }

    public void innerCall() {
        System.out.println("innel call.");
    }
}

通過執行測試類執行exeTarget()方法我們發現,此時只有exeTarget()被增強了,而innerCall()並沒有被施加增強邏輯。在這種場景下,如果目標類的方法之間存在內部呼叫,而我們又希望兩個方法都可以被增強的時候,exposeProxy屬性就派上用場了,我們只需要在配置檔案中增加如下配置項

p:exposeProxy="true"

同時將目標類的內部呼叫方式修改為:

public void exeTarget() {
    System.out.println("execute target.");
    ((Target)AopContext.currentProxy()).innerCall();
}

此時再次執行測試類將會發現,目標類的兩個方法都被成功實施增強了。
原因其實很簡單,在執行代理物件回撥方法的過程中,有這樣一段邏輯:

if (this.advised.exposeProxy) {
    // Make invocation available if necessary.
    oldProxy = AopContext.setCurrentProxy(proxy);
    setProxyContext = true;
}

public abstract class AopContext {

    private static final ThreadLocal<Object> currentProxy = new NamedThreadLocal<>("Current AOP proxy");

    public static Object currentProxy() throws IllegalStateException {
        Object proxy = currentProxy.get();
        if (proxy == null) {
            throw new IllegalStateException(
                    "Cannot find current proxy: Set 'exposeProxy' property on Advised to 'true' to make it available.");
        }
        return proxy;
    }

    @Nullable
    static Object setCurrentProxy(@Nullable Object proxy) {
        Object oldProxy = currentProxy.get();
        if (proxy != null) {
            currentProxy.set(proxy);
        }
        else {
            currentProxy.remove();
        }
        return oldProxy;
    }
}

即如果發現exposeProxy屬性為true,將會把代理物件放入ThreadLocal中,然後在目標類內部呼叫時通過currentProxy()方法取出代理物件,相當於此時代理物件得到了複用。

5. frozen

前面說到,當opaque屬性設定為false時,我們可以將代理物件轉換為Advised型別,進而可以對代理物件的一些屬性進行查詢和修改。而frozen則用來標記是否需要凍結代理物件,即在代理物件生成之後,是否允許對其進行修改,預設為false.
此時配置檔案更新為:

<bean id="frozenTest" class="org.springframework.aop.framework.ProxyFactoryBean"
          p:proxyInterfaces="com.youzan.shys.advice.seq.TargetInterface"
          p:target-ref="target"
          p:interceptorNames="beforeAdvice"/>

測試類為:

public class ProxyConfigTest {
    public static void main(String[] args) {
        String configPath = "advice/proxyconfig/beans.xml";
        ApplicationContext context = new ClassPathXmlApplicationContext(configPath);
        forzenTest(context);
    }


    private static void forzenTest(ApplicationContext context) {
        Advised target = (Advised) context.getBean("frozenTest");
        target.removeAdvisor(0);
        Target t = (Target) target;
        t.exeTarget();
    }
}

在這裡我們通過將代理物件轉換為Advised型別統一移除切面,也就是說在生成代理物件之後對其進行了修改,以達到不實施增強的效果。如果不希望代理物件在生成之後被修改,可以在配置檔案中新增配置項:

p:frozen="true"

此時再次執行測試類將丟擲如下異常:

Exception in thread "main" org.springframework.aop.framework.AopConfigException: Cannot remove Advisor: Configuration is frozen.
    at org.springframework.aop.framework.AdvisedSupport.removeAdvisor(AdvisedSupport.java:279)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:498)
    at org.springframework.aop.support.AopUtils.invokeJoinpointUsingReflection(AopUtils.java:338)
    at org.springframework.aop.framework.JdkDynamicAopProxy.invoke(JdkDynamicAopProxy.java:180)
    at com.sun.proxy.$Proxy4.removeAdvisor(Unknown Source)
    at com.youzan.shys.advice.proxyconfig.ProxyConfigTest.forzenTest(ProxyConfigTest.java:45)
    at com.youzan.shys.advice.proxyconfig.ProxyConfigTest.main(ProxyConfigTest.java:23)

即由於代理物件被凍結,移除切面的操作將被禁止。