【Spring】抽丝剥茧SpringMVC-SessionAttributes机制

源码基于SpringMVC 5.2.7版本

请求进来的时候创建SessionAttributesHandler并将之前的session属性填充到model数据中,请求返回时将model中数据根据注解@SessionAttribute配置保存到SessionAttributesHandler中供下次请求使用。如果请求处理类标记SessionStatus为已完成,则在请求返回时将SessionAttributesHandler清理掉

@SessionAttributes

    @SessionAttributes注解修饰在类上,一般是修饰在处理http请求的类上,为其定义会话级attributes。@SessionAttributes定义的会话级attributes是跟请求Handler的model attributes关联的。处理请求时将@SessionAttributes指定的属性从Session或者其它会话级存储介质中同步到model attributes中;返回响应时将@SessionAttributes指定的model attributes保存到Session或者其它会话级存储介质。

public @interface SessionAttributes {

	@AliasFor("names")
	String[] value() default {};

	@AliasFor("value")
	String[] names() default {};

	Class<?>[] types() default {};

}
  • names数组:String类型数组,指定属性的名字
  • types数组:Class<?>类型数组,指定属性的类型

满足names数组或者types数组的属性都会存储在session或其它会话级存储介质。如果请求Handler明确说明已经完成会话,与该Handler的@SessionAttributes指定的会话级属性将被从Session中清除。所以如果开发者需要永久的会话属性可以直接使用Session,而不是用@SessionAttributes来管理永久的会话属性。

SessionAttributesHandler

SessionAttributesHandler是处理@SessionAttributes的处理器

主要成员变量

  • attributeNames:属性的名字,对应注解@SessionAttributes的names数组
  • attributeTypes:属性的类型,对应注解@SessionAttributes的types数组
  • knownAttributeNames:String数组,当前SessionAttributesHandler已经识别的属性名,其一所有attributeNames中元素属于已识别的属性,其二匹配过attributeTypes的属性名
  • sessionAttributeStore:会话级attributes的存储介质。默认实现类DefaultSessionAttributeStore,DefaultSessionAttributeStore以Session作为存储介质

主要成员方法

storeAttributes

存储属性到会话级存储介质

public void storeAttributes(WebRequest request, Map<String, ?> attributes) {
		attributes.forEach((name, value) -> {
			if (value != null && isHandlerSessionAttribute(name, value.getClass())) {
				this.sessionAttributeStore.storeAttribute(request, name, value);
			}
		});
	}

retrieveAttributes

从存储介质中提取所有已识别的属性

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

cleanupAttributes

从存储介质中清除已识别的属性,当请求的Handler明确说明完成会话,该方法将被调用。

public void cleanupAttributes(WebRequest request) {
		for (String attributeName : this.knownAttributeNames) {
			this.sessionAttributeStore.cleanupAttribute(request, attributeName);
		}
	}

SpringMVC对@SessionAttributes的支持

@SessionAttributes处理流程

先看一下测试demo,提供3个接口,

"/focuse/hello"里面给model添加一个属性("sessionAttr1")。因为@SessionAttributes机制,这个属性将成为session级属性。

"/focuse/hello2"从model中获取属性"sessionAttr1"。

为了简化demo,没有给demo程序配置视图解析器,SpringMVC默认使用InternalResourceView,请求转发到"/focuse/hello3"。

运行的结果将是:我们先请求"/focuse/hello",再请求"/focuse/hello2",在"/focuse/hello2"中能取到属性"sessionAttr1"。

@Controller
@RequestMapping("/focuse")
@SessionAttributes(names = {"sessionAttr1"})
public class DemoController {

    @RequestMapping("hello")
    public String hello(Model model) {
        model.addAttribute("sessionAttr1", "sessionAttr1Val");
        return "/focuse/hello3";
    }

    @RequestMapping("hello2")
    public String hello2(Model model) {
         String sessionAttr1 = (String) model.getAttribute("sessionAttr1");
         return "/focuse/hello3";
    }

    @ResponseBody
    @RequestMapping("hello3")
    public String hello3(Model model) {

        return "hello world!";
    }
}

首先,开发者在请求处理类上添加注解@SessionAttributes

请求处理类一般是@Controller,如demo中DemoController,笔者添加了注解@SessionAttributes,并指定names数组包含元素"sessionAttr1"

其次,RequestMappingHandlerAdapter调用请求处理方法之前初始化该请求处理类的SessionAttributesHandler

