出错现象
近期在维护一个使用vm作为页面视图的项目时,遇到过这样一个错误。
org.apache.catalina.core.StandardWrapperValve.invoke Servlet.service() for servlet [springServlet] in context with path [/portal-web] threw exception
[Cannot expose request attribute ‘attr’ because of an existing model object of the same name] with root cause javax.servlet.ServletException: Cannot expose request attribute ‘processInst’ because of an existing model object of the same name
根据字面意思,Spring试图往实现了Model接口的对象(例如BinddingAwareModelMap,继承关系:–> ExtendedModelMap –> ModelMap ,实现Model接口)里放入request对象的一个attribute时,遇到了已经重名的attribute,因此报该错误。
但是根据我我们以往编码往Model里面放装入属性的经验来看,是能够自动覆盖同名属性的。
原因初步分析
根据SpringMVC的文档,如果要把request和session中的attribute放到model里面让前端使用,可以使用在VelocityLayoutViewResolver中配置下面的属性来实现。
<property name="exposeRequestAttributes" value="true" />
<property name="exposeSessionAttributes" value="true" />
但是在DEBUG项目的时候,又发现在通用的视图控制器里面有这么一段
private void buildModelMap(HttpServletRequest request,ModelMap modelMap){
Enumeration<String> paramNames = request.getParameterNames();
while (paramNames.hasMoreElements()) {
String paramName = paramNames.nextElement();
String[] paramValues = request.getParameterValues(paramName);
if (paramValues.length == 1) {
String paramValue = paramValues[0];
if (paramValue.length() != 0) {
modelMap.put(paramName, paramValue);
}
}
}
}
很明显能看出这段代码所做的工作就是把request里面的属性放到model里面。在执行完这么一段之后,SpringMVC的视图解析器再将request或者session的信息往model里面put的时候就会报key重复的错误。
解决方案
解决方案是在springMVC的视图解析器里面加上配置告诉解析器可以允许属性覆写。
<property name="allowSessionOverride" value="true"/>
<property name="allowSessionOverride" value="true"/>
源码级别分析
虽然有解决方案,但是为什么再次往model里面放置属性不是直接覆盖呢?VelocityLayoutViewResolver给我们提供的exposeRequestAttributes属性在什么地方起作用?这些问题还没有得到解决。因此我们还需要到Spring的源码里面看看是怎么回事。
通过搜索关键字很容易看到AbstractTemplateView类里面有这么一段
protected final void renderMergedOutputModel(Map<String, Object> model, HttpServletRequest request, HttpServletResponse response) throws Exception {
String attribute;
Object attributeValue;
if (this.exposeRequestAttributes) {
for(Enumeration en = request.getAttributeNames(); en.hasMoreElements(); model.put(attribute, attributeValue)) {
attribute = (String)en.nextElement();
if (model.containsKey(attribute) && !this.allowRequestOverride) {
throw new ServletException("Cannot expose request attribute '" + attribute + "' because of an existing model object of the same name");
}
attributeValue = request.getAttribute(attribute);
if (this.logger.isDebugEnabled()) {
this.logger.debug("Exposing request attribute '" + attribute + "' with value [" + attributeValue + "] to model");
}
}
}
当model里面已经有这个attribute,然后allowRequestOverride又为false(默认值)的话,就抛出ServletException。这就是问题关键所在。