SpringMVC@SessionAttributes使⽤详解以及源码分析
@sessionattributes
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
public @interface SessionAttributes {
String[] value() default {};
Class[] types() default {};
}
@sessionattributes注解应⽤到Controller上⾯,可以将Model中的属性同步到session当中。
先看⼀个最基本的⽅法
@Controller
@RequestMapping("/Demo.do")
@SessionAttributes(value={"attr1","attr2"})
public class Demo {
@RequestMapping(params="method=index")
public ModelAndView index() {
ModelAndView mav = new ModelAndView("index.jsp");
mav.addObject("attr1", "attr1Value");
mav.addObject("attr2", "attr2Value");
return mav;
}
@RequestMapping(params="method=index2")
public ModelAndView index2(@ModelAttribute("attr1")String attr1, @ModelAttribute("attr2")String attr2) {
ModelAndView mav = new ModelAndView("success.jsp");
return mav;
}
}
index⽅法返回⼀个ModelAndView 其中包括视图index.jsp 和两个键值放⼊model当中,在没有加⼊
@sessionattributes注解的时候,放⼊model当中的键值是request级别的。
现在因为在Controller上⾯标记了@SessionAttributes(value={"attr1","attr2"}) 那么model中的attr1,attr2会同步到session中,这样当你访问index 然后在去访问index2的时候也会获取这俩个属性的值。
当需要清除session当中的值得时候,我们只需要在controller的⽅法中传⼊⼀个SessionStatus的类型对象通过调⽤setComplete⽅法就可以清除了。
@RequestMapping(params="method=index3")
public ModelAndView index4(SessionStatus status) {
ModelAndView mav = new ModelAndView("success.jsp");
status.setComplete();
return mav;
}
下⾯就直接分析代码来看Spring是如可封装的。
⾸先我们需要看2个类 DefaultSessionAttributeStore和SessionAttributesHandler DefaultSessionAttributeStore这个类是⽤来往WebRequest存取数据的⼯具类,WebRequest是Spring包装的HttpServletRequest,⼤家理解为普通的HttpServletRequest就⾏了。
public class DefaultSessionAttributeStore implements SessionAttributeStore {
private String attributeNamePrefix = "";
public void setAttributeNamePrefix(String attributeNamePrefix) {springmvc考试选择题
this.attributeNamePrefix = (attributeNamePrefix != null ? attributeNamePrefix : "");
}
public void storeAttribute(WebRequest request, String attributeName, Object attributeValue) {
String storeAttributeName = getAttributeNameInSession(request, attributeName);
request.setAttribute(storeAttributeName, attributeValue, WebRequest.SCOPE_SESSION);
}
public Object retrieveAttribute(WebRequest request, String attributeName) {
String storeAttributeName = getAttributeNameInSession(request, attributeName);
Attribute(storeAttributeName, WebRequest.SCOPE_SESSION);
}
public void cleanupAttribute(WebRequest request, String attributeName) {
String storeAttributeName = getAttributeNameInSession(request, attributeName);
}
protected String getAttributeNameInSession(WebRequest request, String attributeName) {
return this.attributeNamePrefix + attributeName;
}
}
Spring会为每⼀个Controller初始化⼀个SessionAttributesHandler实例,⽤来记录@SessionAttributes(value= {"attr1","attr2"})⾥⾯的属性信息,当需要同步model的值时,会先判断是否在SessionAttributes当中定义。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.synchronizedSet(new HashSet<String>(4)
);
private final SessionAttributeStore sessionAttributeStore;
public SessionAttributesHandler(Class<?> handlerType, SessionAttributeStore sessionAttributeStore) {
this.sessionAttributeStore = sessionAttributeStore;
SessionAttributes annotation = AnnotationUtils.findAnnotation(handlerType, SessionAttributes.class);
if (annotation != null) {
this.attributeNames.addAll(Arrays.asList(annotation.value()));
this.attributeTypes.addAll(Arrays.<Class<?>>pes()));
}
this.knownAttributeNames.addAll(this.attributeNames);
}
public boolean hasSessionAttributes() {
return ((this.attributeNames.size() > 0) || (this.attributeTypes.size() > 0));
}
public boolean isHandlerSessionAttribute(String attributeName, Class<?> attributeType) {
if (ains(attributeName) || ains(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 = (name);
Class<?> attrType = (value != null) ? Class() : 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 = ieveAttribute(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 ieveAttribute(request, attributeName);
}
}
当我们访问controller中的⼀个⽅法时,会调⽤RequestMappingHandlerAdapter类当中的invokeHandleMethod的⽅法。
private ModelAndView invokeHandleMethod(HttpServletRequest request,
HttpServletResponse response, HandlerMethod handlerMethod) throws Exception {
ServletWebRequest webRequest = new ServletWebRequest(request, response);
WebDataBinderFactory binderFactory = getDataBinderFactory(handlerMethod);
ModelFactory modelFactory = getModelFactory(handlerMethod, binderFactory);
ServletInvocableHandlerMethod requestMappingMethod = createRequestMappingMethod(handlerMethod, binderFactory);
ModelAndViewContainer mavContainer = new ModelAndViewContainer();
mavContainer.InputFlashMap(request));
modelFactory.initModel(webRequest, mavContainer, requestMappingMethod);
mavContainer.setIgnoreDefaultModelOnRedirect(this.ignoreDefaultModelOnRedirect);
AsyncWebRequest asyncWebRequest = ateAsyncWebRequest(request, response);
asyncWebRequest.setTimeout(this.asyncRequestTimeout);
final WebAsyncManager asyncManager = AsyncManager(request);
asyncManager.setTaskExecutor(this.taskExecutor);
asyncManager.setAsyncWebRequest(asyncWebRequest);
if (asyncManager.hasConcurrentResult()) {
Object result = ConcurrentResult();
mavContainer = (ModelAndViewContainer) ConcurrentResultContext()[0];
asyncManager.clearConcurrentResult();
if (logger.isDebugEnabled()) {
logger.debug("Found concurrent result value [" + result + "]");
}
requestMappingMethod = requestMappingMethod.wrapConcurrentResult(result);
}
requestMappingMethod.invokeAndHandle(webRequest, mavContainer);
if (asyncManager.isConcurrentHandlingStarted()) {
return null;
}
return getModelAndView(mavContainer, modelFactory, webRequest);
}
View Code
我们看这⾏代码
modelFactory.initModel(webRequest, mavContainer, requestMappingMethod);
跟进去看源码
public void initModel(NativeWebRequest request, ModelAndViewContainer mavContainer, HandlerMethod handlerMethod)
throws Exception {
Map<String, ?> attributesInSession = ieveAttributes(request);
invokeModelAttributeMethods(request, mavContainer);
for (String name : findSessionAttributeArguments(handlerMethod)) {
if (!ainsAttribute(name)) {
Object value = ieveAttribute(request, name);
if (value == null) {
throw new HttpSessionRequiredException("Expected session attribute '" + name + "'");
}
mavContainer.addAttribute(name, value);
}
}
}
View Code
其中这两句是关键的地⽅,把session的值取出来全部放⼊到ModelAndViewContainer当中去,这样我就可以再controller当中的⽅法中获取了,当然直接从session中拿也是可以的。
Map<String, ?> attributesInSession = ieveAttributes(request); Attributes(attributesInSession);
在controller中获取sessionAttributes的只有两种⽅式。
⼀、public ModelAndView index2(@ModelAttribute("attr1")String attr1, @ModelAttribute("attr2")String attr2)
⼆、public ModelAndView index2(ModelMap map)
这俩种⽅式的区别是,如果使⽤@ModelAttribute属性获取值,并且@SessionAttributes注解当中还设置了该属性,当属性为null时会跑出异常,因为这⼏⾏代码。
for (String name : findSessionAttributeArguments(handlerMethod)) {
if (!ainsAttribute(name)) {
Object value = ieveAttribute(request, name);
if (value == null) {
throw new HttpSessionRequiredException("Expected session attribute '" + name + "'");
}
mavContainer.addAttribute(name, value);
}
}
以上分析的都是取值的过程,那么Spring是如何将Model中的数据同步到session当中的呢。
我们看上⾯invokeHandleMethod⽅法中最后⼀句话 return getModelAndView(mavContainer, modelFactory, webRequest); 我们跟进去看源码。
private ModelAndView getModelAndView(ModelAndViewContainer mavContainer,
ModelFactory modelFactory, NativeWebRequest webRequest) throws Exception {
modelFactory.updateModel(webRequest, mavContainer);
if (mavContainer.isRequestHandled()) {
return null;
}
ModelMap model = Model();
ModelAndView mav = new ViewName(), model);
if (!mavContainer.isViewReference()) {
mav.setView((View) View());
}
if (model instanceof RedirectAttributes) {
Map<String, ?> flashAttributes = ((RedirectAttributes) model).getFlashAttributes();
HttpServletRequest request = NativeRequest(HttpServletRequest.class);
}
return mav;
}
View Code
有⼀⾏代码是modelFactory.updateModel(webRequest, mavContainer); 我们在跟进去看源码。
我们看到⾸先判断⼀下ModelAndViewContainer中的SessionStatus是否是完成状态,如果是TRUE就清除session中的值,反之将ModelAndViewContainer中的model值设置进去。
public void updateModel(NativeWebRequest request, ModelAndViewContainer mavContainer) throws Exception {
if (SessionStatus().isComplete()){
this.sessionAttributesHandler.cleanupAttributes(request);
}
else {
this.sessionAttributesHandler.storeAttributes(request, Model());
}
if (!mavContainer.isRequestHandled()) {
updateBindingResult(request, Model());
}
}
View Code
看到这⾥可能还有⼀个疑问,就是我们的数据时设置到ModelAndView⾥⾯的,那么这些数据时如何跑到ModelAndViewContainer当中去的呢,其实是在⽅法执⾏完成之后Spring帮我们做的,
具体细节查看 ModelAndViewMethodReturnValueHandler⽅法中的最后⼀⾏代码
mavContainer.Model());
public void handleReturnValue(
Object returnValue, MethodParameter returnType,
ModelAndViewContainer mavContainer, NativeWebRequest webRequest)
throws Exception {
if (returnValue == null) {
mavContainer.setRequestHandled(true);
return;
}
ModelAndView mav = (ModelAndView) returnValue;
if (mav.isReference()) {
String viewName = ViewName();
mavContainer.setViewName(viewName);
if (viewName != null && viewName.startsWith("redirect:")) {
mavContainer.setRedirectModelScenario(true);
}
}
else {
View view = View();
mavContainer.setView(view);
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。
发表评论