1. 程式人生 > >ThreadLocal來儲存Session,以便實現Session any where

ThreadLocal來儲存Session,以便實現Session any where

1.Application物件 

    多個使用者共享的應用級別的作用域,在伺服器端,相比前兩者,這個存在時間是最長的,只有當關閉伺服器的時候才死亡!所以他可以活很長時間。
    Application用於儲存所有使用者的公共的資料資訊,如果使用Application物件,一個需要考慮的問題是任何寫操作都要在Application_OnStart事件(global.asax)中完成.儘管使用Application.Lock和Applicaiton.Unlock方法來避免寫操作的同步,但是它序列化了對Application物件的請求,當網站訪問量大的時候會產生嚴重的效能瓶頸.因此最好不要用此物件儲存大的資料集合

2.Session物件

    session是伺服器端技術,利用這個技術,伺服器可以把與會話相關的資料寫到一個代表會話的 session物件中,用來儲存使用者跨網頁程式的變數或物件,只針對單一使用者。
    Session用於儲存每個使用者的專用資訊.她的生存期是使用者持續請求時間再加上一段時間(一般是20分鐘左右).Session中的資訊儲存在Web伺服器內容中,儲存的資料量可大可小.當Session超時或被關閉時將自動釋放儲存的資料資訊.由於使用者停止使用應用程式後它仍然在記憶體中保持一段時間,因此使用Session物件使儲存使用者資料的方法效率很低.對於小量的資料,使用Session物件儲存還是一個不錯的選擇.使用Session物件儲存資訊:
session有效期可以自己設定
方法一:在web.xm中使用l<session-config>的子標籤 <session.timeout>,單位為分鐘,主要是針對整個應用的所有session。
方法二: 

    HttpSession session  = request.getSession();
    session.setMaxInactiveInterval(“自己想要設定的具體時間”)。

預設情況下關閉瀏覽器session就失效,但是可以手動設定時間的。

3.Cookie物件
    Cookie用於儲存客戶瀏覽器請求伺服器頁面的請求資訊,程式設計師也可以用它存放非敏感性的使用者資訊,資訊儲存的時間可以根據需要設定.如果沒有設定Cookie失效日期,它們僅儲存到關閉瀏覽器程式為止.如果將Cookie物件的Expires屬性設定為Minvalue,則表示Cookie永遠不會過期.Cookie儲存的資料量很受限制,大多數瀏覽器支援最大容量為4096,因此不要用來儲存資料集及其他大量資料.由於並非所有的瀏覽器都支援Cookie,並且資料資訊是以明文文字的形式儲存在客戶端的計算機中,因此最好不要儲存敏感的,未加密的資料,否則會影響網站的安全性.使用Cookie物件儲存的程式碼如下:

//存放資訊
Response.Cookies["UserID"].Value="0001";
//讀取資訊
string UserID=Response.Cookies["UserID"].Value;
Cookie cookie = new Cookie(“mycookie”,“name”);
cookie.setMaxAge("自己指定的時間")。。

 cookie存放在客戶端中,因此有效期時間以客戶端的時間為準。可以自己手動設定,
 如果沒有指定Cookies物件的有效期,則Cookies物件只存在於客戶端的記憶體。當瀏覽器關閉時,Cookies就會失效。

ThreadLocal來儲存Session,以便實現Session any where

ThreadLocal使用場景用來解決 資料庫連線、Session管理等。

package com.enation.framework.context.webcontext;
 
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
 
import org.apache.log4j.Logger;
 
import com.enation.framework.context.webcontext.impl.WebSessionContextImpl;
 
 
/**
 *  用ThreadLocal來儲存Session,以便實現Session any where 
 * @author kingapex
 * <p>2009-12-17 下午03:10:09</p>
 * @version 1.1
 * 新增request any where
 */
public class ThreadContextHolder  {
 
protected static final Logger logger = Logger.getLogger(ThreadContextHolder.class);
 
private static ThreadLocal<WebSessionContext> SessionContextThreadLocalHolder = new ThreadLocal<WebSessionContext>();
private static ThreadLocal<HttpServletRequest> HttpRequestThreadLocalHolder = new ThreadLocal<HttpServletRequest>();
private static ThreadLocal<HttpServletResponse> HttpResponseThreadLocalHolder = new ThreadLocal<HttpServletResponse>();
 
 
public static void setHttpRequest(HttpServletRequest request){
 
 
HttpRequestThreadLocalHolder.set(request);
 
}
 
public static HttpServletRequest getHttpRequest(){
 
return  HttpRequestThreadLocalHolder.get();
 
}
 
 
 
public static void remove(){
 
SessionContextThreadLocalHolder.remove();
HttpRequestThreadLocalHolder.remove();
HttpResponseThreadLocalHolder.remove();
 
}
 
 
public static void setHttpResponse(HttpServletResponse response){
 
HttpResponseThreadLocalHolder.set(response);
 
}
 
public static HttpServletResponse getHttpResponse(){
 
 
return HttpResponseThreadLocalHolder.get();
 
}
 
 
 
public static void setSessionContext(WebSessionContext context) {
 
SessionContextThreadLocalHolder.set(context);
 
}
 
public static void destorySessionContext() {
 
WebSessionContext context = SessionContextThreadLocalHolder.get();
if (context != null) {
 
context.destory();
 
}
 
}
 
public static   WebSessionContext  getSessionContext() {
 
if (SessionContextThreadLocalHolder.get() == null) {
 
//if(logger.isDebugEnabled())
//logger.debug("create new webSessionContext.");
SessionContextThreadLocalHolder.set(new WebSessionContextImpl());
 
}else{
 
//if(logger.isDebugEnabled())
//logger.debug(" webSessionContext not null and return ...");
 
}
return SessionContextThreadLocalHolder.get();
 
}
 
 
}

