IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> Java知识库 -> 技巧 - SpringBoot项目下实现动态化静态文件路径注册 -> 正文阅读

[Java知识库]技巧 - SpringBoot项目下实现动态化静态文件路径注册

实现方式以及相应的底层源码实现逻辑。

背景

最近需要将一个SpringMVC WAR应用切换到SpringBoot架构上来,在完成相关代码迁移之后,发现原项目存在一个需求:”应用可以在运行时动态更新静态文件的映射“。

其实这功能本身是由Tomcat实现的,并非SpringMVC架构体系自带,所以实现思路大概有如下几种:

  1. 理解Tomcat相关实现逻辑,在springboot自带的embed-tomcat中进行配置。
  2. 将springboot默认的jar部署形式调整为war部署形式。
  3. 理解springboot是如何进行静态文件映射的,进而实现动态调用。

经过测试验证和综合考量,笔者最终选择了第三种实现方式。

  1. 对于方法一,虽然笔者早期确实阅读过Tomcat相关源码,但奈何年代比较久远,捡起来成本比较大。
  2. 对于方法二,笔者在自己的测试项目是可以成功的,但在实际中始终存在些许问题,真要去一个个解决,ROI属实有点低了。

实现

直接上代码。

import cn.hutool.core.util.ReflectUtil;
import cn.hutool.core.util.StrUtil;

@Api
@RestController
@RequestMapping("dynamicStatic")
public class StaticResourceDynamicRegistryController {
	@Autowired
	private ApplicationContext applicationContext;

	@ApiOperation(value = "registry")
	@PostMapping(value = "registry", produces = MediaType.APPLICATION_JSON_VALUE)
	public String registry(@ApiParam(defaultValue="/fulizhe") @RequestParam String resourceHandler, //
			@ApiParam(defaultValue="E:/data/") @RequestParam String resourceLocations) {
		registerHandlersForAdditionalStatisResource(Collections.singletonMap(resourceHandler, resourceLocations));
		
		return "SUCCESS";
	}

	private void registerHandlersForAdditionalStatisResource(Map<String, String> registerMapping) {
		// 这些值的由来参见: 
		final UrlPathHelper mvcUrlPathHelper = applicationContext.getBean("mvcUrlPathHelper", UrlPathHelper.class);
		final ContentNegotiationManager mvcContentNegotiationManager = applicationContext
				.getBean("mvcContentNegotiationManager", ContentNegotiationManager.class);
		final ServletContext servletContext = applicationContext.getBean(ServletContext.class);
		// SimpleUrlHandlerMapping.java
		final HandlerMapping resourceHandlerMapping = applicationContext.getBean("resourceHandlerMapping",
				HandlerMapping.class);
		
		// 这里存放的是springmvc已经建立好的映射处理
		@SuppressWarnings("unchecked")
		final Map<String, Object> handlerMap = (Map<String, Object>) ReflectUtil.getFieldValue(resourceHandlerMapping,
				"handlerMap");

		final ResourceHandlerRegistry resourceHandlerRegistry = new ResourceHandlerRegistry(applicationContext,
				servletContext, mvcContentNegotiationManager, mvcUrlPathHelper);

		for (Map.Entry<String, String> entry : registerMapping.entrySet()) {
			String urlPath = entry.getKey();
			String resourceLocations = entry.getValue();
			final String urlPathDealed = StrUtil.appendIfMissing(urlPath, "/**");
			final String resourceLocationsDealed = StrUtil.appendIfMissing(resourceLocations, "/");
			
			// 先移除之前自定义注册过的...
			handlerMap.remove(urlPathDealed);
			// 重新注册
			resourceHandlerRegistry.addResourceHandler(urlPathDealed)
					.addResourceLocations("file:" + resourceLocationsDealed);
		}

		final Map<String, ?> additionalUrlMap = ReflectUtil
				.<SimpleUrlHandlerMapping>invoke(resourceHandlerRegistry, "getHandlerMapping").getUrlMap();

		ReflectUtil.<Void>invoke(resourceHandlerMapping, "registerHandlers", additionalUrlMap);
	}
}

最终效果如下:
在这里插入图片描述

原理剖析

上面的实现只能算是开胃小菜,重头戏还得是源码部分。

首先让我们看看常规场景下SpringBoot是如何实现静态文件映射的:

@Configuration
public class XxxxStaticResourceConfig implements WebMvcConfigurer {

