【SpringMVC(十七)】静态资源

32 篇文章 15 订阅
30 篇文章 5 订阅

在SpringMVC中,如果对静态资源不做特殊处理,请求的path找不到会返回404.

原因是,我们在web.xml中会这样配置dispatcherServlet的url-pattern,至于为什么是/,参见https://blog.csdn.net/u010900754/article/details/79998379

  <servlet-mapping>
    <servlet-name>springmvc</servlet-name>
    <url-pattern>/</url-pattern>
  </servlet-mapping>

那么,像/xxx/a.jpg这样的请求也会被dispatcherServlet处理,但是由于这是静态资源,没有对应的controller,所以会报404。

解决办法有两个:

1.在servlet的配置文件中加入这个配置<mvc:default-servlet-handler/>

对于mvc前缀的配置项,springMVC中有如下parser来解析:

public class MvcNamespaceHandler extends NamespaceHandlerSupport {

	@Override
	public void init() {
		registerBeanDefinitionParser("annotation-driven", new AnnotationDrivenBeanDefinitionParser());
		registerBeanDefinitionParser("default-servlet-handler", new DefaultServletHandlerBeanDefinitionParser());
		registerBeanDefinitionParser("interceptors", new InterceptorsBeanDefinitionParser());
		registerBeanDefinitionParser("resources", new ResourcesBeanDefinitionParser());
		registerBeanDefinitionParser("view-controller", new ViewControllerBeanDefinitionParser());
		registerBeanDefinitionParser("redirect-view-controller", new ViewControllerBeanDefinitionParser());
		registerBeanDefinitionParser("status-controller", new ViewControllerBeanDefinitionParser());
		registerBeanDefinitionParser("view-resolvers", new ViewResolversBeanDefinitionParser());
		registerBeanDefinitionParser("tiles-configurer", new TilesConfigurerBeanDefinitionParser());
		registerBeanDefinitionParser("freemarker-configurer", new FreeMarkerConfigurerBeanDefinitionParser());
		registerBeanDefinitionParser("velocity-configurer", new VelocityConfigurerBeanDefinitionParser());
		registerBeanDefinitionParser("groovy-configurer", new GroovyMarkupConfigurerBeanDefinitionParser());
	}

}

最终会由DefaultServletHandlerBeanDefinitionParser来解析处理:

String defaultServletName = element.getAttribute("default-servlet-name");
RootBeanDefinition defaultServletHandlerDef = new RootBeanDefinition(DefaultServletHttpRequestHandler.class);
defaultServletHandlerDef.setSource(source);
defaultServletHandlerDef.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
if (StringUtils.hasText(defaultServletName)) {
	defaultServletHandlerDef.getPropertyValues().add("defaultServletName", defaultServletName);
}
String defaultServletHandlerName = parserContext.getReaderContext().generateBeanName(defaultServletHandlerDef);
parserContext.getRegistry().registerBeanDefinition(defaultServletHandlerName, defaultServletHandlerDef);
parserContext.registerComponent(new BeanComponentDefinition(defaultServletHandlerDef, defaultServletHandlerName));

这段代码会注册一个DefaultServletHttpRequestHandler类型的bean,该bean本身是一个HttpRequestHandler接口的实例,是一种控制器类型。接着我们看下这个控制器是如何处理请求的:

@Override
public void handleRequest(HttpServletRequest request, HttpServletResponse response)
		throws ServletException, IOException {

	RequestDispatcher rd = this.servletContext.getNamedDispatcher(this.defaultServletName);
	if (rd == null) {
		throw new IllegalStateException("A RequestDispatcher could not be located for the default servlet '" +
				this.defaultServletName +"'");
	}
	rd.forward(request, response);
}

可以看到,这里构造了一个RequestDispatcher实例,借助了Servlet的能力,将当前请求通过forward方式转发给了defaultServlet来处理。defaultServlet是tomcat中一个特殊的servlet,专门用于处理静态资源的返回,其内部实现就是根据request的path取到对应的静态资源并返回,其源码这里就不展开了。

再回到DefaultServletHandlerBeanDefinitionParser中:

Map<String, String> urlMap = new ManagedMap<String, String>();
urlMap.put("/**", defaultServletHandlerName);

RootBeanDefinition handlerMappingDef = new RootBeanDefinition(SimpleUrlHandlerMapping.class);
handlerMappingDef.setSource(source);
handlerMappingDef.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
handlerMappingDef.getPropertyValues().add("urlMap", urlMap);