ThreadLocal獲取session

ThreadLocal用於儲存某個執行緒共享變數:對於同一個static ThreadLocal,不同執行緒只能從中get,set,remove自己的變數,而不會影響其他執行緒的變數。

ThreadLocal在每個執行緒中對該變數會建立一個副本,即每個執行緒內部都會有一個該變數,且線上程內部任何地方都可以使用,執行緒之間互不影響,這樣一來就不存線上程安全問題,也不會嚴重影響程式執行效能。

但是要注意,雖然ThreadLocal能夠解決上面說的問題,但是由於在每個執行緒中都建立了副本,所以要考慮它對資源的消耗,比如記憶體的佔用會比不使用ThreadLocal要大。

ThreadLocal類提供的幾個方法:

1、ThreadLocal.get: 獲取ThreadLocal中當前執行緒共享變數的值,獲取ThreadLocal在當前執行緒中儲存的變數副本

2、ThreadLocal.set: 設定ThreadLocal中當前執行緒共享變數的值,設定當前執行緒中變數的副本

3、ThreadLocal.remove: 移除ThreadLocal中當前執行緒共享變數的值。

4、ThreadLocal.initialValue: ThreadLocal沒有被當前執行緒賦值時或當前執行緒剛呼叫remove方法後呼叫get方法,返回此方法值,是一個protected方法,一般是用來在使用時進行重寫的,它是一個延遲載入方法

    一般的Web應用劃分為展現層、服務層和持久層三個層次,在不同的層中編寫對應的邏輯,下層通過介面向上層開放功能呼叫。在一般情況下,從接收請求到返回響應所經過的所有程式呼叫都同屬於一個執行緒。

   也就是說,同一執行緒貫通N層,不同的執行緒可能由於引數等不同會對程式中的某些變數進行修改,但是又要防止修改後的值對其它執行緒產生影響,因為不同的執行緒可以同時執行滴,這就需要我們解決對某些執行緒共享的變數的訪問衝突問題。ThreadLocal本地執行緒變數就是一種解決方式,它通過將程式中不安全的變數封裝進ThreadLocal中,這相當於為每一個執行緒提供一個獨立的變數副本(其實是不同的物件),執行緒修改變數的值對其它執行緒來說沒影響了,因為其它執行緒有自己的一個副本資訊。

二、藉助ThreadLocal物件每個執行緒只建立一個例項

 public static final String dateFormat="yyyy-MM-dd";
 private static final ThreadLocal<DateFormat> dfThreadLocal=new ThreadLocal<DateFormat>(){
      @Override
      protected DateFormat initialValue() {
          return new SimpleDateFormat(dateFormat);
      }
 };
 public static String dateToString(Date date){
      return dfThreadLocal.get().format(date);
}

對於每個執行緒,都有一個類似於Map的東西ThreadLocalMap(ThreadLocal的靜態類 ),那它裡面儲存了什麼東東呢,肯定是key-value啊,key就是上面程式碼中的共享靜態變數 dfThreadLocal,value就是DateFormat例項了,即new SimpleDateFormat(dateFormat)這個東東。那接下來,線上程內我要如何去獲取這個值呢,就是靠dfThreadLocal.get()實現滴,方法原始碼如下:

ThreadLocal .ThreadLocalMap inheritableThreadLocals = null ;
  public T get () {
     Thread t = Thread.currentThread ();
     ThreadLocalMap map = getMap(t );
     if ( map != null) {
          ThreadLocalMap.Entry e = map.getEntry (this);
         if ( e != null)
             return ( T)e .value;
     }
    return setInitialValue ();
}
ThreadLocalMap getMap (Thread t) {
    return t .inheritableThreadLocals;
}

