簡介
ThreadLocal
能夠?yàn)楫?dāng)前線程提供存儲和讀取變量的能力瓦灶,提供一個靜態(tài)方法烁登,從而能夠讓若干模塊解耦奶陈;也為多線程并發(fā)提供一個思路捧挺,在ThreadLocal
中為當(dāng)前儲存變量,只為當(dāng)前線程所用尿瞭,讓多線程之間不互相干擾。
本文簡單介紹ThreadLocal
翅睛,列舉一些常見框架中的使用場景声搁,從而對它有更好的認(rèn)識捕发。
ThreadLocal API
ThreadLocal
常用的有三個方法set
, get
, remove
,下面用一小段代碼來看看這三個方法的使用疏旨。
public class ThreadLocalTest {
final static ThreadLocal<String> threadLocal = new ThreadLocal<>();
@Test
public void testThreadLocal() {
final String valueBeforeSet = threadLocal.get();
log.info("value before set: {}", valueBeforeSet);
threadLocal.set("test");
final String valueAfterSet = threadLocal.get();
log.info("value after set: {}", valueAfterSet);
threadLocal.remove();
final String valueAfterRemove = threadLocal.get();
log.info("value after remove: {}", valueAfterRemove);
}
}
輸出:
2018-05-21 20:43:41,390{GMT} INFO value before set: null
2018-05-21 20:43:41,396{GMT} INFO value after set: test
2018-05-21 20:43:41,396{GMT} INFO value after remove: null
上面的代碼中可以清楚看到get
和set
方法的使用檐涝,同時remove
也是非常重要的,因?yàn)榫€程池的原因法挨,如果不執(zhí)行remove
操作谁榜,這個線程在下次被重復(fù)使用的時候凡纳,存儲在ThreadLocal
中的值仍可使用。
在框架中的使用
Spring Security中的使用
如果你使用過Spring Security
,那么對SecurityContextHolder
肯定不陌生:
SecurityContext securityContext = SecurityContextHolder.getContext();
Authentication authentication = securityContext.getAuthentication();
上面這段代碼經(jīng)常用來獲取當(dāng)前認(rèn)證過的用戶相關(guān)信息荐糜,而這個方法之所以能夠工作巷怜,其中之一就用的ThreadLocal
。
詳細(xì)代碼可以可見github,這里摘取一些通過ThreadLocal
來存儲認(rèn)證信息片段暴氏。
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain)
throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest) req;
HttpServletResponse response = (HttpServletResponse) res;
if (request.getAttribute(FILTER_APPLIED) != null) {
// ensure that filter is only applied once per request
chain.doFilter(request, response);
return;
}
final boolean debug = logger.isDebugEnabled();
request.setAttribute(FILTER_APPLIED, Boolean.TRUE);
if (forceEagerSessionCreation) {
HttpSession session = request.getSession();
if (debug && session.isNew()) {
logger.debug("Eagerly created session: " + session.getId());
}
}
HttpRequestResponseHolder holder = new HttpRequestResponseHolder(request, response);
// 下面摘取HttpSessionSecurityContextRepository代碼來看看這個從哪里獲取
SecurityContext contextBeforeChainExecution = repo.loadContext(holder);
try {
// 調(diào)用set方法存儲
SecurityContextHolder.setContext(contextBeforeChainExecution);
// 繼續(xù)調(diào)用其他filter
chain.doFilter(holder.getRequest(), holder.getResponse());
} finally {
SecurityContext contextAfterChainExecution = SecurityContextHolder.getContext();
// Crucial removal of SecurityContextHolder contents - do this before anything else.
// finally方法中一定要remove,防止線程被重復(fù)使用关带,變量仍在
SecurityContextHolder.clearContext();
repo.saveContext(contextAfterChainExecution, holder.getRequest(), holder.getResponse());
request.removeAttribute(FILTER_APPLIED);
if (debug) {
logger.debug("SecurityContextHolder now cleared, as request processing completed");
}
}
}
下面在看看上面如何獲取SecurityContext
,這里摘取HttpSessionSecurityContextRepository
源碼片段,
public SecurityContext loadContext(HttpRequestResponseHolder requestResponseHolder) {
// 從requestResponseHolder中獲取request和response
HttpServletRequest request = requestResponseHolder.getRequest();
HttpServletResponse response = requestResponseHolder.getResponse();
HttpSession httpSession = request.getSession(false);
// 下面private方法看看如何從session里面獲取context
SecurityContext context = readSecurityContextFromSession(httpSession);
if (context == null) {
if (logger.isDebugEnabled()) {
logger.debug("No SecurityContext was available from the HttpSession: " + httpSession +". " +
"A new one will be created.");
}
context = generateNewContext();
}
SaveToSessionResponseWrapper wrappedResponse = new SaveToSessionResponseWrapper(response, request, httpSession != null, context);
requestResponseHolder.setResponse(wrappedResponse);
if(isServlet3) {
requestResponseHolder.setRequest(new Servlet3SaveToSessionRequestWrapper(request, wrappedResponse));
}
return context;
}
/**
*
* @param httpSession the session obtained from the request.
*/
private SecurityContext readSecurityContextFromSession(HttpSession httpSession) {
final boolean debug = logger.isDebugEnabled();
if (httpSession == null) {
if (debug) {
logger.debug("No HttpSession currently exists");
}
return null;
}
// Session exists, so try to obtain a context from it.
// 原來session里面存儲著一個特殊的屬性key為SPRING_SECURITY_CONTEXT研儒,這個就是context
Object contextFromSession = httpSession.getAttribute(springSecurityContextKey);
if (contextFromSession == null) {
if (debug) {
logger.debug("HttpSession returned null object for SPRING_SECURITY_CONTEXT");
}
return null;
}
// We now have the security context object from the session.
if (!(contextFromSession instanceof SecurityContext)) {
if (logger.isWarnEnabled()) {
logger.warn(springSecurityContextKey + " did not contain a SecurityContext but contained: '"
+ contextFromSession + "'; are you improperly modifying the HttpSession directly "
+ "(you should always use SecurityContextHolder) or using the HttpSession attribute "
+ "reserved for this class?");
}
return null;
}
if (debug) {
logger.debug("Obtained a valid SecurityContext from " + springSecurityContextKey + ": '" + contextFromSession + "'");
}
// Everything OK. The only non-null return from this method.
return (SecurityContext) contextFromSession;
}
上面代碼我們就看出了ThreadLocalSecurityContextHolderStrategy
是如何工作的了。
Spring MVC中的使用
如果你在Spring MVC
中啟用過過org.springframework.web.context.request.RequestContextListener
好芭,那么對RequestContextHolder
肯定不陌生,通過RequestContextHolder
提供的靜態(tài)方法招狸,可以獲取當(dāng)前request
對象邻薯。
public static HttpServletRequest getRequest() {
ServletRequestAttributes attr = (ServletRequestAttributes) RequestContextHolder.currentRequestAttributes();
return attr.getRequest();
}
public static HttpSession getSession() {
ServletRequestAttributes attr = (ServletRequestAttributes) RequestContextHolder.currentRequestAttributes();
return attr.getRequest().getSession(false); // true == allow create
}
上述方法能夠生效只要看看RequestContextListener源碼就很好理解了
public void requestInitialized(ServletRequestEvent requestEvent) {
if (!(requestEvent.getServletRequest() instanceof HttpServletRequest)) {
throw new IllegalArgumentException(
"Request is not an HttpServletRequest: " + requestEvent.getServletRequest());
}
HttpServletRequest request = (HttpServletRequest) requestEvent.getServletRequest();
ServletRequestAttributes attributes = new ServletRequestAttributes(request);
request.setAttribute(REQUEST_ATTRIBUTES_ATTRIBUTE, attributes);
LocaleContextHolder.setLocale(request.getLocale());
//存儲
RequestContextHolder.setRequestAttributes(attributes);
}
public void requestDestroyed(ServletRequestEvent requestEvent) {
//銷毀
ServletRequestAttributes attributes =
(ServletRequestAttributes) requestEvent.getServletRequest().getAttribute(REQUEST_ATTRIBUTES_ATTRIBUTE);
ServletRequestAttributes threadAttributes =
(ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
if (threadAttributes != null) {
// We're assumably within the original request thread...
if (attributes == null) {
attributes = threadAttributes;
}
LocaleContextHolder.resetLocaleContext();
RequestContextHolder.resetRequestAttributes();
}
if (attributes != null) {
attributes.requestCompleted();
}
}
多數(shù)據(jù)源切換
在開發(fā)中可能會遇到有多個數(shù)據(jù)源的情況厕诡,例如兩個Datasource
之間的切換累榜,配合Spring
的AOP
和AbstractRoutingDataSource
灵嫌,在加上改變ThreadLocal
中的值來完成數(shù)據(jù)源的切換。
實(shí)現(xiàn)AbstractRoutingDataSource
時猖凛,實(shí)現(xiàn)determineCurrentLookupKey
方法來返回ThreadLocal
中的值绪穆,通過其他方法
去改變ThreadLocal
中的值,例如對某些方法的切面, 自定義注解等等玖院。
log4j中的使用
如果想在·log4j·中打印一些公共的變量。
例如TrackingId
表示每一個請求难菌,可以在filter中先通過org.apache.log4j.MDC.put(String key, Object o)
插入一個trackingId
的key
(例如TACKING_ID
)和value
,然后配置log4j的格式%X{TACKING_ID}
即可在日志中實(shí)現(xiàn)插入一個值,即使為null
也沒有問題耍共。