当前位置: 首页 > 工具软件 > Object-proxy > 使用案例 >

Spring学习笔记 —— AOP标签详解(<scoped-proxy>)

朱风史
2023-12-01

引言

在前两篇文章,Spring学习笔记 —— AOP(面向切面编程) 之AspectJSpring学习笔记 —— AOP(面向切面编程) 之使用ProxyFactoryBean实现AOP,我们介绍了AOP的概念,在Spring下两种实现AOP的方式。

但是,除了面向切面编程之外,AOP的名字空间中还包含了一些重要的标签,比如”scoped-proxy”。这篇文章就会详细介绍这个标签的作用,以及它的实现方式分析。

scoped-proxy 标签介绍

Spring学习笔记 —— 从IOC说起,我们介绍过,Spring中的Bean是有Scope属性的,代表着bean的生存周期。而Spring中默认的Scope分为”singleton”和”prototype”两种。

那么,问题就来了,如果在一个singleton的Bean中引用了一个prototype的Bean,结果会怎样呢?——在默认情况下,单例会永远持有一开始构造所赋给它的值。

所以,为了让我们在每次调用这个Bean的时候都能够得到具体scope中的值,比如prototype,那么我们希望每次在单例中调用这个Bean的时候,得到的都是一个新的prototype,Spring中AOP名字空间中引入了这个标签。
xml
<aop:scoped-proxy/>

示例

举个例子。
PrototypeBean.java这个类在初始化的时候会得到当前时间的时间戳,它的scope为prototype(每次获取都会重新生成一个)。

public class PrototypeBean {
    private Long timeMilis;

    public PrototypeBean(){
        timeMilis = (new Date()).getTime();
    }

    public void printTime() {
        System.out.println(timeMilis+"");
    }
}

SingletonBean.java这个单例的Bean持有一个PrototypeBean,同时它的printTime()方法输出PrototypeBean的时间戳。

public class SingletonBean {
    private PrototypeBean prototype;

    public void printTime() {
        prototype.printTime();
    }

    public void setPrototype(PrototypeBean prototype) {
        this.prototype = prototype;
    }
}

scopedProxyBean.xml

<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:context="http://www.springframework.org/schema/context"
    xmlns:aop="http://www.springframework.org/schema/aop"
    xsi:schemaLocation="http://www.springframework.org/schema/beans
    http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
    http://www.springframework.org/schema/context
    http://www.springframework.org/schema/context/spring-context-3.0.xsd
    http://www.springframework.org/schema/aop 
    http://www.springframework.org/schema/aop/spring-aop-3.0.xsd">

    <bean id="protyotypBean" class="com.study.scopedProxy.PrototypeBean" scope="prototype">
    <aop:scoped-proxy/>
    </bean>

    <bean id="singletonBean" class="com.study.scopedProxy.SingletonBean">
        <property name="prototype">
            <ref bean="protyotypBean" />  
        </property>
    </bean>

</beans>

ScopedProxyMain.java

public class ScopedProxyMain {
    public static void main(String args[]) {
        ApplicationContext app = new ClassPathXmlApplicationContext("scopedProxyBean.xml");

        SingletonBean singleton = app.getBean(SingletonBean.class);

        singleton.printTime();
        //1477958215532
        singleton.printTime();
        //1477958215571
    }
}

如果不加上<aop:scoped-proxy/>这个标签,那么两次将会输出同样的时间戳。而我们加上标签之后,每次调用这个Bean的时候,系统就会先取一遍这个Bean,确保我们得到的Bean是在当前的scope当中的。

源码解析

那么具体在代码层面,是如何完成这个实现的呢?

我们还是从AopNamespaceHandler先着手。

AopNamespaceHandler.init里面是对各个标签进行解析类注册,我们能看到,scoped-proxy对应的注册类就是ScopedProxyBeanDefinitionDecorator

registerBeanDefinitionDecorator("scoped-proxy", new ScopedProxyBeanDefinitionDecorator());

而再到这个类里面,发现它实现了BeanDefinitionDecorator接口,于是查看其decorate方法。

BeanDefinitionHolder holder =
                ScopedProxyUtils.createScopedProxy(definition, parserContext.getRegistry(), proxyTargetClass);

createScopedProxy

String originalBeanName = definition.getBeanName();
        BeanDefinition targetDefinition = definition.getBeanDefinition();
        String targetBeanName = getTargetBeanName(originalBeanName);

        // 创建一个ScopedProxyFactoryBean,这个Bean中保存了目标Bean的名称,
        // 同时在内部保存了目标Bean定义的引用。注意并没有对BeanDefinition设置scope,
        //因此这个代理bean的scope就默认是singleton了。
        RootBeanDefinition proxyDefinition = new RootBeanDefinition(ScopedProxyFactoryBean.class);
        proxyDefinition.setDecoratedDefinition(new BeanDefinitionHolder(targetDefinition, targetBeanName));
        proxyDefinition.setOriginatingBeanDefinition(targetDefinition);
        proxyDefinition.setSource(definition.getSource());
        proxyDefinition.setRole(targetDefinition.getRole());

        proxyDefinition.getPropertyValues().add("targetBeanName", targetBeanName);
        // 默认情况下proxyTargetClass都是True
        if (proxyTargetClass) {
            targetDefinition.setAttribute(AutoProxyUtils.PRESERVE_TARGET_CLASS_ATTRIBUTE, Boolean.TRUE);

        }
        else {
            proxyDefinition.getPropertyValues().add("proxyTargetClass", Boolean.FALSE);
        }

        proxyDefinition.setAutowireCandidate(targetDefinition.isAutowireCandidate());
        proxyDefinition.setPrimary(targetDefinition.isPrimary());
        if (targetDefinition instanceof AbstractBeanDefinition) {
            proxyDefinition.copyQualifiersFrom((AbstractBeanDefinition) targetDefinition);
        }


        targetDefinition.setAutowireCandidate(false);
        targetDefinition.setPrimary(false);

        // 因为在后面还是会用到目标Bean,因此也需要将它的定义注册到Registry中
        registry.registerBeanDefinition(targetBeanName, targetDefinition);

        //将新的Bean返回
        return new BeanDefinitionHolder(proxyDefinition, originalBeanName, definition.getAliases());