可以很明顯的看出,首先根據Thread.currentThread ()獲取到inheritableThreadLocals(即ThreadLocalMap,他是Thread的一個變數),然後將this(即最上面程式碼的dfThreadLocal物件)作為key(或索引)獲取到真正的值T(就是SimpleDateFormat物件)啊,至此應該比較清楚了。

    為什麼不同的執行緒有各自的值,因為 不同的執行緒--->不同的ThreadLocalMap物件(執行緒的變數)--->通過相同的key(如果有被static修飾)獲取到不同的value值

  備註:一般都被static修飾,因為可以避免在一個執行緒內可能發生的重複建立TSO(Thread Specific Object,即ThreadLocal所關聯的物件),被statis修飾了,同一執行緒key也肯定一樣,value也肯定只有一份了。

 一個ThreadLocal例項關聯當前執行緒的一個TSO物件,如果把ThreadLocal宣告為例項變數,那麼每建立一個類例項就會導致一個TSO例項誕生,這肯定沒有這個必要滴。

具體實現

獲取session的工具類

<!-- http session 處理類 -->
	<bean id="httpSessionService" class="com.session.impl.HttpSessionService" lazy-init="false"/>
package com.platform.framework.session.impl;

import com.platform.framework.context.ThreadContextHolder;
import com.platform.inf.ISessionService;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpSession;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

public class HttpSessionService
  implements ISessionService
{
  private final Log logger = LogFactory.getLog(getClass());
  
  public Object getAttribute(String arg0)
  {
    HttpSession session = getSession();
    return session == null ? null : session.getAttribute(arg0);
  }
  
  public String getId()
  {
    HttpSession session = getSession();
    return session == null ? null : session.getId();
  }
  
  public void invalidate()
  {
    HttpSession session = getSession();
    if (session != null) {
      session.invalidate();
    }
  }
  
  public void removeAttribute(String arg0)
  {
    HttpSession session = getSession();
    if (session != null) {
      session.removeAttribute(arg0);
    }
  }
  
  public void setAttribute(String arg0, Object arg1)
  {
    HttpSession session = getSession();
    if (session != null) {
      session.setAttribute(arg0, arg1);
    }
  }
  
  private HttpSession getSession()
  {
    HttpServletRequest request = ThreadContextHolder.getHttpRequest();
    if ((request == null) || (request.getSession() == null)) {
      this.logger.info("============================>>>sessoin 失效");
    }
    return request.getSession();
  }
}
package com.platform.framework.context;

import java.util.HashMap;
import java.util.Map;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;

public class ThreadContextHolder
{
  private static ThreadLocal<HttpServletRequest> HttpRequestThreadLocalHolder = new ThreadLocal();
  private static ThreadLocal<HttpServletResponse> HttpResponseThreadLocalHolder = new ThreadLocal();
  private static ThreadLocal<Map<String, Object>> threadVar = new ThreadLocal();
  
  public static void setThreadValue(String key, Object value)
  {
    Map<String, Object> map = (Map)threadVar.get();
    if (map == null)
    {
      map = new HashMap();
      map.put(key, value);
      threadVar.set(map);
    }
    else
    {
      map.put(key, value);
    }
  }
  
  public static <T> T getThreadValue(String key)
  {
    Map<String, Object> map = (Map)threadVar.get();
    if (map != null) {
      return map.get(key);
    }
    return null;
  }
  
  public static void setHttpRequest(HttpServletRequest request)
  {
    HttpRequestThreadLocalHolder.set(request);
  }
  
  public static HttpServletRequest getHttpRequest()
  {
    return (HttpServletRequest)HttpRequestThreadLocalHolder.get();
  }
  
  public static void setHttpResponse(HttpServletResponse response)
  {
    HttpResponseThreadLocalHolder.set(response);
  }
  
  public static HttpServletResponse getHttpResponse()
  {
    return (HttpServletResponse)HttpResponseThreadLocalHolder.get();
  }
  
  public static String getSessionId()
  {
    HttpServletRequest request = (HttpServletRequest)HttpRequestThreadLocalHolder.get();
    if (null != request) {
      return request.getSession().getId();
    }
    return null;
  }
  
