Spring中RequestScope作用域Bean原理

Easter79
• 阅读 589

一、前言

web.xml里面配置

<listener>
        <listener-class>org.springframework.web.context.request.RequestContextListener</listener-class>
</listener>

component-bean.xml里面配置

 <bean id="lavaPvgInfo" class="com.alibaba.lava.privilege.PrivilegeInfo"
        scope="request">
        <property name="aesKey" value="666" />
        <aop:scoped-proxy />
    </bean>

测试Rpc

@WebResource("/testService")public class TestRpc {    @Autowired
    private PrivilegeInfo pvgInfo;    @ResourceMapping("test")    public ActionResult test(ErrorContext context) {
        ActionResult result = new ActionResult();

        String aseKey = pvgInfo.getAesKey();
        pvgInfo.setAesKey("888");
        System.out.println("aseKey---" + aseKey);        return result;
    }
}

二、源码分析

2.1 使用装饰模式对Bean定义进行修改

先上时序图:
Spring中RequestScope作用域Bean原理
可知上面时序图完成了对RequestScope对象定义的修改创建了代理bean,具体修改内容是修改了beanClass为ScopedProxyFactoryBean,并且保存了原来的bean定义originatingBeanDefinition。

下面看下主要代码ScopedProxyUtils中的createScopedProxy

public static BeanDefinitionHolder createScopedProxy(BeanDefinitionHolder definition,
            BeanDefinitionRegistry registry, boolean proxyTargetClass) {

        String originalBeanName = definition.getBeanName();
        BeanDefinition targetDefinition = definition.getBeanDefinition();        // 保持原来的beanName不变,但是基于原来的bean定义创建代理bean定义,
        // 保存原来的bean定义到代理bean里面为后面创建代理类做准备.
        RootBeanDefinition proxyDefinition = new RootBeanDefinition(ScopedProxyFactoryBean.class);
        proxyDefinition.setOriginatingBeanDefinition(definition.getBeanDefinition());
        proxyDefinition.setSource(definition.getSource());
        proxyDefinition.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);

        String targetBeanName = getTargetBeanName(originalBeanName);
        proxyDefinition.getPropertyValues().add("targetBeanName", targetBeanName);        if (proxyTargetClass) {
            targetDefinition.setAttribute(AutoProxyUtils.PRESERVE_TARGET_CLASS_ATTRIBUTE, Boolean.TRUE);            // ScopedFactoryBean's "proxyTargetClass" default is TRUE, so we don't need to set it explicitly here.
        }        else {
            proxyDefinition.getPropertyValues().add("proxyTargetClass", Boolean.FALSE);
        }        // Copy autowire settings from original bean definition.
        proxyDefinition.setAutowireCandidate(targetDefinition.isAutowireCandidate());
        proxyDefinition.setPrimary(targetDefinition.isPrimary());        if (targetDefinition instanceof AbstractBeanDefinition) {
            proxyDefinition.copyQualifiersFrom((AbstractBeanDefinition) targetDefinition);
        }        // The target bean should be ignored in favor of the scoped proxy.
        targetDefinition.setAutowireCandidate(false);
        targetDefinition.setPrimary(false);        // 注册代理前的bean到容器,在创建代理bean时候使用.targetBeanName=scopedTarget.lavaPvgInfo
        registry.registerBeanDefinition(targetBeanName, targetDefinition);        // 返回代理bean定义作为原来的bean定义
        return new BeanDefinitionHolder(proxyDefinition, originalBeanName, definition.getAliases());
    }

2.2 创建代理Bean

先上时序图
Spring中RequestScope作用域Bean原理

主要代码如下:

    public void setBeanFactory(BeanFactory beanFactory) {
        ...
        ConfigurableBeanFactory cbf = (ConfigurableBeanFactory) beanFactory;        this.scopedTargetSource.setBeanFactory(beanFactory);               //创建代理工厂
        ProxyFactory pf = new ProxyFactory();
        pf.copyFrom(this);
        pf.setTargetSource(this.scopedTargetSource);

        ...        // Add an introduction that implements only the methods on ScopedObject.
        ScopedObject scopedObject = new DefaultScopedObject(cbf, this.scopedTargetSource.getTargetBeanName());
        pf.addAdvice(new DelegatingIntroductionInterceptor(scopedObject));        // Add the AopInfrastructureBean marker to indicate that the scoped proxy
        // itself is not subject to auto-proxying! Only its target bean is.
        pf.addInterface(AopInfrastructureBean.class);        this.proxy = pf.getProxy(cbf.getBeanClassLoader());
    }

