Spring 绑定有状态对象到线程
2018-04-13 本文已影响104人
ilaoke
问题背景
Spring中,在Bean默认为单例的前提下,如果需要在Controller和Services之间频繁传递一个有状态对象时,总是通过方法形参传递,是否不够优雅?如果是,那就continue,如果无所谓,就return.
解决方法
使用ThreadLocalTargetSource
,设置被代理对象(target bean),每个线程在操作target bean时,都是处理绑定在当前线程的对象,其原理是 代理 + ThreadLocal。
注意事项
在request结束时,要清理被代理对象的状态,这里利用filter来实现。
代码
配置ThreadLocalTargetSource以及Filter
@Configuration
public class AppConfig {
@Bean
public Filter transactionLogFilter() {
return new TransactionLogFilter();
}
@Bean
public FilterRegistrationBean tenantFilterRegistration() {
FilterRegistrationBean result = new FilterRegistrationBean();
result.setFilter(transactionLogFilter());
result.setUrlPatterns(Collections.singletonList("/*"));
result.setName("Transaction Log Filter");
result.setOrder(1);
return result;
}
@Bean(destroyMethod = "destroy")
public ThreadLocalTargetSource threadLocalTenantStore() {
ThreadLocalTargetSource threadLocalTargetSource = new ThreadLocalTargetSource();
threadLocalTargetSource.setTargetBeanName("transactionLogStore");
return threadLocalTargetSource;
}
@Primary
@Bean(name = "proxiedThreadLocalTargetSource")
public ProxyFactoryBean proxiedThreadLocalTargetSource(ThreadLocalTargetSource threadLocalTargetSource) {
ProxyFactoryBean result = new ProxyFactoryBean();
result.setTargetSource(threadLocalTargetSource);
return result;
}
@Bean(name = "transactionLogStore")
@Scope(scopeName = "prototype") // 注意此处是prototype,在需要Bean时,为每个线程创建各自的实例
public TransactionLogStore transactionLogStore() {
return new TransactionLogStore();
}
}
Filter
public class TransactionLogFilter implements Filter {
@Autowired
TransactionLogStore transactionLogStore;
@Override
public void init(FilterConfig filterConfig) throws ServletException {
}
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest) servletRequest;
String v = request.getParameter("v");
try {
transactionLogStore.init(v);
filterChain.doFilter(servletRequest, servletResponse);
} finally {
// 注意此处要清理被代理对象的状态,避免当前线程在被下一个请求使用时保留有上一个请求的状态
transactionLogStore.clear();
}
}
@Override
public void destroy() {
}
}
public class TransactionLogStore {
private String logId;
private TTransactionLog transactionLog;
public void init(String v) {
transactionLog = new TTransactionLog();
logId = v;
}
public void clear() {
logId = null;
transactionLog = null;
}
// ... set get
}
Ref:
http://tech.asimio.net/2017/11/28/An-alternative-to-ThreadLocal-using-Spring.html