  public static void clearThreadValues()
  {
    threadVar.remove();
  }
}
	public String getDict(String dictCode, String value) {
		Dictionary dict = getDict(dictCode);
		if (dict == null || value == null)
			return null;
		List<DictionaryItem> dictionaryItems = dict.getDictItems();
		
		if(dictionaryItems == null && dict.getDictType() == Constant.NUMBER_INTEGER_1) { 		//內部字典,但字典項為null(單獨獲取內部字典項,並放入快取中)
			dictionaryItems = dictContextService.getDictItems(dict);
			dict.setDictItems(dictionaryItems);
		}else if(dict.getDictType() == Constant.NUMBER_INTEGER_2) {								//外部字典(獲取字典項從執行緒快取中)
			Dictionary odict = ThreadContextHolder.getThreadValue(dictCode);
			if(odict == null) {
				dictionaryItems = dictContextService.getDictItems(dict);
				dict.setDictItems(dictionaryItems);
				ThreadContextHolder.setThreadValue(dict.getDictCode(), dict);
			}else {
				dictionaryItems = odict.getDictItems();
			}
		}
		
	@ResponseBody
	@RequestMapping(value = "/loginCheck", method = RequestMethod.POST)
	public AssembleJSON loginCheck(Model model,HttpServletRequest request) {
		try {
			String userCode = request.getParameter("userCode");
			String sender = request.getParameter("userCode") + request.getParameter("password");
			String EncryptedStr = MD5Util.MD5Encrypted(sender);
			String str = userService.checkUser(userCode);
			if (LoginConstant.LOGIN_USER_NOTEXIST_CODE.equals(str)) { // 使用者不存在
				return AssembleJSON.SUCCESS(Integer.valueOf(LoginConstant.LOGIN_USER_NOTEXIST_CODE),
						LoginConstant.LOGIN_NOTEXIST_STRING);
			}
			if (str == LoginConstant.LOGIN_USER_INVALID_CODE) { // 無效使用者
				return AssembleJSON.SUCCESS(Integer.valueOf(LoginConstant.LOGIN_USER_INVALID_CODE),
						LoginConstant.LOGIN_USER_INVALID_STRING);
			}
			if (str == LoginConstant.LOGIN_USER_LOCKED_CODE) { // 鎖定使用者
				return AssembleJSON.SUCCESS(Integer.valueOf(LoginConstant.LOGIN_USER_LOCKED_CODE),
						LoginConstant.LOGIN_USER_LOCKED_STRING);
			}
			String verifyCode = request.getParameter("verifyCode");
			String code = (String) request.getSession().getAttribute("verCode");
			if (null == code) { // 驗證碼過期
				return AssembleJSON.SUCCESS(Integer.valueOf(LoginConstant.LOGIN_VERIFYCODE_OUTDATE_CODE),
						LoginConstant.LOGIN_VERIFYCODE_OUTDATE_STRING);
			}
			if (null != code && verifyCode.toLowerCase().equals(code.toLowerCase())) {
				if (EncryptedStr.equals(str)) {
					User user = userService.getUserByCode(userCode);
					user.setUserPass(request.getParameter("password"));
					request.getSession(true).setAttribute(LoginConstant.LOGIN_USER_SESSION_KEY, user);
					ThreadContextHolder.setHttpRequest(request); // 將當前登入 Request 放入執行緒變數
					return AssembleJSON.SUCCESS(user);
				} else { // 使用者密碼錯誤
					return checkLoginNum(request,userCode);
				}
			} else { // 驗證碼錯誤
				return AssembleJSON.SUCCESS(Integer.valueOf(LoginConstant.LOGIN_VERIFYCODE_ERROR_CODE),
						LoginConstant.LOGIN_VERIFYCODE_ERROR_STRING);
			}
		}finally{
			try {
				User user = (User) request.getSession().getAttribute(LoginConstant.LOGIN_USER_SESSION_KEY);
				if(user != null) {
					Log log = new Log();
					log.setLogUserCode(user.getUserCode());
					log.setLogUserName(user.getUserName());
					log.setLogType(Constant.LOG_TYPE_LOGIN);
					log.setLogTime(new Date());
					log.setLogIp(request.getRemoteAddr());
					logService.insertLog(log);			// 新增登入記錄到系統日誌表
				 }
				}catch(Exception e) {
					log.error(e.getMessage());
				}
			}
	}



取執行緒中的中的user

User user = (User)ThreadContextHolder.getHttpRequest().getSession().getAttribute("current_login_user");

實現request anywhere

  import com.platform.core.web.Request;
  public class AppMgr {
   /**
     * 執行緒級變數
     */
    private static final ThreadLocal TL = new ThreadLocal();

    /**
     * 獲取執行緒級例項物件
     * @return rtnBean rtnBean
     */
    private static Bean threadBean() {
        Bean bean = (Bean) TL.get();
        if (bean == null) {
            bean = new Bean();
            TL.set(bean);
        }
        return bean;
    }

    /**
     * 獲取執行緒級例項物件中某引數值
     * @param key key
     * @return rtnObj rtnObj
     */
    public static Object threadVar(String key) {
        return threadBean().get(key);
    }

    /**
     * 設定執行緒級例項物件中某引數值
     * @param key key
     * @param obj setter objInst
     */
    public static void setThreadVar(String key, Object obj) {
        threadBean().set(key, obj);
    }


   /**
     * 引數BEAN鍵
     */
    public static final String KEY_PARAM_BEAN = "$PARAM_BEAN";

    /**
     * 引數值
     * @param key
     * @return Object
     */
    public static Object paramVar(String key) {
        IBean bean = (IBean) threadVar(KEY_PARAM_BEAN);
        if (bean != null) {
            return bean.get(key);
        } else {
            return null;
        }
    }
}
public class Request {
    /**
	 * 獲取Request例項
	 *
	 * @return Request例項物件
	 */
	public static HttpServletRequest getInst() {
		return (HttpServletRequest) AppMgr.threadVar("request");
	}

	/**
	 * @param request
	 *            void
	 */
	public static void setInst(HttpServletRequest request) {
		AppMgr.setThreadVar("request", request);
	}

	/**
	 * 返回當前請求的Session
	 *
	 * @param create
	 *            沒有有效的Session時,是否建立新Session,不建立返回null
	 * @return Session
	 */
	public static HttpSession getSession(boolean create) {
		return getInst().getSession(create);
	}

	/**
	 * 返回當前請求的Session
	 *
	 * @return Session
	 */
	public static HttpSession getSession() {
		return getInst().getSession();
	}
}

代理執行緒中的HttpServletRequest變數,使程式碼中可以通過靜態方法訪問request

      var namesalt = getNowFormatDate();
		
		var strUrl = Leopard.getContextPath() +
		"/DoMyServlet?className=ExcelPoiAction&methodName=createExcel&btnCode=empdata&forWard=isFile&namesalt="+namesalt+"&func="+_func
	    +"&pbean="+encodeURI(encodeURI(strwhere))+"&btnCode"+empexcel;
		var ifm;
		if (document.getElementById("empexcel_iframe") == undefined) {
			ifm = document.createElement("IFRAME");
			ifm.setAttribute("id", "empexcel_iframe");
			ifm.setAttribute("name", "empexcel_iframe");
			ifm.style.height = "0";
			ifm.style.width = "0";
			ifm.style.display = "block";
			ifm.src = "";
			document.body.appendChild(ifm);
			document.getElementById("empexcel_iframe").attachEvent(
					"onload",
					function() {
						window.frames['empexcel'].document.getElementById("empexcel").click();
					});
		} else {				ifm = document.getElementById("empexcel_iframe");			}
		ifm.src = strUrl; 


public class ExcelPoiAction {
public void createExcel() throws IOException {
HttpServletRequest req = Request.getInst();
this.funcCode = req.getParameter("func");		//功能單元
//this.strWhere = req.getParameter("pbean");		//附件查詢條件
this.strWhere =	java.net.URLDecoder.decode(req.getParameter("pbean"),"utf-8");
if (!StringUtils.isEmpty(strWhere)) {
try {
objWhere = new JSONObject("{" + strWhere + "}");
} catch (JSONException e) {}
}
//獲取業務引數
String busiStr = req.getParameter("busiData");
if(!StringUtils.isEmpty(busiStr)){
try {
this.busiData = JsonUtils.transferToBean(busiStr);
} catch (Exception e) {}
}
}

執行緒共享變數快取如下:

Thread.ThreadLocalMap<ThreadLocalObject>;

1、Thread: 當前執行緒,可以通過Thread.currentThread()獲取。

2、ThreadLocal:我們的static ThreadLocal變數。

3、Object: 當前執行緒共享變數。

我們呼叫ThreadLocal.get方法時,實際上是從當前執行緒中獲取ThreadLocalMap<ThreadLocalObject>,然後根據當前ThreadLocal獲取當前執行緒共享變數Object。

ThreadLocal.set,ThreadLocal.remove實際上是同樣的道理。

這種儲存結構的好處:

1、執行緒死去的時候,執行緒共享變數ThreadLocalMap則銷燬。

2、ThreadLocalMap<ThreadLocal,Object>鍵值對數量為ThreadLocal的數量,一般來說ThreadLocal數量很少,相比在ThreadLocal中用Map<Thread, Object>鍵值對儲存執行緒共享變數(Thread數量一般來說比ThreadLocal數量多),效能提高很多。

關於ThreadLocalMap<ThreadLocalObject>弱引用問題:

當執行緒沒有結束,但是ThreadLocal已經被回收,則可能導致執行緒中存在ThreadLocalMap<nullObject>的鍵值對,造成記憶體洩露。(ThreadLocal被回收,ThreadLocal關聯的執行緒共享變數還存在)。

雖然ThreadLocal的get,set方法可以清除ThreadLocalMap中key為null的value,但是get,set方法在記憶體洩露後並不會必然呼叫,所以為了防止此類情況的出現,我們有兩種手段。

1、使用完執行緒共享變數後,顯示呼叫ThreadLocalMap.remove方法清除執行緒共享變數;

2、JDK建議ThreadLocal定義為private static,這樣ThreadLocal的弱引用問題則不存在了。

原始碼實現

ThreadLocal類提供的幾個方法:


    public T get() {
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null) {
            ThreadLocalMap.Entry e = map.getEntry(this);
            if (e != null) {
                @SuppressWarnings("unchecked")
                T result = (T)e.value;
                return result;
            }
        }
        return setInitialValue();

--------------------- 
 ThreadLocalMap getMap(Thread t) {
        return t.threadLocals;
    }

--------------------- 
ThreadLocal.ThreadLocalMap threadLocals = null;

--------------------- 

static class ThreadLocalMap {
 
        /**
         * The entries in this hash map extend WeakReference, using
         * its main ref field as the key (which is always a
         * ThreadLocal object).  Note that null keys (i.e. entry.get()
         * == null) mean that the key is no longer referenced, so the
         * entry can be expunged from table.  Such entries are referred to
         * as "stale entries" in the code that follows.
         */
        static class Entry extends WeakReference<ThreadLocal<?>> {
            /** The value associated with this ThreadLocal. */
            Object value;
 
            Entry(ThreadLocal<?> k, Object v) {
                super(k);
                value = v;
            }
        }
--------------------- 

    private T setInitialValue() {
        T value = initialValue();
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null)
            map.set(this, value);
        else
            createMap(t, value);
        return value;
    }
--------------------- 
void createMap(Thread t, T firstValue) {
        t.threadLocals = new ThreadLocalMap(this, firstValue);
    }

 

request和response

Servlet 

   說明servlet主要有三個生命週期:

     ①Web伺服器首先檢查是否已經裝載並建立了該Servlet的例項物件。如果是,則直接執行第④步,否則,執行第②步。

  ②裝載並建立該Servlet的一個例項物件,注意這個例項是單例的,所以呼叫後面的service方法的時候可能有併發問題。
  ③呼叫Servlet例項物件的init()方法。
  ④建立一個用於封裝HTTP請求訊息的HttpServletRequest物件和一個代表HTTP響應訊息的HttpServletResponse物件,然後呼叫Servlet的service()方法並將請求和響應物件作為引數傳遞進去。
  ⑤WEB應用程式被停止或重新啟動之前,Servlet引擎將解除安裝Servlet,並在解除安裝之前呼叫Servlet的destroy()方法。

    關於servlet執行緒安全的問題這裡多提一下:

    對於每次訪問請求,Servlet引擎都會建立一個新的HttpServletRequest請求物件和一個新的HttpServletResponse響應物件,然後將這兩個物件作為引數傳遞給它呼叫的Servlet的service()方法,service方法再根據請求方式分別呼叫doXXX方法。只要該Servlet中不存在全域性變數就不存線上程安全問題,因為每個執行緒訪問的時候都會有自己的方法棧,區域性變數是互不影響的。

web.xml

<servlet>  
    <servlet-name>ServletDemo</servlet-name>  
    <servlet-class>com.ServletDemo</servlet-class>  
  </servlet>  
  
  <servlet-mapping>  
    <servlet-name>ServletDemo</servlet-name>  
    <url-pattern>/servlet/ServletDemo</url-pattern>  
  </servlet-mapping>  
protected void service(HttpServletRequest req, HttpServletResponse resp)  
       throws ServletException, IOException  
   {  
       String method = req.getMethod();  
       if(method.equals("GET"))  
       {  
           long lastModified = getLastModified(req);  
           if(lastModified == -1L)  
           {  
               doGet(req, resp);  
           } else  
           {  
               long ifModifiedSince = req.getDateHeader("If-Modified-Since");  
               if(ifModifiedSince < (lastModified / 1000L) * 1000L)  
               {  
                   maybeSetLastModified(resp, lastModified);  
                   doGet(req, resp);  
               } else  
               {  
                   resp.setStatus(304);  
               }  
           }  
       } else  
       if(method.equals("HEAD"))  
       {  
           long lastModified = getLastModified(req);  
           maybeSetLastModified(resp, lastModified);  
           doHead(req, resp);  
       } else  
       if(method.equals("POST"))  
           doPost(req, resp);  
       else  
       if(method.equals("PUT"))  
           doPut(req, resp);  
       else  
       if(method.equals("DELETE"))  
           doDelete(req, resp);  
       else  
       if(method.equals("OPTIONS"))  
           doOptions(req, resp);  
       else  
       if(method.equals("TRACE"))  
       {  
           doTrace(req, resp);  
       } else  
       {  
           String errMsg = lStrings.getString("http.method_not_implemented");  
           Object errArgs[] = new Object[1];  
           errArgs[0] = method;  
           errMsg = MessageFormat.format(errMsg, errArgs);  
           resp.sendError(501, errMsg);  
       }  
   }  

Request

1.獲取請求的基本資訊

1>獲取請求的url和uri

2>獲取url後面的請求引數部分的字串

3>獲取請求方式

4>獲取主機名,IP地址

5>獲取 Contexpath

String url = request.getRequestURL().toString();
System.out.println(url);
String uri = request.getRequestURI().toString();
System.out.println(uri);
String params = request.getQueryString();
System.out.println(params);
String method = request.getMethod();
System.out.println(method);
String addr = request.getRemoteHost() + request.getRemotePort() + request.getRemoteAddr() +
    "==user=" + request.getRemoteUser();
System.out.println("addr: " + addr);
String contextPath = request.getContextPath();
response.sendRedirect(contextPath + "/index.jsp");

獲取請求引數

Map<String, String[]> params = request.getParameterMap();
for (String name : params.keySet()) {
   String value = request.getParameter(name);
   System.out.println("name=" + name + ", value=" + value);
}

解決請求亂碼問題:

request.setCharacterEncoding("Utf-8");

如果上面的程式碼只能解決POST的亂碼問題, 則可以自行進行解碼操作

String userName = request.getParameter("username");
userName =  new String(userName.getBytes("ISO8859-1"), "UTF-8");

前段使用 UTF-8 進行編碼, 傳輸到伺服器, 伺服器可以使用 ISO8859-1 解碼得到UTF-8編碼後的碼值, 然後通過new String(bytes, charset)的方式進行解碼

設定和獲取域屬性

Object attr = request.getAttribute("attr");
request.setAttribute("key", "value");
request.removeAttribute("attr");
Enumeration<String> attributeNames = request.getAttributeNames();

一般我們的應用是servlet處理資料, 將處理好的資料放到request域中,然後帶到jsp頁面上進行展示操作.

請求轉發與請求包含

請求轉發:

request.getRequestDispatcher("/DispatcherTest2").forward(request, response);

this.getServletContext().getRequestDispatcher("/DispatcherTest2").forward(request, response);

1)一次請求只能轉發一次, 否則會發生下面的異常: -- 可以得到第一次轉發獲取的資料
java.lang.IllegalStateException: Cannot forward after response has been committed
2)當有資料已經寫到客戶端時再請求轉發也會丟擲異常.
3)若轉發前有資料寫入到response緩衝區,則請求轉發會清空response緩衝區的實體內容, 但不會清空請求頭資訊.

