专注于 JetBrains IDEA 全家桶,永久激活,教程
持续更新 PyCharm,IDEA,WebStorm,PhpStorm,DataGrip,RubyMine,CLion,AppCode 永久激活教程

如何向singleton级别的bean中注入request级别的bean

1.现象

众所周知,在spring的bean中使用HttpServletRequest可以使用

@Autowired
protected HttpServletRequest request;

或者在controller的方法入参中加入HttpServletRequest

public void exportOrder(HttpServletRequest request) {
    return null;
}

但是为什么可以这样使用?controller类默认是单例的,使用@Autowired注入的response和request却可以在多个线程中使用,这就出现了作用域request级别的bean注入到singleton级别的bean中去的现象。

那么spring是如何保证在singleton作用域中使用更长作用域的bean的呢?

2.spring如何注入作用域不同的bean

spring如何注入作用域不同的bean

在此文中说明了spring中,如果需要将作用域较大的bean A注入较小的bean B中时,其实注入的是A的代理对象,在需要时使用代理getObject()来获取一些指定的依赖。

3.HttpServletRequest如何初始化

在controller中使用HttpServletRequest时,我们并不需要注入就可以使用。这是因为spring初始化的过程中,就已经把HttpServletRequest注册到容器中去了。通过spring的refresh()方法的postProcessBeanFactory(beanFactory),

61_1.png

该方法实现的ServletWebServerApplicationContextpostProcessBeanFactory调用了WebApplicationContextUtils.registerWebApplicationScopes()

61_2.png

4.HttpServletRequest的使用

那么知道了是如何注入之后,那么又是如何使用的呢。使用一个例子来看一看:

/**
 * @author LWong
 * @date 2020/04/23
 */
@RestController("req")
public class ExportController {

    @Autowired
    private HttpServletRequest httpServletRequest;

    @PostMapping("export")
    public void export(HttpServletRequest httpServletRequest1){
        String method = httpServletRequest.getMethod();
        System.out.println(method);
    }
}

分别在方法入参和controller中引用HttpServletRequest,打断点查看httpServletRequest和httpServletRequest1

61_3.png

在图片中可以发现httpServletRequest是一个代理对象是,作为成员变量注入的时候注入的是代理对象,AutowireUtils.ObjectFactoryDelegatingInvocationHandler的实例,而作为方法参数注入的就是我们一般使用的Request对象。那么来看看这个handler

/**
     * Reflective {@link InvocationHandler} for lazy access to the current target object.
     */
    @SuppressWarnings("serial")
private static class ObjectFactoryDelegatingInvocationHandler implements                 InvocationHandler, Serializable {

        private final ObjectFactory<?> objectFactory;

        public ObjectFactoryDelegatingInvocationHandler(ObjectFactory<?> objectFactory) {
            this.objectFactory = objectFactory;
        }

        @Override
        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable         {
            String methodName = method.getName();
            if (methodName.equals("equals")) {
                // Only consider equal when proxies are identical.
                return (proxy == args[0]);
            }
            else if (methodName.equals("hashCode")) {
                // Use hashCode of proxy.
                return System.identityHashCode(proxy);
            }
            else if (methodName.equals("toString")) {
                return this.objectFactory.toString();
            }
            try {
                return method.invoke(this.objectFactory.getObject(), args);
            }
            catch (InvocationTargetException ex) {
                throw ex.getTargetException();
            }
        }
}

通过this.objectFactory.getObject()方法进入到WebApplicationContextUtils.currentRequestAttributes()

61_4.png

从这个又要进入RequestContextHolder的getRequestAttributes方法

61_5.png

而requestAttributesHolder正是初始化的threadLocal,保证了线程安全

61_6.png

看到这里答案已经很明了了,这个RequestContextHolder的ThreadLocal成员变量就是实现的关键所在,它存放了每个线程对应的Request对象,因此在@Controller中调用作为成员变量注入的代理类的方法时,最终可以取到当前线程相对应的Request对象,并调用Request对应的方法,这样@Controller中的成员变量不需要重复注入(它一直都是最初bean初始化时注入的代理类),也避免了线程不安全的问题。


5.总结

1、 在bean中注入作为成员变量的HttpServletRequest时,实际注入的是spring框架生成的代理对象,是ObjectFactoryDelegatingInvocationHandler的实例。在我们调用这个成员变量的方法时,最终是调用了objectFactorygetObject()对象的对应方法,在这里objectFactoryRequestObjectFactory这个类的对象。
2、 RequestObjectFactorygetObject方法是从RequestContextHolder的threadlocal中去取值的。

文章永久链接:https://tech.souyunku.com/41142

未经允许不得转载:搜云库技术团队 » 如何向singleton级别的bean中注入request级别的bean

JetBrains 全家桶,激活、破解、教程

提供 JetBrains 全家桶激活码、注册码、破解补丁下载及详细激活教程,支持 IntelliJ IDEA、PyCharm、WebStorm 等工具的永久激活。无论是破解教程,还是最新激活码,均可免费获得,帮助开发者解决常见激活问题,确保轻松破解并快速使用 JetBrains 软件。获取免费的破解补丁和激活码,快速解决激活难题,全面覆盖 2024/2025 版本!

联系我们联系我们