对于BeanDefintion的解析,到这里就可以看作结束了。那么,这个ScopedProxyFactoryBean,又会在什么时候创建,怎么创建呢?

关键就是要看ScopedProxyFactoryBean.setBeanFactory

@Override
    public void setBeanFactory(BeanFactory beanFactory) {
        if (!(beanFactory instanceof ConfigurableBeanFactory)) {
            throw new IllegalStateException("Not running in a ConfigurableBeanFactory: " + beanFactory);
        }
        ConfigurableBeanFactory cbf = (ConfigurableBeanFactory) beanFactory;

        this.scopedTargetSource.setBeanFactory(beanFactory);

        ProxyFactory pf = new ProxyFactory();
        pf.copyFrom(this);
        //这个这个代理Bean设置Bean的来源,很重要。这个方法决定了,
        //每次取target的时候,都会调用beanFactory.getBean。
        pf.setTargetSource(this.scopedTargetSource);

        Class<?> beanType = beanFactory.getType(this.targetBeanName);
        if (beanType == null) {
            throw new IllegalStateException("Cannot create scoped proxy for bean '" + this.targetBeanName +
                    "': Target type could not be determined at the time of proxy creation.");
        }
        if (!isProxyTargetClass() || beanType.isInterface() || Modifier.isPrivate(beanType.getModifiers())) {
            pf.setInterfaces(ClassUtils.getAllInterfacesForClass(beanType, cbf.getBeanClassLoader()));
        }

        // 构建一个introduction,这个introduction只实现了ScopedObject的所有接口
        ScopedObject scopedObject = new DefaultScopedObject(cbf, this.scopedTargetSource.getTargetBeanName());
        //将scopedObject作为通知加入到proxy中,DelegatingIntroductionInterceptor作为通知的拦截器,
        //实际上是使得所有在proxyBean上调用的'getTargetObject`方法都被代理到了`DefaultScopedObject`中。
        //在增加通知的同时会生成DefaultIntroductionAdvisor 作为advisor,
        pf.addAdvice(new DelegatingIntroductionInterceptor(scopedObject));

        pf.addInterface(AopInfrastructureBean.class);

        this.proxy = pf.getProxy(cbf.getBeanClassLoader());
    }

而在后面,根据advisor生成相应的代理类,在之前已经解释过了,就不再赘述了。

而关于Introduction,这里有一个很好的例子。你也可以将cglib生成的class文件保存,就能够发现最后的Bean中的确存在着getTargetObject这个方法。

个人认为注册Advice的作用并不是很大,因为我们已经为这个ProxyBean设置好它的target source了,对于scoped-proxy的目的也就达到了。

最后来看一下真正被使用的地方把。

DynamicAdvisedInterceptor.intercept,前文也说过了,这个是CglibAOP的拦截类。所有被拦截的方法都会首先调用这里的intercept。

public Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
            Object oldProxy = null;
            boolean setProxyContext = false;
            Class<?> targetClass = null;
            Object target = null;
            try {
                if (this.advised.exposeProxy) {
                    // Make invocation available if necessary.
                    oldProxy = AopContext.setCurrentProxy(proxy);
                    setProxyContext = true;
                }
                //最重要的就是这里,getTarget,
                //这会到之前设置过的TargetSource中去获取TargetObject,
                //也就是SimpleBeanTarget的getTarget方法,
                //到这一步,scoped的proxy已经完成。
                target = getTarget();
                //省略后续调用
        }

小结

在这里我们介绍了AOP的一个重要标签,scoped-proxy。它是由ScopedProxyFactoryBean进行创建的,在创建的时候,为生成代理Bean的ProxyFactory指定了TargetSource,因此在每次拦截方法,进行调用之前,首先都会到指定的TargetSouce,也就是SimpleBeanTargetSource中获取对应Scoped下的Bean。

此外,ScopedProxyFactory还为这个Bean增加了getTargetObject的方法(使用Introduction),因此所有带上了这个标签的Bean,也就默认实现了ScopedObject的接口,可以调用getTargetObject方法。这个方法的意义在于,因为代理Bean的scope是默认singleton的,这也就意味着,我们每次调用applicationContext.getBean方法,总是返回同一个代理bean,如果我们想要获得scope下真正的bean的话,就可以调用getTargetObject方法了。

 类似资料: