SpringBoot处理静态文件源码分析

前言

在使用SpringBoot框架进行开发时,如果我们要在应用上放一下静态文件访问,那么只要遵守SpringBoot的规范,在指定目录下放我们的静态文件即可

使用

我们新建一个SpringBoot工程,在resources目录下新建了一个public目录,在public目录下创建一个a.txt,如图所示
在这里插入图片描述
启动项目,直接访问http://localhost:8080/a.txt

可以看到浏览器返回了我们的文件内容

源码

我们从springmvc的入口DispatcherServlet的doDispatch开始看

protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
		......

				// Determine handler for the current request.
				// 核心代码1
				mappedHandler = getHandler(processedRequest);
				if (mappedHandler == null) {
					noHandlerFound(processedRequest, response);
					return;
				}

				// Determine handler adapter for the current request.
				HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());

				......

				// Actually invoke the handler.
				// 核心代码2
				mv = ha.handle(processedRequest, response, mappedHandler.getHandler());

				......

只看核心代码,核心代码1处根据请求获取可以处理的HandlerExecutionChain

@Nullable
	protected HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
		if (this.handlerMappings != null) {
			for (HandlerMapping mapping : this.handlerMappings) {
				HandlerExecutionChain handler = mapping.getHandler(request);
				if (handler != null) {
					return handler;
				}
			}
		}
		return null;
	}

默认有很多handlerMappings,从里面选择到适合当前request的,这里通过打断点可以找到最后返回的是这个HandlerMapping
在这里插入图片描述
我们看看为什么会返回这个,跟进断点
在这里插入图片描述
在这里插入图片描述