public Object getProxy(ClassLoader classLoader) {
            ....        try {//获取目标类,也就是被代理的
            Class rootClass = this.advised.getTargetClass();
            Assert.state(rootClass != null, "Target class must be available for creating a CGLIB proxy");

            Class proxySuperClass = rootClass;            if (ClassUtils.isCglibProxyClass(rootClass)) {
                proxySuperClass = rootClass.getSuperclass();
                Class[] additionalInterfaces = rootClass.getInterfaces();                for (Class additionalInterface : additionalInterfaces) {                    this.advised.addInterface(additionalInterface);
                }
            }            // Validate the class, writing log messages as necessary.
            validateClassIfNecessary(proxySuperClass);            // Configure CGLIB Enhancer...
            Enhancer enhancer = createEnhancer();            if (classLoader != null) {
                enhancer.setClassLoader(classLoader);                if (classLoader instanceof SmartClassLoader &&
                        ((SmartClassLoader) classLoader).isClassReloadable(proxySuperClass)) {
                    enhancer.setUseCache(false);
                }
            }                       //设置被代理类为超类,这样解释了为啥代理后的类能够赋值给被代理类不会发生错误
            enhancer.setSuperclass(proxySuperClass);
            enhancer.setStrategy(new UndeclaredThrowableStrategy(UndeclaredThrowableException.class));
            enhancer.setInterfaces(AopProxyUtils.completeProxiedInterfaces(this.advised));
            enhancer.setInterceptDuringConstruction(false);                       //获取拦截器,其中就有DynamicAdvisedInterceptor
            Callback[] callbacks = getCallbacks(rootClass);
            enhancer.setCallbacks(callbacks);
            enhancer.setCallbackFilter(new ProxyCallbackFilter(                    this.advised.getConfigurationOnlyCopy(), this.fixedInterceptorMap, this.fixedInterceptorOffset));

            Class[] types = new Class[callbacks.length];            for (int x = 0; x < types.length; x++) {
                types[x] = callbacks[x].getClass();
            }
            enhancer.setCallbackTypes(types);            // Generate the proxy class and create a proxy instance.
            Object proxy;            if (this.constructorArgs != null) {
                proxy = enhancer.create(this.constructorArgTypes, this.constructorArgs);
            }            else {
                proxy = enhancer.create();
            }            return proxy;
        }

2.3 调用时序图

Spring中RequestScope作用域Bean原理
代码:

private static class DynamicAdvisedInterceptor implements MethodInterceptor, Serializable {

        ...        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;
                }                // 获取被代理类
                target = getTarget();                if (target != null) {
                    targetClass = target.getClass();
                }
                List<Object> chain = this.advised.getInterceptorsAndDynamicInterceptionAdvice(method, targetClass);
                Object retVal;                // Check whether we only have one InvokerInterceptor: that is,
                // no real advice, but just reflective invocation of the target.
                if (chain.isEmpty() && Modifier.isPublic(method.getModifiers())) {

                    retVal = methodProxy.invoke(target, args);
                }                else {                    // We need to create a method invocation...
                    retVal = new CglibMethodInvocation(proxy, target, method, args, targetClass, chain, methodProxy).proceed();
                }
                retVal = massageReturnTypeIfNecessary(proxy, target, method, retVal);                return retVal;
            }            finally {                if (target != null) {
                    releaseTarget(target);
                }                if (setProxyContext) {                    // Restore old proxy.
                    AopContext.setCurrentProxy(oldProxy);
                }
            }
        }
}

getTarget是关键方法,看下:

    protected Object getTarget() throws Exception {        return this.advised.getTargetSource().getTarget();
    }    public Object getTarget() throws Exception {        return getBeanFactory().getBean(getTargetBeanName());
    }

所以最后是从IOC获取目标类bean.下面看下getBean代码:

//获取RequestScope对象String scopeName = mbd.getScope();final Scope scope = this.scopes.get(scopeName);if (scope == null) {    throw new IllegalStateException("No Scope registered for scope '" + scopeName + "'");
}try {   //调用RequestScope对象对象的get方法
    Object scopedInstance = scope.get(beanName, new ObjectFactory<Object>() {        public Object getObject() throws BeansException {
            beforePrototypeCreation(beanName);            try {                return createBean(beanName, mbd, args);
            }            finally {
                afterPrototypeCreation(beanName);
            }
        }
    });
    bean = getObjectForBeanInstance(scopedInstance, name, beanName, mbd);
}catch (IllegalStateException ex) {    throw new BeanCreationException(beanName,            "Scope '" + scopeName + "' is not active for the current thread; " +            "consider defining a scoped proxy for this bean if you intend to refer to it from a singleton",
            ex);
}

requestscope的get方法:

    public Object get(String name, ObjectFactory objectFactory) {               //获取当前线程属性集合
        RequestAttributes attributes = RequestContextHolder.currentRequestAttributes();
        Object scopedObject = attributes.getAttribute(name, getScope());        if (scopedObject == null) {//不在属性集则调用createBean创建,然后放入集合
            scopedObject = objectFactory.getObject();
            attributes.setAttribute(name, scopedObject, getScope());
        }        return scopedObject;
    }

可知requestAttributesHolder属性是threadlocal