請求包含:

當需要將多個servlet的輸出合併到一塊打給瀏覽器時可以使用請求包含

request.getRequestDispatcher("/DispatcherTest2").include(request, response);
或
this.getServletContext().getRequestDispatcher("/DispatcherTest2").include(request, response);

    1)被包含的Servlet程式不能改變響應訊息的狀態碼和響應頭,如果它裡面存在這樣的語句,這些語句的執行結果將被忽略.

    2)常被用來進行頁面佈局

response.sendRedirect(request.getContextPath() + "/DispatcherTest2");

1) 不能在資料已經發送到瀏覽器之後再進行請求重定向:
java.lang.IllegalStateException: Cannot call sendRedirect() after the response has been committed

2) 在請求重定向之前寫入到response緩衝區的資料會被清空
3) 一次請求只能重定向一次

請求重定向位址列會發生變化.請求轉發位址列不發生變化.
請求重定向兩次請求兩次響應.請求轉發一次請求一次響應.

如果需要在資源跳轉時利用request域傳遞域屬性則必須使用請求轉發
如果希望資源跳轉後修改使用者的位址列則使用請求重定向

如果使用請求轉發也可以重定向也可以,則優先使用請求轉發,減少瀏覽器對伺服器的訪問次數減輕伺服器的壓力.

 

