SpringBoot 注入请求公用参数(线程安全)
介绍
Spring 中我们可以使用如下方式注入当前请求的信息,而不会产生线程安全问题。
public class DemoController {
@Autowired
private HttpServletRequest httpServletRequest;
}
其主要实现方式为: 将当前请求的信息放入ThreadLocal中。并且会创建一个HttpServletRequest的代理对象,每次使用时通过该代理对象获取ThreadLocal中的信息
我们项目中通常也存在这样的需求,前端将一些公用的参数放在请求头中传递到后台,后台使用时无需再次查询数据库,可以直接获取。
原理介绍
我们通过ThreadLocal存储请求信息。
通过BeanFactoryPostProcessor定义一个动态创建的bean(调用时才创建bean)。
通过ObjectFactory生产动态创建的bean,获取ThreadLocal中的信息返回。
ThreadLocal
ThreadLocal用于隔离每个线程中的变量,使用者可以将当前线程的变量存储,其他线程无法获取。这非常适用于我们web请求中,某次请求的信息存储。但注意,如果在服务器端处理时,开启了新的线程,threadLocal获取信息时则获取不到,可以提现获取并存储在final中供新的线程使用。
BeanFactoryPostProcessor&&BeanPostProcessor
BeanFactoryPostProcessor(定义bean的扩展点)和BeanPostProcessor(注入bean的扩展点)作为Spring提供的操作bean的扩展点,被各大组件广泛应用。
如mybatis的扫描组件MapperScannerConfigurer便实现的BeanDefinitionRegistryPostProcessor(继承的BeanFactoryPostProcessor),用于将mybatis注解所标注的类,定义成spring bean,等待spring容器的初始化。
其中BeanFactoryPostProcessor可以拦截到定义bean、但是还没注入到容器时的端点,可以在此时增加自定义的一些组件,如创建一个自定义注解,将其标注的类定义成Spring的Bean。
其中BeanPostProcessor可以拦截到bean注入时的端点,可以在bean注入前与注入后针对要注入的对象做一些处理。
ObjectFactory
BeanFactoryPostProcessor中的postProcessBeanFactory方法,允许我们额外添加bean的定义。
其中ConfigurableListableBeanFactory存在registerResolvableDependency方法允许我们定义的bean不直接加入容器,而是在调用时才创建bean,此方法需要传递生成bean的工厂类。
ObjectFactory允许在每次调用时返回一些目标对象的新实例。我们可以在其getObject方法中,每次都动态返回一个对象。
实现
WebContextFacade
public class WebContextFacade {
private static ThreadLocal<RequestContext> requestContextThreadLocal = new ThreadLocal<>();
public static RequestContext getRequestContext() {
RequestContext requestContext = requestContextThreadLocal.get();
return requestContext == null ? new RequestContextConcrete() : requestContext;
}
public static void setRequestContext(RequestContext requestContext) {
requestContextThreadLocal.set(requestContext);
}
public static void removeRequestContext() {
requestContextThreadLocal.remove();
}
}
RequestContext
ObjectFactory要求必须定义接口。
public interface RequestContext {
String getIp();
void setIp(String ip);
String getUri();
void setUri(String uri);
}
RequestContextConcrete
RequestContextConcrete为真实生成的对象。
Data
public class RequestContextConcrete implements RequestContext {
private String ip;
private String uri;
}
ContextInterceptor
通过拦截器将请求对象放入ThreadLocal中,供ObjectFactory调用。
public class ContextInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request,
HttpServletResponse response, Object handler) {
setRequestContext(request);
return true;
}
private void setRequestContext(HttpServletRequest request) {
RequestContext requestContext = WebContextFacade.getRequestContext();
requestContext.setIp(NetworkUtil.getIp(request));
requestContext.setUri(request.getRequestURI());
WebContextFacade.setRequestContext(requestContext);
}
@Override
public void afterCompletion(HttpServletRequest request,
HttpServletResponse response, Object handler, Exception ex) {
WebContextFacade.removeRequestContext();
}
}
RequestContextBeanFactoryPostProcessor&&RequestContextObjectFactory
Spring启动时,定义动态装配bean。
请求时,通过ObjectFactory的getObject()动态获取当前请求的信息,由拦截器放入到ThreadLocal中。
public class RequestContextBeanFactoryPostProcessor implements BeanFactoryPostProcessor {
@Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
beanFactory.registerResolvableDependency(RequestContext.class, new RequestContextObjectFactory());
}
public static class RequestContextObjectFactory implements ObjectFactory<RequestContext>, Serializable {
@Override
public RequestContext getObject() {
return WebContextFacade.getRequestContext();
}
@Override
public String toString() {
return WebContextFacade.getRequestContext().toString();
}
}
}