public abstract class RequestContextHolder  {    private static final boolean jsfPresent =
            ClassUtils.isPresent("javax.faces.context.FacesContext", RequestContextHolder.class.getClassLoader());    private static final ThreadLocal<RequestAttributes> requestAttributesHolder =            new NamedThreadLocal<RequestAttributes>("Request attributes");    private static final ThreadLocal<RequestAttributes> inheritableRequestAttributesHolder =            new NamedInheritableThreadLocal<RequestAttributes>("Request context");
}

本文分享自微信公众号 - 技术原始积累(gh_805ebfd2deb0)。
如有侵权,请联系 support@oschina.cn 删除。
本文参与“OSC源创计划”,欢迎正在阅读的你也加入,一起分享。

点赞
收藏
评论区
推荐文章
浅梦一笑 浅梦一笑
5个月前
初学 Python 需要安装哪些软件?超级实用,小白必看!
编程这个东西是真的奇妙。对于懂得的人来说,会觉得这个工具是多么的好用、有趣,而对于小白来说,就如同大山一样。其实这个都可以理解,大家都是这样过来的。那么接下来就说一下python相关的东西吧,并说一下我对编程的理解。本人也是小白一名,如有不对的地方,还请各位大神指出01名词解释:如果在编程方面接触的比较少,那么对于软件这一块,有几个名词一定要了解,比如开发环
blmius blmius
1年前
MySQL:[Err] 1292 - Incorrect datetime value: ‘0000-00-00 00:00:00‘ for column ‘CREATE_TIME‘ at row 1
文章目录问题用navicat导入数据时,报错:原因这是因为当前的MySQL不支持datetime为0的情况。解决修改sql\mode:sql\mode:SQLMode定义了MySQL应支持的SQL语法、数据校验等,这样可以更容易地在不同的环境中使用MySQL。全局s
技术小男生 技术小男生
5个月前
linux环境jdk环境变量配置
1:编辑系统配置文件vi/etc/profile2:按字母键i进入编辑模式,在最底部添加内容:JAVAHOME/opt/jdk1.8.0152CLASSPATH.:$JAVAHOME/lib/dt.jar:$JAVAHOME/lib/tools.jarPATH$JAVAHOME/bin:$PATH3:生效配置
光头强的博客 光头强的博客
5个月前
Java面向对象试题
1、请创建一个Animal动物类,要求有方法eat()方法,方法输出一条语句“吃东西”。创建一个接口A,接口里有一个抽象方法fly()。创建一个Bird类继承Animal类并实现接口A里的方法输出一条有语句“鸟儿飞翔”,重写eat()方法输出一条语句“鸟儿吃虫”。在Test类中向上转型创建b对象,调用eat方法。然后向下转型调用eat()方
刚刚好 刚刚好
5个月前
css问题
1、在IOS中图片不显示(给图片加了圆角或者img没有父级)<div<imgsrc""/</divdiv{width:20px;height:20px;borderradius:20px;overflow:h
小森森 小森森
5个月前
校园表白墙微信小程序V1.0 SayLove -基于微信云开发-一键快速搭建,开箱即用
后续会继续更新,敬请期待2.0全新版本欢迎添加左边的微信一起探讨!项目地址:(https://www.aliyun.com/activity/daily/bestoffer?userCodesskuuw5n)\2.Bug修复更新日历2.情侣脸功能大家不要使用了,现在阿里云的接口已经要收费了(土豪请随意),\\和注意
晴空闲云 晴空闲云
5个月前
css中box-sizing解放盒子实际宽高计算
我们知道传统的盒子模型,如果增加内边距padding和边框border,那么会撑大整个盒子,造成盒子的宽度不好计算,在实务中特别不方便。boxsizing可以设置盒模型的方式,可以很好的设置固定宽高的盒模型。盒子宽高计算假如我们设置如下盒子:宽度和高度均为200px,那么这会这个盒子实际的宽高就都是200px。但是当我们设置这个盒子的边框和内间距的时候,那
艾木酱 艾木酱
5个月前
快速入门|使用MemFire Cloud构建React Native应用程序
MemFireCloud是一款提供云数据库,用户可以创建云数据库,并对数据库进行管理,还可以对数据库进行备份操作。它还提供后端即服务,用户可以在1分钟内新建一个应用,使用自动生成的API和SDK,访问云数据库、对象存储、用户认证与授权等功能,可专
Wesley13 Wesley13
1年前
MySQL查询按照指定规则排序
1.按照指定(单个)字段排序selectfromtable_nameorderiddesc;2.按照指定(多个)字段排序selectfromtable_nameorderiddesc,statusdesc;3.按照指定字段和规则排序selec
Wesley13 Wesley13
1年前
MySQL部分从库上面因为大量的临时表tmp_table造成慢查询
背景描述Time:20190124T00:08:14.70572408:00User@Host:@Id:Schema:sentrymetaLast_errno:0Killed:0Query_time:0.315758Lock_
helloworld_28799839 helloworld_28799839
5个月前
常用知识整理
Javascript判断对象是否为空jsObject.keys(myObject).length0经常使用的三元运算我们经常遇到处理表格列状态字段如status的时候可以用到vue