response
    ServletResponse -- 通用的response提供了一個響應應該具有最基本的屬性和方法
        |
        |-HttpServletResponse -- 在ServletResponse的基礎上針對於HTTP協議增加了很多強化的屬性和方法

2.輸出資料
 1)getOutputStream位元組輸出流

response.getOutputStream().write("中國".getBytes("utf-8"));

string.getBytes()如果沒有指定編碼方式,會使用平臺預設的編碼方式

2)getWriter字元輸出流

response.getWriter().write("北京");

getWriter和getOutputStream在一次請求中只能使用一個
    使用字元輸出流輸出中文時, 由於網線上只能輸出高低電平,如果沒有指定編碼方式,那麼伺服器在傳送資料時會使用預設的ISO-8859-1對資料編碼(該碼錶中沒有漢字, 因此漢字會被編碼為?, 傳送到瀏覽器上的資料實際就是?).

解決亂碼

    1> 通知伺服器傳送資料時使用utf-8編碼

response.setCharacterEncoding("utf-8");

  2> 通知瀏覽器接受資料時使用utf-8解碼

response.setHeader("Content-Type", "text/html;charset=utf-8");

 a. response物件中對Content-Type響應頭進行了封裝,可以使用一下程式碼代替 2>

response.setContentType("text/html;charset=utf-8");

   b. 如果設定了Content-Type,伺服器會自動的設定 characterEncoding,因此解決亂碼只需要設定Content-Type響應頭一行程式碼就可以了,但是為了程式碼的可讀性更高,一般還是建議同時設定 characterEncoding 和 Content-Type.
