【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)

即由于代理对象被冻结,移除切面的操作将被禁止。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值