这里初始化SessionAttributesHandler主要时根据请求处理类上的注解@SessionAttributes。源码在RequestMappingHandlerAdapter#getModelFactory

    public SessionAttributesHandler(Class<?> handlerType, SessionAttributeStore sessionAttributeStore) {
		Assert.notNull(sessionAttributeStore, "SessionAttributeStore may not be null");
		this.sessionAttributeStore = sessionAttributeStore;

        //笔者注:根据注解@SessionAttributes设置SessionAttributesHandler属性
		SessionAttributes ann = AnnotatedElementUtils.findMergedAnnotation(handlerType, SessionAttributes.class);
		if (ann != null) {
			Collections.addAll(this.attributeNames, ann.names());
			Collections.addAll(this.attributeTypes, ann.types());
		}
		this.knownAttributeNames.addAll(this.attributeNames);
	}

再次,RequestMappingHandlerAdapter初始化model时将SessionAttributesHandler中存储的属性添加进去

这里添加2部署数据到model中,

  • 一种是SessionAttributesHandler存储的属性,
  • 另一种是请求处理方法的注解@ModelAttribute注解的参数且参数类型与@SessionAttributes指定的类型匹配。这种除了添加到model中,同时也会将其标记为session属性并且在接下来的步骤中添加到SessionAttributesHandler中
public final class ModelFactory {
... ...    
    public void initModel(NativeWebRequest request, ModelAndViewContainer container, HandlerMethod handlerMethod)
			throws Exception {

        //笔者注:从请求处理类对应的sessionAttributesHandler取出属性添加到model中
		Map<String, ?> sessionAttributes = this.sessionAttributesHandler.retrieveAttributes(request);
		container.mergeAttributes(sessionAttributes);
		invokeModelAttributeMethods(request, container);

        //笔者注:找到请求处理方法中@ModelAttribute注解的参数,如果参数类型匹配@SessionAttributes设置的类型,也添加到model属性中
		for (String name : findSessionAttributeArguments(handlerMethod)) {
			if (!container.containsAttribute(name)) {
				Object value = this.sessionAttributesHandler.retrieveAttribute(request, name);
				if (value == null) {
					throw new HttpSessionRequiredException("Expected session attribute '" + name + "'", name);
				}
				container.addAttribute(name, value);
			}
		}
	}
    ... ...
}

然后,请求处理完之后获取ModelAndView时根据当前请求处理类的SessionStatus状态去清除SessionAttributes或者去保存SessionAttributes

public class RequestMappingHandlerAdapter extends AbstractHandlerMethodAdapter
		implements BeanFactoryAware, InitializingBean {
... ...
    @Nullable
	private ModelAndView getModelAndView(ModelAndViewContainer mavContainer,
			ModelFactory modelFactory, NativeWebRequest webRequest) throws Exception {
        
        //笔者注:获取ModelAndView对象时更新Model
		modelFactory.updateModel(webRequest, mavContainer);
		if (mavContainer.isRequestHandled()) {
			return null;
		}
    ... ...
    }
    ... ...
}

public final class ModelFactory {
... ...    
    //笔者注:更新model
    public void updateModel(NativeWebRequest request, ModelAndViewContainer container) throws Exception {
		ModelMap defaultModel = container.getDefaultModel();

        //笔者注:根据当前请求类的SessionStatus状态清除或者保存SessionAttributes
		if (container.getSessionStatus().isComplete()){
			this.sessionAttributesHandler.cleanupAttributes(request);
		}
		else {
			this.sessionAttributesHandler.storeAttributes(request, defaultModel);
		}
		if (!container.isRequestHandled() && container.getModel() == defaultModel) {
			updateBindingResult(request, defaultModel);
		}
	}
... ...
}

简单总结一下就是:请求进来的时候创建SessionAttributesHandler并将之前的session属性填充到model数据中,请求返回时将model中数据根据注解@SessionAttribute配置保存到SessionAttributesHandler中供下次请求使用。如果请求处理类标记SessionStatus为已完成,则在请求返回时将SessionAttributesHandler清理掉。

如何设置SessionStatus

前面说了,如果要清除掉@SessionAttributes保持的session属性,可以将请求处理类的SessionStatus设置为完成,那么如何设置?

需要在请求处理方法上注入SessionStatus类型的参数,然后设置SessionStatus#setComplete。那么请求在返回之前就会清除相应的属性。参考下面的demo

@RequestMapping("hello4")
    public String hello4(Model model, SessionStatus status) {
        status.setComplete();

        return "/focuse/hello3";
    }


目录 目录

上一篇 请求间传递参数机制FlashMap

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值