	@Override
	public void addResourceHandlers(ResourceHandlerRegistry registry) {

	 	registry.addResourceHandler("/cat.html").addResourceLocations("classpath:/static/cat.html");
	 	// 目录映射, 注意 ResourceLocations 配置部分一定要带上 / 
		registry.addResourceHandler("/catjs/**").addResourceLocations("classpath:/static/catjs/");
	
	}

以上代码基础上,通过断点大法,我们可以得到如下堆栈:
在这里插入图片描述

上述堆栈中,我们挑选WebMvcConfigurationSupport.resourceHandlerMapping()方法进行进一步解读:

	// WebMvcConfigurationSupport.java
	//     其子类WebMvcAutoConfiguration.EnableWebMvcConfiguration.java 会进行覆写, 加入springboot默认静态配置
	//	   上一小节<实现>正是参考了本方法参数
	//	   HandlerMapping接口在SpringMVC整体架构上处于核心位置, 其抽象的是为当前请求找出对应的处理类. 相关介绍详见笔者之前的博客<SpringMVC源码研究之DispatcherServlet处理请求>.
	@Bean
	@Nullable
	public HandlerMapping resourceHandlerMapping(
			@Qualifier("mvcUrlPathHelper") UrlPathHelper urlPathHelper,
			@Qualifier("mvcPathMatcher") PathMatcher pathMatcher,
			@Qualifier("mvcContentNegotiationManager") ContentNegotiationManager contentNegotiationManager,
			@Qualifier("mvcConversionService") FormattingConversionService conversionService,
			@Qualifier("mvcResourceUrlProvider") ResourceUrlProvider resourceUrlProvider) {

		Assert.state(this.applicationContext != null, "No ApplicationContext set");
		Assert.state(this.servletContext != null, "No ServletContext set");
		// 这个使用new实例化出来的registry, 正是我们在实现 WebMvcConfigurer.addResourceHandlers(ResourceHandlerRegistry registry)的方法参数 
		ResourceHandlerRegistry registry = new ResourceHandlerRegistry(this.applicationContext,
				this.servletContext, contentNegotiationManager, urlPathHelper);
		// 回调容器中所有的 WebMvcConfigurer.addResourceHandlers(ResourceHandlerRegistry registry) 实现
		// 正是在这个回调里, springboot默认的 /** 对应 [classpath:/META-INF/resources/, classpath:/resources/, classpath:/static/, classpath:/public/] 正是通过 WebMvcAutoConfiguration.WebMvcAutoConfigurationAdapter类, 这个WebMvcConfigurer实现类来完成的. 具体参见: WebMvcAutoConfiguration.WebMvcAutoConfigurationAdapter.addResourceHandlers() 方法
		addResourceHandlers(registry);
		
		// 将上述自定义配置转换为spring内部类型
		// 实际返回类型为 SimpleUrlHandlerMapping, 这个类覆写了基类的initApplicationContext() 方法, 以将收集到的自定义配置 urlMap 注册到自身的 handlerMap 字段中(其实是在基类中定义的); 之后在进行静态文件响应时候, 该 SimpleUrlHandlerMapping 就根据注册的映射关系, 读取并向前端返回静态资源了..	
		AbstractHandlerMapping handlerMapping = registry.getHandlerMapping();
		if (handlerMapping == null) {
			return null;
		}
		// 装填其它实例字段信息
		handlerMapping.setPathMatcher(pathMatcher);
		handlerMapping.setUrlPathHelper(urlPathHelper);
		handlerMapping.setInterceptors(getInterceptors(conversionService, resourceUrlProvider));
		handlerMapping.setCorsConfigurations(getCorsConfigurations());
		return handlerMapping;
	}

关于该HandlerMapping实例Bean 如何装载进SpringMVC核心类DispatcherServlet中的,可以参见SpringMVC源码研究之DispatcherServlet初始化

最后让我们看一下上面注册的HandlerMapping实例在运行时的状态。嗯,倒数第二,前面的都不匹配才轮得到它…(其中第一个是swagger,第二,三个属于actuate)
在这里插入图片描述

参考

  1. SpringMVC源码研究之DispatcherServlet处理请求
  2. spring-boot-war-tomcat-deploy
  Java知识库 最新文章
计算距离春节还有多长时间
系统开发系列 之WebService(spring框架+ma
springBoot+Cache(自定义有效时间配置)
SpringBoot整合mybatis实现增删改查、分页查
spring教程
SpringBoot+Vue实现美食交流网站的设计与实
虚拟机内存结构以及虚拟机中销毁和新建对象
SpringMVC---原理
小李同学: Java如何按多个字段分组
打印票据--java
上一篇文章      下一篇文章      查看所有文章
加:2022-02-01 20:27:55  更:2022-02-01 20:28:28 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/24 9:49:04-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码