@Nullable
	protected Object lookupHandler(String urlPath, HttpServletRequest request) throws Exception {
		// Direct match?
		// 核心代码1,handlerMap里面有/webjars/**和/**,所以get后为null
		Object handler = this.handlerMap.get(urlPath);
		......

		// Pattern match?
		// 核心代码2,根据正则来匹配下,最终发现/**的正则可以匹配我们当前的/a.txt,所以matchingPatterns放了一个/**
		List<String> matchingPatterns = new ArrayList<>();
		for (String registeredPattern : this.handlerMap.keySet()) {
			if (getPathMatcher().match(registeredPattern, urlPath)) {
				matchingPatterns.add(registeredPattern);
			}
		......
		}

		String bestMatch = null;
		Comparator<String> patternComparator = getPathMatcher().getPatternComparator(urlPath);
		if (!matchingPatterns.isEmpty()) {
			......
			// 给bestMatch 赋值为/**
			bestMatch = matchingPatterns.get(0);
		}
		if (bestMatch != null) {
			// 核心代码3 根据/**匹配到当前的规则了
			handler = this.handlerMap.get(bestMatch);
			......
	}

从这里的代码可以看出来,先根据我们的路径在所有handlerMappings里面寻找,寻找是按照顺序来的,当前面几个HandlerMapping都匹配不到,则会走到规则为/webjars/**和/**的 SimpleUrlHandlerMapper了

调用了mapping.getHandler(request),获得了一个HandlerExecutionChain,可以看到这里的HandlerExecutionChain是一个ResourceHttpRequestHandler

在这里插入图片描述
然后我们看上述核心代码2

mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
@Override
	@Nullable
	public ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler)
			throws Exception {

		((HttpRequestHandler) handler).handleRequest(request, response);
		return null;
	}

进入了ResourceHttpRequestHandler的handleRequest

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

		// For very general mappings (e.g. "/") we need to check 404 first
		// 核心代码1
		Resource resource = getResource(request);
		......

		// Header phase
		// 核心代码2
		if (new ServletWebRequest(request, response).checkNotModified(resource.lastModified())) {
			logger.trace("Resource not modified");
			return;
		}

		// Apply cache settings, if any
		prepareResponse(response);

		// Check the media type for the resource
		// 获取MediaType,这个是http请求response需要的
		MediaType mediaType = getMediaType(request, resource);

		// Content phase
		if (METHOD_HEAD.equals(request.getMethod())) {
			setHeaders(response, resource, mediaType);
			return;
		}

		ServletServerHttpResponse outputMessage = new ServletServerHttpResponse(response);
		if (request.getHeader(HttpHeaders.RANGE) == null) {
			Assert.state(this.resourceHttpMessageConverter != null, "Not initialized");
			setHeaders(response, resource, mediaType);
			// 核心代码3
			this.resourceHttpMessageConverter.write(resource, mediaType, outputMessage);
		}
		......
	}

核心代码1,从名字也可以看出来是获取当前资源

@Nullable
	protected Resource getResource(HttpServletRequest request) throws IOException {
		......
		// 核心代码
		Resource resource = this.resolverChain.resolveResource(request, path, getLocations());
		if (resource != null) {
			resource = this.transformerChain.transform(request, resource);
		}
		return resource;
	}
@Override
	@Nullable
	public Resource resolveResource(
			@Nullable HttpServletRequest request, String requestPath, List<? extends Resource> locations) {

		return (this.resolver != null && this.nextChain != null ?
				this.resolver.resolveResource(request, requestPath, locations, this.nextChain) : null);
	}

进入resolveResource

@Override
	@Nullable
	public Resource resolveResource(@Nullable HttpServletRequest request, String requestPath,
			List<? extends Resource> locations, ResourceResolverChain chain) {

		return resolveResourceInternal(request, requestPath, locations, chain);
	}
@Override
	protected Resource resolveResourceInternal(@Nullable HttpServletRequest request, String requestPath,
			List<? extends Resource> locations, ResourceResolverChain chain) {

		return getResource(requestPath, request, locations);
	}
@Nullable
	private Resource getResource(String resourcePath, @Nullable HttpServletRequest request,
			List<? extends Resource> locations) {
		// 核心代码
		for (Resource location : locations) {
			try {
				String pathToUse = encodeIfNecessary(resourcePath, request, location);
				Resource resource = getResource(pathToUse, location);
				if (resource != null) {
					return resource;
				}
			}
			......
		return null;
	}

从这里就是从指定的所有locations前缀目录里面查找文件,看断点可见
在这里插入图片描述
也就是我们的静态资源其实放上面任意一个位置都可以被访问到

接下来获取到Resource资源了,然后我们看前面的核心代码2

// Header phase
		if (new ServletWebRequest(request, response).checkNotModified(resource.lastModified())) {
			logger.trace("Resource not modified");
			return;
		}

调用checkNotModified方法判断是否修改,这里尝试了几次发现第一次访问时会返回false,但是第二次就会返回true了,好像是在Header里面做了缓存,这里不做分析,继续往下走

看核心代码3

if (request.getHeader(HttpHeaders.RANGE) == null) {
	Assert.state(this.resourceHttpMessageConverter != null, "Not initialized");
	setHeaders(response, resource, mediaType);
	// 核心代码
	this.resourceHttpMessageConverter.write(resource, mediaType, outputMessage);
}
@Override
	public final void write(final T t, @Nullable MediaType contentType, HttpOutputMessage outputMessage)
			throws IOException, HttpMessageNotWritableException {

		final HttpHeaders headers = outputMessage.getHeaders();
		addDefaultHeaders(headers, t, contentType);

		if (outputMessage instanceof StreamingHttpOutputMessage) {
			StreamingHttpOutputMessage streamingOutputMessage = (StreamingHttpOutputMessage) outputMessage;
			streamingOutputMessage.setBody(outputStream -> writeInternal(t, new HttpOutputMessage() {
				@Override
				public OutputStream getBody() {
					return outputStream;
				}
				@Override
				public HttpHeaders getHeaders() {
					return headers;
				}
			}));
		}
		else {
			// 核心代码
			writeInternal(t, outputMessage);
			outputMessage.getBody().flush();
		}
	}

到这里就基本看完了,t是我们的Resource,outputMessage是一个Response,也就是把我们的文件写入到Response里面,然后调用flush,刷新数据。

总结

通过源码可以看到SpringBoot通过多个handerMapper来根据http请求的url来找到适合当前的handerMapper,比如我们的Controller方法路径则会有相应的handerMapper处理,如果匹配不到则由后面的handerMapper继续匹配,匹配到我们的资源文件的handerMapper则继续寻找,找到了最后还是通过字节的方式通过输出流输出

扩展:springboot还支持我们自定义静态文件路径,其实也就是支持我们修改locations的值,让handerMapper可以匹配到即可

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值