一、背景说明
在预发布环境中进行功能验证时,点击“我的反馈”菜单时出现Cannot expose request attribute 'userName' because of an existing model object of the same name 错误,在测试环境正常显示。
二、认识异常
11-Jun-2019 16:43:57.429 SEVERE org.apache.catalina.core.StandardWrapperValve.invoke Servlet.service() for servlet [springServlet] in context with path [] threw exception [Cannot expose request attribute 'userName' because of an existing model object of the same name] with root cause
javax.servlet.ServletException: Cannot expose request attribute 'userName' because of an existing model object of the same name
at org.springframework.web.servlet.view.AbstractTemplateView.renderMergedOutputModel(AbstractTemplateView.java:123)
at org.springframework.web.servlet.view.AbstractView.render(AbstractView.java:303)
at org.springframework.web.servlet.DispatcherServlet.render(DispatcherServlet.java:1257)
at org.springframework.web.servlet.DispatcherServlet.processDispatchResult(DispatcherServlet.java:1037)
at org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:980)
at org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:897)
at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:970)
at org.springframework.web.servlet.FrameworkServlet.doGet(FrameworkServlet.java:861)
at javax.servlet.http.HttpServlet.service(HttpServlet.java:622)
at org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:846)
at javax.servlet.http.HttpServlet.service(HttpServlet.java:729)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:291)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:206)
at org.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:52)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:239)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:206)
at org.springframework.web.filter.CharacterEncodingFilter.doFilterInternal(CharacterEncodingFilter.java:197)
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:239)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:206)
at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:212)
at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:106)
at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:502)
at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:141)
at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:79)
at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:88)
at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:521)
at org.apache.coyote.http11.AbstractHttp11Processor.process(AbstractHttp11Processor.java:1096)
at org.apache.coyote.AbstractProtocol$AbstractConnectionHandler.process(AbstractProtocol.java:674)
at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1500)
at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.run(NioEndpoint.java:1456)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617)
at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61)
at java.lang.Thread.run(Thread.java:745)
错误异常说明:request中存在同名的userName属性,获取失败
注:该错误会在Tomcat的server/logs/localhost.log日志中展示
三、排查问题
1、在LoginInteceptor中通过 request.setAttribute 方式添加了userName属性
public class LoginInteceptor implements HandlerInterceptor{
@Override
boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
throws Exception{
/**......**/
request.setAttribute("userName",'test');
/**......**/
}
}
2、同时在Controller中通过modelAndView.addObject 方式添加了userName属性
@RequestMapping("/myCall")
public ModelAndView myCall(ModelAndView modelAndView){
modelAndView.setViewName("my/call");
modelAndView.addObject("userName","test");
return modelAndView ;
}
3、同时在Spring MVC的viewResolver解析器中配置了exposeRequestAttributes和exposeSessionAttributes值为ture
<bean id="viewResolver" class="org.springframework.web.servlet.view.velocity.VelocityLayoutViewResolver">
/**......**/
<property name="exposeRequestAttributes" value="true"/>
<property name="exposeSessionAttributes" value="true"/>
/**......**/
</bean>
exposeRequestAttributes 和 exposeSessionAttributes 设置为true的话会将request和session中的键值和值合并到modelAndView的Map参数中。
通过以上三项最终导致问题的产生
四、解决问题
方案一:修改Controller中参数名称userName,改为modelAndView.addObject("myUserName","test"),简单粗暴
方案二:在Spring MVC的viewResolver解析器配置中添加allowRequestOverride=true和allowSessionOverride=true配置,允许request和session设置的键值被覆盖,具体配置如下所示:
<bean id="viewResolver" class="org.springframework.web.servlet.view.velocity.VelocityLayoutViewResolver">
/**......**/
<property name="exposeRequestAttributes" value="true"/>
<property name="allowRequestOverride" value="true"/>
<property name="exposeSessionAttributes" value="true"/>
<property name="allowSessionOverride" value="true"/>
/**......**/
</bean>
注:两种方案都可以解决Cannot expose request attribute 'userName' because of an existing model object of the same name 问题