Spring boot 注入成员变量HttpServletRequest的原理

前言

最近做项目,springboot项目,本来我们在controller的requestmapping取参数值或者返回写时,使用方法参数,但是发现老项目直接注入了成员变量,Spring本身是单例的,如果是成员变量注入,那么也是单例的,怎么实现不同的请求读取不同的参数呢,如果实现线程安全呢,笔者立马想到了ThreadLocal,但是如果要说就是这个原理,那么必须源码证明。

准备demo

简单写一个demo

@RestController
public class DemoController {

    @Autowired
    private HttpServletRequest request;

    @GetMapping("/hello")
    public String demo(String param) {
        request.getParameterMap().forEach((k,v)-> System.out.println(k + " : " + Arrays.toString(v)));
        return param + ":hello";
    }
}

只写了 HttpServletRequest,实际上HttpServletResponse亦是如此。

分析demo,注入的HttpServletRequest是接口类型,那么在Boot启动中就会动态代理实现,由于是接口,可以推测是JDK动态代理,debug果然如此。

源码分析

根据debug,JDK动态代理注入了接口的实现类,关键在于InvocationHandler,Spring使用如下:

org.springframework.beans.factory.support.AutowireUtils.ObjectFactoryDelegatingInvocationHandler

里面有关键代码

return method.invoke(this.objectFactory.getObject(), args);

this.objectFactory.getObject()这句决定线程安全

看看Spring Bean下JDK是怎么动态代理注入的

可以看到JDK动态代理在Spring注入的时候,把这个factory注入了InvocationHandler

其中的handler的invoke方法,这里实际上还要其他类的读取埋点。

这里的invoke仅仅是反射,关键还是 HttpServletRequest的对象来源,跟踪读取逻辑

org.springframework.web.context.request.RequestContextHolder

到此就明确了,注入的成员变量,动态代理后使用Threadlocal处理,所以线程安全,因为每次请求都是线程请求,那么原始的HttpServletRequest对象怎么塞进去的呢,就要看filter的了

org.springframework.web.filter.RequestContextFilter

在doFilter时,执行

同理在org.springframework.web.servlet.FrameworkServlet也会再次读取和写入

这里是为了,如果filter被去除的时候可以有值,再次保底,并且在结束时rest

 只不过这个rest有点奇怪

对象并没有清除,还在,说明即使 FrameworkServlet后还可以获取,因为有filter可能会在这个后面执行,如果干掉,很可能就不能读取了。会在RequestContextFilter后面remove;如果我们去掉这个filter,那么需要自定义一个filter实现remove防止内存泄漏。

清除当前线程及子线程ThreadLocal

public static void resetRequestAttributes() {
    requestAttributesHolder.remove();
    inheritableRequestAttributesHolder.remove();
}

至此 HttpServletRequest的成员变量注入逻辑,即ThreadLocal变量,所以请求可以正常访问

总结

实际上这种用法很多项目都用了,只不过我们写代码下意思的通过方法参数来规避线程安全性,这种想法是有益的,可以从源头规避风险,不过实际上Spring也帮我们考虑了这个问题,相当于使用RequestContextFilter做了保底措施。源码分析实际上是知所以然,尤其是我们自己写公共SDK时,可以把这种设计放在代码中,实现优雅和保底逻辑。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值