1. 程式人生 > >springMVC原始碼分析[email protected

springMVC原始碼分析[email protected

@SessionAttribute作用於處理器類上,用於在多個請求之間傳遞引數,類似於Session的Attribute,但不完全一樣,一般來說@SessionAttribute設定的引數只用於暫時的傳遞,而不是長期的儲存,長期儲存的資料還是要放到Session中。

通過@SessionAttribute註解設定的引數有3類用法:

(1)在檢視中通過request.getAttribute或session.getAttribute獲取

(2)在後面請求返回的檢視中通過session.getAttribute或者從model中獲取

(3)自動將引數設定到後面請求所對應處理器的Model型別引數或者有@ModelAttribute註釋的引數裡面。

將一個引數設定到SessionAttribute中需要滿足兩個條件:

(1)在@SessionAttribute註解中設定了引數的名字或者型別

(2)在處理器中將引數設定到了model中。

@SessionAttribute使用者後可以呼叫SessionStatus.setComplete來清除,這個方法只是清除SessionAttribute裡的引數,而不會應用Session中的引數。

示例如下:註解@SessionAttribute中設定book、description和types={Double},這樣值會被放到@SessionAttribute中,但Redirect跳轉時就可以重新獲得這些資料了,接下來操作sessionStatus.setComplete(),則會清除掉所有的資料,這樣再次跳轉時就無法獲取資料了。

@Controller
@RequestMapping("/book")
@SessionAttributes(value ={"book","description"},types={Double.class})
public class RedirectController {
	
	@RequestMapping("/index")
	public String index(Model model){
		model.addAttribute("book", "金剛經");
		model.addAttribute("description","不擦擦擦擦擦擦擦車");
		model.addAttribute("price", new Double("1000.00"));
		//跳轉之前將資料儲存到book、description和price中,因為註解@SessionAttribute中有這幾個引數
		return "redirect:get.action";
	}
	
	@RequestMapping("/get")
	public String get(@ModelAttribute ("book") String book,ModelMap model,
			SessionStatus sessionStatus){
		//可以獲得book、description和price的引數
		System.out.println(model.get("book")+";"+model.get("description")+";"+model.get("price"));
		sessionStatus.setComplete();
		return "redirect:complete.action";
	}
	
	@RequestMapping("/complete")
	public String complete(ModelMap modelMap){
		//已經被清除,無法獲取book的值
		System.out.println(modelMap.get("book"));
		modelMap.addAttribute("book", "妹紙");
		return "sessionAttribute";
	}

}
接下來我們分析一下@SessionAttribute的實現機制

第一步:我們首先要獲取註解@SessionAttribute的值的情況,在RequestMappingHandlerAdapter中的getModelFactory中處理。

protected ModelAndView invokeHandlerMethod(HttpServletRequest request,
			HttpServletResponse response, HandlerMethod handlerMethod) throws Exception {

		//這裡面對註解的@SessionAttribute的處理器做處理
		ModelFactory modelFactory = getModelFactory(handlerMethod, binderFactory);

		........
		//會對@SessionAttribute操作的值進行處理
		modelFactory.initModel(webRequest, mavContainer, invocableMethod);
		........

		return getModelAndView(mavContainer, modelFactory, webRequest);
	}
在getModelFactory中會建立@SessionAttribute的處理器SessionAttributeHandler
	
	private ModelFactory getModelFactory(HandlerMethod handlerMethod, WebDataBinderFactory binderFactory) {
		//建立SessionAttribute處理器
		SessionAttributesHandler sessionAttrHandler = getSessionAttributesHandler(handlerMethod);
		
		return new ModelFactory(attrMethods, binderFactory, sessionAttrHandler);
	}

getSessionAttributesHandler的操作就是獲取或者初始化SessionAttributesHandler

//已經獲取過的SessionAttribute放到Map中,如果沒有則需要初始化
	private SessionAttributesHandler getSessionAttributesHandler(HandlerMethod handlerMethod) {
		Class<?> handlerType = handlerMethod.getBeanType();
		SessionAttributesHandler sessionAttrHandler = this.sessionAttributesHandlerCache.get(handlerType);
		if (sessionAttrHandler == null) {
			synchronized (this.sessionAttributesHandlerCache) {
				sessionAttrHandler = this.sessionAttributesHandlerCache.get(handlerType);
				if (sessionAttrHandler == null) {
					//初始化sessionAttrHandler,並放到map中
					sessionAttrHandler = new SessionAttributesHandler(handlerType, sessionAttributeStore);
					this.sessionAttributesHandlerCache.put(handlerType, sessionAttrHandler);
				}
			}
		}
		return sessionAttrHandler;
	}

SessionAttributesHandler的建構函式中的操作如下,其實就是解析被@SessionAttribute註解的處理器,這樣就完成了@SessionAttribute註解中設定的key的解析。
public SessionAttributesHandler(Class<?> handlerType, SessionAttributeStore sessionAttributeStore) {
		Assert.notNull(sessionAttributeStore, "SessionAttributeStore may not be null.");
		this.sessionAttributeStore = sessionAttributeStore;
		//解析被@SessionAttribute註解的處理器
		SessionAttributes annotation = AnnotationUtils.findAnnotation(handlerType, SessionAttributes.class);
		if (annotation != null) {
			this.attributeNames.addAll(Arrays.asList(annotation.names()));
			this.attributeTypes.addAll(Arrays.asList(annotation.types()));
		}

		for (String attributeName : this.attributeNames) {
			this.knownAttributeNames.add(attributeName);
		}
	}

接下來我們看看springMVC對@SessionAttribute的處理操作,在ModelFactory.initModel會對@SessionAttribute的註解進行處理操作。

protected ModelAndView invokeHandlerMethod(HttpServletRequest request,
			HttpServletResponse response, HandlerMethod handlerMethod) throws Exception {

		//這裡面對註解的@SessionAttribute的處理器做處理
		ModelFactory modelFactory = getModelFactory(handlerMethod, binderFactory);

		........
		//會對@SessionAttribute操作的值進行處理
		modelFactory.initModel(webRequest, mavContainer, invocableMethod);
		........

		return getModelAndView(mavContainer, modelFactory, webRequest);
	}

initModel其實做了兩步操作,一是:獲取上一次請求儲存在SessionAttributeHandler中值,給這次請求的值用,二是將這次請求的處理結果可能會對上次的@SessionAttribute中的值進行改變的值進行儲存給下一次請求使用。
public void initModel(NativeWebRequest request, ModelAndViewContainer mavContainer, HandlerMethod handlerMethod)
			throws Exception {
		//獲取所有的@SessionAttribute註解設定的key中值
		Map<String, ?> sessionAttributes = this.sessionAttributesHandler.retrieveAttributes(request);
		//將獲取的值傳遞給下一個請求使用
		mavContainer.mergeAttributes(sessionAttributes);

		invokeModelAttributeMethods(request, mavContainer);
		
		//請求訪問完之後將修改的值重新放到@SessionAttributeStore設定的key中
		for (String name : findSessionAttributeArguments(handlerMethod)) {
			if (!mavContainer.containsAttribute(name)) {
				Object value = this.sessionAttributesHandler.retrieveAttribute(request, name);
				if (value == null) {
					throw new HttpSessionRequiredException("Expected session attribute '" + name + "'");
				}
				mavContainer.addAttribute(name, value);
			}
		}
	}
sessionAttributesHandler.retrieveAttributes的操作是將request中值,按照註解@SessionAttribute中key取值處理,然後儲存到attribute中,作為這次請求的傳遞值使用。
public Map<String, Object> retrieveAttributes(WebRequest request) {
		Map<String, Object> attributes = new HashMap<String, Object>();
		//獲取註解@SessionAttribute中設定的key
		for (String name : this.knownAttributeNames) {
			//如果設定的key有值則把它儲存到attribute中,給跳轉之後的請求使用
			Object value = this.sessionAttributeStore.retrieveAttribute(request, name);
			if (value != null) {
				attributes.put(name, value);
			}
		}
		return attributes;
	}
這樣就完成了將值儲存在SessionAttributeHandler中,這樣下一次請求過來時依然可以從SessionAttributeHandler中獲取上次的結果,完成了類似Session的實現機制,但明顯感覺和Session不一樣,所有的請求其值是儲存在一個同一個SessionAttributeHandler中。


SessionAttributeHandler其實維持了一個Map結構來存取資料,功能主要有解析@SessionAttribute註解,get和set相關值,原始碼如下:

public class SessionAttributesHandler {

	private final Set<String> attributeNames = new HashSet<String>();

	private final Set<Class<?>> attributeTypes = new HashSet<Class<?>>();

	private final Set<String> knownAttributeNames =
			Collections.newSetFromMap(new ConcurrentHashMap<String, Boolean>(4));

	private final SessionAttributeStore sessionAttributeStore;


	//建構函式,解析@SessionAttribute註解,將其設定額key等資訊儲存
	public SessionAttributesHandler(Class<?> handlerType, SessionAttributeStore sessionAttributeStore) {
		Assert.notNull(sessionAttributeStore, "SessionAttributeStore may not be null.");
		this.sessionAttributeStore = sessionAttributeStore;

		SessionAttributes annotation = AnnotationUtils.findAnnotation(handlerType, SessionAttributes.class);
		if (annotation != null) {
			this.attributeNames.addAll(Arrays.asList(annotation.names()));
			this.attributeTypes.addAll(Arrays.asList(annotation.types()));
		}

		for (String attributeName : this.attributeNames) {
			this.knownAttributeNames.add(attributeName);
		}
	}

	
	public boolean hasSessionAttributes() {
		return ((this.attributeNames.size() > 0) || (this.attributeTypes.size() > 0));
	}

	//判斷型別
	public boolean isHandlerSessionAttribute(String attributeName, Class<?> attributeType) {
		Assert.notNull(attributeName, "Attribute name must not be null");
		if (this.attributeNames.contains(attributeName) || this.attributeTypes.contains(attributeType)) {
			this.knownAttributeNames.add(attributeName);
			return true;
		}
		else {
			return false;
		}
	}

	//儲存
	public void storeAttributes(WebRequest request, Map<String, ?> attributes) {
		for (String name : attributes.keySet()) {
			Object value = attributes.get(name);
			Class<?> attrType = (value != null) ? value.getClass() : null;

			if (isHandlerSessionAttribute(name, attrType)) {
				this.sessionAttributeStore.storeAttribute(request, name, value);
			}
		}
	}

	//獲取
	public Map<String, Object> retrieveAttributes(WebRequest request) {
		Map<String, Object> attributes = new HashMap<String, Object>();
		for (String name : this.knownAttributeNames) {
			Object value = this.sessionAttributeStore.retrieveAttribute(request, name);
			if (value != null) {
				attributes.put(name, value);
			}
		}
		return attributes;
	}

	//清除所有內容
	public void cleanupAttributes(WebRequest request) {
		for (String attributeName : this.knownAttributeNames) {
			this.sessionAttributeStore.cleanupAttribute(request, attributeName);
		}
	}

	//獲取所有值
	Object retrieveAttribute(WebRequest request, String attributeName) {
		return this.sessionAttributeStore.retrieveAttribute(request, attributeName);
	}

}