3)實現下載

protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
    response.setHeader("Content-Disposition", "attachment;filename=" + URLEncoder.encode("美女.jpg"));
    InputStream in = new FileInputStream(this.getServletContext().getRealPath("美女.jpg"));
    OutputStream out = response.getOutputStream();
    byte[] bytes = new byte[1024];
    int len = -1;
    while(-1 != (len = in.read(bytes))) {
       out.write(bytes, 0, len);
    }
    in.close();
}

4) 實現定時重新整理

Servlet實現

response.setContentType("text/html;charset=utf-8");
response.getWriter().write("恭喜您註冊成功, 3秒後回到主頁");
response.setHeader("Refresh", "3;url=OutServlet");

html實現

<meta charset="UTF-8">
<meta http-equiv="Refresh" content="3; url=index.jsp" >
<title>Insert title here</title>
</head>
<body>
    恭喜您註冊成功, 3秒後回到主頁....
</body>

5)控制瀏覽器是否快取

response.setIntHeader("Expires", -1);
response.setHeader("Cache-Control", "no-cache");
response.setHeader("Pragma", "no-cache");
response.getWriter().write(new Date().toLocaleString());

6)實現請求重定向

response.sendRedirect(this.getServletContext().getContextPath());

 

 

import java.util.HashMap;
import java.util.Map;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;

