springMVC注入是執行緒安全的嗎
springmvc的注入執行緒安全嗎
servlet是單例的,而tomcat則是在多個執行緒中呼叫servlet的處理方法。因此如果servlet存在例項物件,那麼就會引出執行緒安全的問題。而springmvc允許在controller類中通過@Autowired配置request、response以及requestcontext等例項物件。這種配置方法是否執行緒安全?答案是——這種配置方法是執行緒安全的,request、response以及requestcontext在使用時不需要進行同步。而根據spring的預設規則,controller對於beanfactory而言是單例的。即
在解釋controller執行緒安全這一問題之前需要首先了解如下的一些問題和概念:
1.servlet的request域的問題:request域是javaweb的基礎概念,他指的是從發起http請求到返回響應的這一段時間內,存在一個httprequest物件對應於http請求。以上的表述是沒有問題的,然而有些人“自作主張”的將之前的表述換成了其他的描述方式:(1):request
2.Threadlocal類:該物件包含兩個關鍵函式:set(Object obj)和get()。這兩個函式與呼叫該函式的執行緒相關,set方法將某一物件“注入”到當前執行緒中,而get方法則是從當前執行緒中獲取物件。
3.InvocationHandler介面:這是springmvc保證request物件執行緒安全的核心。通過實現該介面,開發者能夠在Java物件方法執行時進行干預,搭配Threadlocal就能夠實現執行緒安全。
下面將通過例子介紹springmvc如何保證request物件執行緒安全:
Httprequest介面:
- public interface HttpRequest {
- public void service();
- }
HttpRequestImpl類:對httprequest介面的具體實現,為了區別不同的HttpRequestImpl物件,本人為HttpRequestImpl設定了一個Double物件,如果不設定該物件,其預設為null
- public class HttpRequestImpl implements HttpRequest{
- public Double d;
- @Override
- public void service() {
- System.out.println("do some serivce, random value is "+d);
- }
- }
ThreadLocalTest類:負責向ThreadLocal設定物件和獲取物件,本人設定ThreadLocal物件為static,因此ThreadLocalTest類中只能有一個ThreadLocal物件。
- public class ThreadLocalTest {
- public static ThreadLocal<HttpRequest> local=new ThreadLocal<HttpRequest>();
- public static void set(HttpRequest f){
- if(get()==null){
- System.out.println("ThreadLocal is null");
- local.set(f);
- }
- }
- public static HttpRequest get(){
- return local.get();
- }
- }
Factory類:該類是一個工廠類並且是單例模式,主要負責向ThreadLocalTest物件中設定和獲取物件
- public class Factory{
- private static Factory factory=new Factory();
- private Factory(){
- }
- public static Factory getInstance(){
- return factory;
- }
- public HttpRequest getObject(){
- return (HttpRequest)ThreadLocalTest.get();
- }
- public void setObject(HttpRequest request){
- ThreadLocalTest.set(request);
- }
- }
Delegate類:該類實現了InvocationHandler介面,並實現了invoke方法
- import java.lang.reflect.InvocationHandler;
- import java.lang.reflect.Method;
- import java.lang.reflect.Proxy;
- public class Delegate implements InvocationHandler{
- private Factory factory;
- public Factory getFactory() {
- return factory;
- }
- public void setFactory(Factory factory) {
- this.factory = factory;
- }
- @Override
- public Object invoke(Object proxy, Method method, Object[] args)
- throws Throwable {
- return method.invoke(this.factory.getObject(), args);
- }
- }
ProxyUtils類:該類是一個工具類,負責生成一個httprequest物件的代理
- import java.lang.reflect.Proxy;
- public class ProxyUtils {
- public static HttpRequest getRequest(){
- HttpRequest request=new HttpRequestImpl();
- Delegate delegate=new Delegate();
- delegate.setFactory(Factory.getInstance());
- HttpRequest proxy=(HttpRequest) Proxy.newProxyInstance(request.getClass().getClassLoader(), request.getClass().getInterfaces(), delegate);
- return proxy;
- }
- }
TestThread類:該類用來模擬多執行緒呼叫controller的情況,類中擁有一個靜態物件request。
- public class TestThread implements Runnable{
- private static HttpRequest request;
- public void init(){
- HttpRequestImpl requestimpl=new HttpRequestImpl();
- requestimpl.d=Math.random();
- Factory.getInstance().setObject(requestimpl);
- }
- @Override
- public void run() {
- System.out.println("*********************");
- init();
- request.service();
- System.out.println("*********************");
- }
- public static HttpRequest getRequest() {
- return request;
- }
- public static void setRequest(HttpRequest request) {
- TestThread.request = request;
- }
- }
main:測試類
- public class main {
- /**
- * @param args
- */
- public static void main(String[] args) {
- HttpRequest request=ProxyUtils.getRequest();
- TestThread thread1=new TestThread();
- thread1.setRequest(request);
- TestThread thread2=new TestThread();
- thread2.setRequest(request);
- Thread t1=new Thread(thread1);
- Thread t2=new Thread(thread2);
- t1.start();
- t2.start();
- }
- }
thread1和thread2設定了同一個request物件,正常來說這兩個物件呼叫run方法時輸出的隨機值應該為null(因為設定給這兩個物件的request並沒有設定d的值)。然而事實上這兩個執行緒在呼叫時不但輸出了隨機值而且隨機值還各不相同。這是因為request物件設定了代理,當呼叫request物件的service方法時,代理物件會從Threadlocal中獲取實際的request物件以替代呼叫當前的request物件。由於httprequest物件在處理執行緒中保持不變,因此controller通過呼叫httprequest物件的方法能夠獲取當前請求的引數。
以上都是一家之言,下面將通過展現springmvc原始碼的形式證明以上的說法:
ObjectFactoryDelegatingInvocationHandler類:該類是AutowireUtils的一個私有類,該類攔截了除了equals、hashcode以及toString以外的其他方法,其中的objectFactory是RequestObjectFactory例項。
- private static class ObjectFactoryDelegatingInvocationHandler implements InvocationHandler, Serializable {
- private final ObjectFactory objectFactory;
- public ObjectFactoryDelegatingInvocationHandler(ObjectFactory objectFactory) {
- this.objectFactory = objectFactory;
- }
- 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();
- }
- }
- }
RequestObjectFactory類:其中currentReuqestAttributes負責從Threadlocal中獲取物件
- private static class RequestObjectFactory implements ObjectFactory<ServletRequest>, Serializable {
- public ServletRequest getObject() {
- return currentRequestAttributes().getRequest();
- }
- @Override
- public String toString() {
- return "Current HttpServletRequest";
- }
- }
既然需要從Threadlocal中獲取物件,那springmvc在何時向Threadlocal設定了該物件呢?分別在如下兩個類中完成:RequestContextListener和FrameworkServlet。RequestContextListener負責監聽servletcontext,當servletcontext啟動時,RequestContextListener向Threadlocal設定了httprequest物件。FrameworkServlet是DispatchServlet的基類,tomcat會在執行過程中啟動新的執行緒,而該執行緒中並沒有httprequest物件。因此servlet會在每次處理http請求的時候檢驗當前的Threadlocal中是否有httprequest物件,如果沒有則設定該物件。
FrameworkServlet通過布林值previousRequestAttributes檢驗httprequest是否存在的程式碼:
- protected final void processRequest(HttpServletRequest request, HttpServletResponse response)
- throws ServletException, IOException {
- long startTime = System.currentTimeMillis();
- Throwable failureCause = null;
- // Expose current LocaleResolver and request as LocaleContext.
- LocaleContext previousLocaleContext = LocaleContextHolder.getLocaleContext();
- LocaleContextHolder.setLocaleContext(buildLocaleContext(request), this.threadContextInheritable);
- // Expose current RequestAttributes to current thread.
- RequestAttributes previousRequestAttributes = RequestContextHolder.getRequestAttributes();
- ServletRequestAttributes requestAttributes = null;
- if (previousRequestAttributes == null || previousRequestAttributes.getClass().equals(ServletRequestAttributes.class)) {
- requestAttributes = new ServletRequestAttributes(request);
- RequestContextHolder.setRequestAttributes(requestAttributes, this.threadContextInheritable);
- }
- if (logger.isTraceEnabled()) {
- logger.trace("Bound request context to thread: " + request);
- }
- try {
- doService(request, response);
- }
- catch (ServletException ex) {
- failureCause = ex;
- throw ex;
- }
- catch (IOException ex) {
- failureCause = ex;
- throw ex;
- }
- catch (Throwable ex) {
- failureCause = ex;
- throw new NestedServletException("Request processing failed", ex);
- }
- finally {
- // Clear request attributes and reset thread-bound context.
- LocaleContextHolder.setLocaleContext(previousLocaleContext, this.threadContextInheritable);
- if (requestAttributes != null) {
- RequestContextHolder.setRequestAttributes(previousRequestAttributes, this.threadContextInheritable);
- requestAttributes.requestCompleted();
- }
- if (logger.isTraceEnabled()) {
- logger.trace("Cleared thread-bound request context: " + request);
- }
- if (logger.isDebugEnabled()) {
- if (failureCause != null) {
- this.logger.debug("Could not complete request", failureCause);
- }
- else {
- this.logger.debug("Successfully completed request");
- }
- }
- if (this.publishEvents) {
- // Whether or not we succeeded, publish an event.
- long processingTime = System.currentTimeMillis() - startTime;
- this.webApplicationContext.publishEvent(
- new ServletRequestHandledEvent(this,
- request.getRequestURI(), request.getRemoteAddr(),
- request.getMethod(), getServletConfig().getServletName(),
- WebUtils.getSessionId(request), getUsernameForRequest(request),
- processingTime, failureCause));
- }
- }
- }
RequestContextListener在context初始化時通過requestInitialized函式向Threadlocal設定httprequest物件的程式碼:
- 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);
- }