String handlerMappingBeanName = parserContext.getReaderContext().generateBeanName(handlerMappingDef);
parserContext.getRegistry().registerBeanDefinition(handlerMappingBeanName, handlerMappingDef);
parserContext.registerComponent(new BeanComponentDefinition(handlerMappingDef, handlerMappingBeanName));

接着又构造了一个SimpleUrlHandlerMapping的bean,该bean是HandlerMapping的实例,是一种控制器的匹配器。其路径是/**,也就是任意路径。该mapping的bean会在dispatcherServlet中被调用,匹配到之前的DefaultServletHttpRequestHandler来处理当前请求。

 

2.在servlet的配置文件中加入这个配置<mvc:resources mapping="/static/**/" location="/static/"></mvc:resources>

这里的mapping代表请求路径,location代表静态资源目录。

该标签也是mvc前缀,从之前的配置类中可以看到,是由ResourcesBeanDefinitionParser类来解析的。

说先会调用registerResourceHandler方法来生成特定的Handler:

String locationAttr = element.getAttribute("location");
if (!StringUtils.hasText(locationAttr)) {
	parserContext.getReaderContext().error("The 'location' attribute is required.", parserContext.extractSource(element));
	return null;
}

ManagedList<String> locations = new ManagedList<String>();
locations.addAll(Arrays.asList(StringUtils.commaDelimitedListToStringArray(locationAttr)));

RootBeanDefinition resourceHandlerDef = new RootBeanDefinition(ResourceHttpRequestHandler.class);
resourceHandlerDef.setSource(source);
resourceHandlerDef.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
resourceHandlerDef.getPropertyValues().add("locations", locations);

这里将xml标签中的location属性取出,然后构造了一个ResourceHttpRequestHandler类型的控制器,该控制器也是实现自HttpRequestHandler接口的,并将location属性赋值。

Map<String, String> urlMap = new ManagedMap<String, String>();
String resourceRequestPath = element.getAttribute("mapping");
if (!StringUtils.hasText(resourceRequestPath)) {
	parserContext.getReaderContext().error("The 'mapping' attribute is required.", parserContext.extractSource(element));
	return null;
}
urlMap.put(resourceRequestPath, resourceHandlerName);

RuntimeBeanReference pathMatcherRef = MvcNamespaceUtils.registerPathMatcher(null, parserContext, source);
RuntimeBeanReference pathHelperRef = MvcNamespaceUtils.registerUrlPathHelper(null, parserContext, source);

RootBeanDefinition handlerMappingDef = new RootBeanDefinition(SimpleUrlHandlerMapping.class);
handlerMappingDef.setSource(source);
handlerMappingDef.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
handlerMappingDef.getPropertyValues().add("urlMap", urlMap);

接着,仍是构建了一个SinleUrlHandlerMapping类型的匹配器,并且将xml标签的mapping属性赋值到urlMap中,也就是request的path能被mapping属性匹配到,那么就会被之前的控制器处理。

再看下ResourceHttpRequestHandler这个控制器是如何处理的?

@Override
public void handleRequest(HttpServletRequest request, HttpServletResponse response)
		throws ServletException, IOException {

	checkAndPrepare(request, response, true);

	// check whether a matching resource exists
	Resource resource = getResource(request);
	if (resource == null) {
		logger.trace("No matching resource found - returning 404");
		response.sendError(HttpServletResponse.SC_NOT_FOUND);
		return;
	}

	// check the resource's media type
	MediaType mediaType = getMediaType(resource);
	if (mediaType != null) {
		if (logger.isTraceEnabled()) {
			logger.trace("Determined media type '" + mediaType + "' for " + resource);
		}
	}
	else {
		if (logger.isTraceEnabled()) {
			logger.trace("No media type found for " + resource + " - not sending a content-type header");
		}
	}

	// header phase
	if (new ServletWebRequest(request, response).checkNotModified(resource.lastModified())) {
		logger.trace("Resource not modified - returning 304");
		return;
	}
	setHeaders(response, resource, mediaType);

	// content phase
	if (METHOD_HEAD.equals(request.getMethod())) {
		logger.trace("HEAD request - skipping content");
		return;
	}
	writeContent(response, resource);
}

其核心方法如上,首先调用getResource方法取到静态资源,如果不存在,会在response中设置error,一旦error被设置,该请求就会被tomcat处理。否则存在,就将资源写入到body中。

 

可以看到这两种方法都是注册了一个特殊的handler,当controller的handler都没有匹配到就会走到特殊的handler。

这两种方式的区别:

default-servlet-handler是forward到tomcat的特殊servlet处理的;

而resource是在springmvc内部被处理的。

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

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值