public class ThreadContextHolder
{
  private static ThreadLocal<HttpServletRequest> HttpRequestThreadLocalHolder = new ThreadLocal();
  private static ThreadLocal<HttpServletResponse> HttpResponseThreadLocalHolder = new ThreadLocal();
  private static ThreadLocal<Map<String, Object>> threadVar = new ThreadLocal();
  
  public static void setThreadValue(String key, Object value)
  {
    Map<String, Object> map = (Map)threadVar.get();
    if (map == null)
    {
      map = new HashMap();
      map.put(key, value);
      threadVar.set(map);
    }
    else
    {
      map.put(key, value);
    }
  }
  
  public static <T> T getThreadValue(String key)
  {
    Map<String, Object> map = (Map)threadVar.get();
    if (map != null) {
      return map.get(key);
    }
    return null;
  }
  
  public static void setHttpRequest(HttpServletRequest request)
  {
    HttpRequestThreadLocalHolder.set(request);
  }
  
  public static HttpServletRequest getHttpRequest()
  {
    return (HttpServletRequest)HttpRequestThreadLocalHolder.get();
  }
  
  public static void setHttpResponse(HttpServletResponse response)
  {
    HttpResponseThreadLocalHolder.set(response);
  }
  
  public static HttpServletResponse getHttpResponse()
  {
    return (HttpServletResponse)HttpResponseThreadLocalHolder.get();
  }
  
  public static String getSessionId()
  {
    HttpServletRequest request = (HttpServletRequest)HttpRequestThreadLocalHolder.get();
    if (null != request) {
      return request.getSession().getId();
    }
    return null;
  }
  
  public static void clearThreadValues()
  {
    threadVar.remove();
  }
}

 

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpSession;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

public class HttpSessionService
  implements ISessionService
{
  private final Log logger = LogFactory.getLog(getClass());
  
  public Object getAttribute(String arg0)
  {
    HttpSession session = getSession();
    return session == null ? null : session.getAttribute(arg0);
  }
  
  public String getId()
  {
    HttpSession session = getSession();
    return session == null ? null : session.getId();
  }
  
  public void invalidate()
  {
    HttpSession session = getSession();
    if (session != null) {
      session.invalidate();
    }
  }
  
  public void removeAttribute(String arg0)
  {
    HttpSession session = getSession();
    if (session != null) {
      session.removeAttribute(arg0);
    }
  }
  
  public void setAttribute(String arg0, Object arg1)
  {
    HttpSession session = getSession();
    if (session != null) {
      session.setAttribute(arg0, arg1);
    }
  }
  
  private HttpSession getSession()
  {
    HttpServletRequest request = ThreadContextHolder.getHttpRequest();
    if ((request == null) || (request.getSession() == null)) {
      this.logger.info("============================>>>sessoin 失效");
    }
    return request.getSession();
  }
}

 

 

public class SessionContextHolder
{
  private static ISessionService sessionService;
  
  public static ISessionService getInstance()
  {
    sessionService = (ISessionService)SpringContextHolder.getBean("httpSessionService");
    return sessionService;
  }
}
 <!-- bean 管理器 -->
	<bean id="springContextHolder" class="com.platform.framework.context.SpringContextHolder" lazy-init="false"/>

 

	public List<Map<String, Object>> getOrgUsers(){
		User user = (User) SessionContextHolder.getInstance().getAttribute(LoginConstant.LOGIN_USER_SESSION_KEY);
		return userMapper.selectOrgUsers(user);
	}