SpringWeb Cannot expose request attribute 'XXX' because of an existing model object of the same name

首页 Java SpringWeb Cannot expose request attribute 'XXX' because of an existing model object of the same name
~~~ 热烈庆祝通天技术网开业大吉 ~~~

发生场景:

导致这个异常只有一种原因,数据模型跟request中的attributes存在同名属性,大致在几种场景下出现: 1. Model和Request对象中有同名的attribute - 比如你在Interceptor中request.setAttribute("key1","niubi"),然后又在Controller中调用model.addAttribute("key1","wocao") 2. PathVariable 和 Model/Request中的attribute同名

解决方法

根据模板引擎添加对应配置即可。

不过本人还是建议能改个属性名就改名吧,不到迫不得已,尽量少一些个性化的配置,日后代码维护或交接的时候会轻松许多。

freemarker

# 指定HttpServletRequest的属性是否可以覆盖controller的model的同名项
spring.freemarker.allow-request-override=true

velocity

# 指定HttpServletRequest的属性是否可以覆盖controller的model的同名项
spring.velocity.allow-request-override=true

groovy

# 指定HttpServletRequest的属性是否可以覆盖controller的model的同名项
spring.groovy.template.allow-request-override=true

追根溯源,揭开神秘面纱

顺着堆栈debug

Java代码:

    @GetMapping("/www.tongtian.icu/{test}")
    public String tongtian_icu(@PathVariable String test,HttpServletRequest request) {
        request.setAttribute("test","123456");
        return "TestTemplate";
    }

堆栈信息:

javax.servlet.ServletException: Cannot expose request attribute 'test' because of an existing model object of the same name
	at org.springframework.web.servlet.view.AbstractTemplateView.renderMergedOutputModel(AbstractTemplateView.java:124) ~[spring-webmvc-5.3.4.jar:5.3.4]
	at org.springframework.web.servlet.view.AbstractView.render(AbstractView.java:316) ~[spring-webmvc-5.3.4.jar:5.3.4]
	at org.springframework.web.servlet.DispatcherServlet.render(DispatcherServlet.java:1393) ~[spring-webmvc-5.3.4.jar:5.3.4]
	at org.springframework.web.servlet.DispatcherServlet.processDispatchResult(DispatcherServlet.java:1138) ~[spring-webmvc-5.3.4.jar:5.3.4]
	at org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:1077) ~[spring-webmvc-5.3.4.jar:5.3.4]
	at org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:962) ~[spring-webmvc-5.3.4.jar:5.3.4]
# 到DispatcherServlet就可以了,后续略...

renderMergedOutputModel 渲染合并后的输出模型

这个方法作用是判断是否公开request中的attributes,如果exposeRequestAttributes为true,会将request中的attributes复制到model中。

第十行可以看到,在exposeRequestAttributes为true,且allowRequestOverride不为true的情况下,会抛出异常,这也就是场景1中出现这种情况的原因,所以我们解决方法中的配置就允许覆盖。

	@Override
	protected final void renderMergedOutputModel(
			Map<String, Object> model, HttpServletRequest request, HttpServletResponse response) throws Exception {

		if (this.exposeRequestAttributes) {
			Map<String, Object> exposed = null;
			for (Enumeration<String> en = request.getAttributeNames(); en.hasMoreElements();) {
				String attribute = 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");
				}
				Object attributeValue = request.getAttribute(attribute);
				if (logger.isDebugEnabled()) {
					exposed = exposed != null ? exposed : new LinkedHashMap<>();
					exposed.put(attribute, attributeValue);
				}
				model.put(attribute, attributeValue);
			}
			if (logger.isTraceEnabled() && exposed != null) {
				logger.trace("Exposed request attributes to model: " + exposed);
			}
		}

render

注释的意思是,将静态属性与requestContext属性合并(17行),再委托renderMergedOutputModel进行实际渲染。

	/**
	 * Prepares the view given the specified model, merging it with static
	 * attributes and a RequestContext attribute, if necessary.
	 * Delegates to renderMergedOutputModel for the actual rendering.
	 * @see #renderMergedOutputModel
	 */
	@Override
	public void render(@Nullable Map<String, ?> model, HttpServletRequest request,
			HttpServletResponse response) throws Exception {

		if (logger.isDebugEnabled()) {
			logger.debug("View " + formatViewName() +
					", model " + (model != null ? model : Collections.emptyMap()) +
					(this.staticAttributes.isEmpty() ? "" : ", static attributes " + this.staticAttributes));
		}

		Map<String, Object> mergedModel = createMergedOutputModel(model, request, response);
		prepareResponse(request, response);
		renderMergedOutputModel(mergedModel, getRequestToExpose(request), response);
	}

createMergedOutputModel

19行可以看到,我们的pathVariable全部都被加入到了mergedModel中,所以再通过request.setAttribude方法设置同名属性,就会在renderMergedOutputModel实际渲染的时候抛出异常。这也就是场景2中抛出异常的原因,针对场景2,我们可以单独设置exposePathVariables为false解决报错。

	/**
	 * Creates a combined output Map (never {@code null}) that includes dynamic values and static attributes.
	 * Dynamic values take precedence over static attributes.
	 */
	protected Map<String, Object> createMergedOutputModel(@Nullable Map<String, ?> model,
			HttpServletRequest request, HttpServletResponse response) {

		@SuppressWarnings("unchecked")
		Map<String, Object> pathVars = (this.exposePathVariables ?
				(Map<String, Object>) request.getAttribute(View.PATH_VARIABLES) : null);

		// Consolidate static and dynamic model attributes.
		int size = this.staticAttributes.size();
		size += (model != null ? model.size() : 0);
		size += (pathVars != null ? pathVars.size() : 0);

		Map<String, Object> mergedModel = CollectionUtils.newLinkedHashMap(size);
		mergedModel.putAll(this.staticAttributes);
		if (pathVars != null) {
			mergedModel.putAll(pathVars);
		}
		if (model != null) {
			mergedModel.putAll(model);
		}

		// Expose RequestContext?
		if (this.requestContextAttribute != null) {
			mergedModel.put(this.requestContextAttribute, createRequestContext(request, response, mergedModel));
		}

		return mergedModel;
	}
资源下载
资源下载

本文没有可供下载资源

点击下载

标题:SpringWeb Cannot expose request attribute 'XXX' because of an existing model object of the same name

分类:Java

链接:https://www.tongtian.icu/content/93

版权:通天技术网(www.tongtian.icu)所分享发布内容,部分为网络转载,如有侵权请立即联系方式,我们第一时间删除并致歉!

评论 (评论区只画了下样式,还没做功能,这里短时间内不会更新,有问题弹射至网站底部工单系统)

电子邮件地址不会被公开。 必填项已用 * 标注

  • 通天技术网
    回复

    热烈庆祝通天技术网开业大吉

  • 通天技术网
    回复

    热烈庆祝通天技术网开业大吉

    相关文章