SpringBoot 视图模板


前言

今天天气很好,小编的心情也很好,于是兴致勃勃的开始了今天的学习之旅。

代码编写的很顺利,但是在 Run Bootstrap 的时候,小编的心情似乎就不是那么好了,就感觉脑瓜上有一片乌云时不时的还下点雨。

没错就是今天的主题 SpringBoot 视图模板 关于这个关键词在度娘上肯定是不少的,我刚开始也是度娘上寻找答案的,无奈没有一丢丢收获,他们的文章或者说他们那些笔者可能是一个师傅教出来的,内容大体都如出一辙。

于似乎小编心中有了一些怒火,如果都这样借鉴别人的文章去发博客,那让小白该怎么去学,遇到问题了该怎么解决。如今互联网人才空缺的原因之一就是这么一群互相抄袭博客的人才,不明白其中的原理就断章取义大肆传播。

不说那么多了,下面把舞台交给小编,看我操作。



SpringBoot Thymeleaf

介绍

Thymeleaf is a modern server-side Java template engine that emphasizes natural HTML templates that can be previewed in a browser by double-clicking, which is very helpful for independent work on UI templates (for example, by a designer) without the need for a running server. If you want to replace JSPs, Thymeleaf offers one of the most extensive sets of features to make such a transition easier. Thymeleaf is actively developed and maintained. For a more complete introduction, see the Thymeleaf project home page.
The Thymeleaf integration with Spring MVC is managed by the Thymeleaf project. The configuration involves a few bean declarations, such as ServletContextTemplateResolver, SpringTemplateEngine, and ThymeleafViewResolver. See Thymeleaf+Spring for more details.

  1. 这是 Spring 官网对 Thymeleaf 的描述 点我跳转
  2. 霹雳扒拉的讲了一堆,核心是 Thymeleaf to is Java server template engine (散装英语有点差劲,意思就是 Thymeleaf 是一个Java 服务端模板引擎)
  3. SpringBoot 将Thymeleaf 作为首选视图模板引擎肯定是出于性能和使用考虑的,所以Thymeleaf 的利大于弊,大家放心使用就好了。

使用

  1. 这里的使用我是建立在 SpringBoot 环境的项目上的
  2. 首先在 pom 文件中添加 Thymeleaf 的依赖
	<dependency>
		<groupId>org.springframework.boot</groupId>
		<artifactId>spring-boot-starter-thymeleaf</artifactId>
	</dependency>
  1. 编写 Thymeleaf HTML 模板 (这里是演示嘛,所以怎么简单怎么来哈)
<!-- classpath:/templates/thymeleaf/index.html -->
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org" lang="th">
    <body>
        <p th:text="${message}">!!!</p>
    </body>
</html>
  1. 编写 Thymeleaf 测试类
public class ThymeleafTemplateEngineBootstrap {

	public static void main(String[] args) throws IOException {
		// Spring Thymeleaf 模板引擎
		ITemplateEngine templateEngine = new SpringTemplateEngine();

		// 渲染上下文
		Context context = new Context();
		context.setVariable("message", "Hello World");

		// classpath:/templates/thymeleaf/index.html
		ResourceLoader resourceLoader = new DefaultResourceLoader();
		Resource resource = resourceLoader.getResource("classpath:/templates/thymeleaf/index.html");
		File file = resource.getFile();
		FileInputStream inputStream = new FileInputStream(file);
		ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
		IOUtils.copy(inputStream, outputStream);

		// 模板内容 采用外部配置方式 减少硬编码
		String content = outputStream.toString("UTF-8");
//		String content = "<p th:text=\"${message}\">!!!</p>";

		// 渲染
		String result = templateEngine.process(content, context);
		// 渲染结果
		System.out.println(result);
	}
}

------------------------ output console ------------------------

<!-- classpath:/templates/thymeleaf/index.html -->
<!DOCTYPE html>
<html lang="th">
    <body>
        <p>Hello World</p>
    </body>
</html>
	
  1. Thymeleaf 在Spring Boot 中的使用也非常简单,编写 application.properties 配置文件。(注意:访问不到视图模板的情况下,如果控制层和模板没有问题的话,那么问题肯定是在这里,spring.thymeleaf.prefix/suffix 这两个属性切记一定要配置正确,否则度娘上的文章会让你彻底失去继续学下去的信心)
# Thymeleaf 模板所在的文件夹
spring.thymeleaf.prefix = /templates/thymeleaf/
# Thymeleaf 模板的后缀
spring.thymeleaf.suffix = .html
# Thymeleaf 是否缓存
spring.thymeleaf.cache = false
# Thymeleaf 文件编码
spring.thymeleaf.encoding = UTF-8
  1. 编写控制层
@Controller
public class CombatController {

	@RequestMapping("/")
	public String index() {
		return "index";
	}

	@ModelAttribute("message")
	public String message() {
		return "Hello World";
	}
}
  1. 编写引导类
@SpringBootApplication
public class CombatApplication {

	public static void main(String[] args) {
		SpringApplication.run(CombatApplication.class, args);
	}
}
  1. 启动项目 测试访问 http://localhost:8080/ZhangchongSR0208


SpringBoot Jsp

介绍

  1. 相信各位对 JSP 并不陌生,从 JavaWeb 的那只三脚猫就开始用这玩意了,这玩意确实难用,还特别容易报错。
  2. 但这毕竟是 JavaWeb 的基石,同时也是这篇文章的重点,下面开始!!!

使用

  1. 相信大家在度娘上搜索 SpringBoot 整合 JSP 的文章,大致都如下
    • 创建 webapp 文件夹
    • 创建 WEB-INF 文件夹
    • 创建 jsp 文件夹并在文件夹下创建 jsp 文件
    • pom 文件中添加 buildsrc/main/webapp 编译到 META-INF/resources 文件夹下
    • application.properties 文件中添加配置
  2. 然后 application.proeprties 就是这个样子
# Jsp 模板前缀
spring.mvc.view.prefix = /WEB-INF/jsp/
# Jsp 模板后缀
spring.mvc.view.suffix = .jsp
  1. 然后编译后的文件夹是这个样子
    ZhangchongSR0208
  2. 对于将 JavaWeb 升级到 SpringBoot 项目的这类人来说上面的一切都很正常,很正常非常正常,WEB-INF 对吧 没毛病。
  3. 对比一下 Thymeleaf 的配置
# Jsp 模板前缀
spring.mvc.view.prefix = /WEB-INF/jsp/
# Jsp 模板后缀
spring.mvc.view.suffix = .jsp

# Thymeleaf 模板所在的文件夹
spring.thymeleaf.prefix = /templates/thymeleaf/
# Thymeleaf 模板的后缀
spring.thymeleaf.suffix = .html
# Thymeleaf 是否缓存
spring.thymeleaf.cache = false
# Thymeleaf 文件编码
spring.thymeleaf.encoding = UTF-8
  1. 这样真的好吗,对于 SpringBoot 采用 JSP 首当模板引擎来说,这样好吗,这样不好。我得创建个 webapp 用来单独存储 jsp 同时这也极大的增加了小白看见 404 的极大几率。
  2. 对于从 JavaWeb 升级到 SpringBoot 的项目来说,这样其实没关系,但是对于一些早期的开发人员。比如说我们架构群里的吉尔哥,那从小到大都是用 JSP 的,那突然有一天项目经理讲话 吉尔 你用 SpringBootJSP 吧,别用 SSM 那老架构了扛不住高并发,咱们公司也得跟着互联网的脚步走。对于 吉尔哥 这样的开发人员来说是非常容易 404 的。

找茬

  1. 呸呸呸,这个 找茬 标题起的不对,应该叫 定制
  2. 上面我们说到的 吉尔哥 对于这样的开发人员来说,JSP 的配置无疑难度是非常大的,这完全超出了自身技术能力,CPU 有可能还得烧坏了。
  3. 言回正传,对于 JSP 的配置着实让人有点烧脑,如果不按照网上那一群人的方式来的话,那 resources 就得有 /META-INF/resources/WEB-INF/jsp 这个文件夹,而 application.properties 还是这样,这完全是卖狗肉挂羊头、驴头不对马嘴。
# Jsp 模板前缀
spring.mvc.view.prefix = /WEB-INF/jsp/
# Jsp 模板后缀
spring.mvc.view.suffix = .jsp
  1. 于是准备将 resources 下调整为 /templates/jsp/ 这个文件夹,而 application.properties 配置调整为下面这样。
# Jsp 模板前缀
spring.mvc.view.prefix = /templates/jsp/
# Jsp 模板后缀
spring.mvc.view.suffix = .jsp
  1. 说干就干,开始分析源码
  2. /WEB-INF/jsp/xx.jsp/META-INF/resources/WEB-INF/jsp/xx.jsp 这个过程是怎么完成的?
    • 通过源码分析断点调试,在 org.apache.catalina.webresources.StandardRoot#getResourceInternal 方法中找到了猫腻
    • 这个方法是为了找到 JSP 资源文件,是在 JSP -> Servlet 之前。
    • allResources 变量中存在两个地址 一个是 Tomcat 的临时地址 ( System.getProperty("java.io.tmpdir")\... ),一个是 SpringBoot 内部配置 ( ...\target\classes\META-INF\resources )
    • 这也不难看出,SpringBoot 还是非常遵守 Servlet 规范的,铁打的 JSP 就是必须在 META-INF/resources 下。
    protected final WebResource getResourceInternal(String path,
            boolean useClassLoaderResources) {
        WebResource result = null;
        WebResource virtual = null;
        WebResource mainEmpty = null;
        for (List<WebResourceSet> list : allResources) {
            for (WebResourceSet webResourceSet : list) {
                if (!useClassLoaderResources &&  !webResourceSet.getClassLoaderOnly() ||
                        useClassLoaderResources && !webResourceSet.getStaticOnly()) {
                    result = webResourceSet.getResource(path);
                    if (result.exists()) {
                        return result;
                    }
                    if (virtual == null) {
                        if (result.isVirtual()) {
                            virtual = result;
                        } else if (main.equals(webResourceSet)) {
                            mainEmpty = result;
                        }
                    }
                }
            }
        }

        // Use the first virtual result if no real result was found
        if (virtual != null) {
            return virtual;
        }

        // Default is empty resource in main resources
        return mainEmpty;
    }
  1. SpringBoot 是怎么配置 Tomcat 内部资源路径的?
    • org.springframework.boot.web.embedded.tomcat.TomcatServletWebServerFactory.StaticResourceConfigurer 监听器中配置内部资源地址
    • 首先 org.springframework.boot.web.servlet.context.ServletWebServerApplicationContext#onRefresh 方法中会调用创建 WebServer 的方法
    • 进入到 org.springframework.boot.web.embedded.tomcat.TomcatServletWebServerFactory#getWebServer 方法中后会添加下面的一个监听器,这个监听器是在 Tomcat 启动的时候会被触发,里面有事件判断。
    • 接收到 Lifecycle.CONFIGURE_START_EVENT 事件后,这个监听器会在 Tomcat context 中加入一个静态资源地址。
	private final class StaticResourceConfigurer implements LifecycleListener {

		private final Context context;

		private StaticResourceConfigurer(Context context) {
			this.context = context;
		}

		@Override
		public void lifecycleEvent(LifecycleEvent event) {
			if (event.getType().equals(Lifecycle.CONFIGURE_START_EVENT)) {
				addResourceJars(getUrlsOfJarsWithMetaInfResources());
			}
		}

		private void addResourceJars(List<URL> resourceJarUrls) {
			for (URL url : resourceJarUrls) {
				String path = url.getPath();
				if (path.endsWith(".jar") || path.endsWith(".jar!/")) {
					String jar = url.toString();
					if (!jar.startsWith("jar:")) {
						// A jar file in the file system. Convert to Jar URL.
						jar = "jar:" + jar + "!/";
					}
					addResourceSet(jar);
				}
				else {
					addResourceSet(url.toString());
				}
			}
		}

		private void addResourceSet(String resource) {
			try {
				if (isInsideNestedJar(resource)) {
					// It's a nested jar but we now don't want the suffix because Tomcat
					// is going to try and locate it as a root URL (not the resource
					// inside it)
					resource = resource.substring(0, resource.length() - 2);
				}
				URL url = new URL(resource);
				String path = "/META-INF/resources";
				this.context.getResources().createWebResourceSet(ResourceSetType.RESOURCE_JAR, "/", url, path);
			}
			catch (Exception ex) {
				// Ignore (probably not a directory)
			}
		}

		private boolean isInsideNestedJar(String dir) {
			return dir.indexOf("!/") < dir.lastIndexOf("!/");
		}

	}
  1. 如何像 SpringBoot 一样配置 Tomcat.context 资源地址?
    • 首先继承 org.springframework.boot.web.server.WebServerFactoryCustomizer 并在泛型类型中添加 需要 Customizer 的具体WebServerFactory 实现类。(这里针对的是 Tomcat 所以是 TomcatServletWebServerFactory )
    • 此配置类让 SpringBoot 加载到后,会让 org.springframework.boot.web.server.WebServerFactoryCustomizerBeanPostProcessor 这个 BeanPostProcessor 进行处理。
    • 其中就会调用 org.springframework.boot.web.server.WebServerFactoryCustomizer#customize 方法,这个处理是在初始化 Tomcat 之前的,所以不用担心刚才的 Listener 不会被加载到
public class TomcatConfiguration implements WebServerFactoryCustomizer<TomcatServletWebServerFactory> {

    @Override
    public void customize(TomcatServletWebServerFactory factory) {
		factory.addContextCustomizers((context -> {
			context.addLifecycleListener(new LifecycleListener() {
				@Override
				public void lifecycleEvent(LifecycleEvent event) {
					if (event.getType().equals(Lifecycle.CONFIGURE_START_EVENT)) {
						try {
							// 这里并没有像 SpringBoot 一样对运行环境进行区分,因为 jar 和 !jar 的地址是不一样的
							// 如果将此代码应用在生产环境这里还需要添加逻辑
							URL url = new URL(this.getClass().getResource("/").toString());
							String path = "/";
							context.getResources().createWebResourceSet(WebResourceRoot.ResourceSetType.RESOURCE_JAR, "/", url, path);
						} catch (MalformedURLException e) {
							e.printStackTrace();
						}
					}
				}
			});
		}));
    }
}
  1. 至此刚才的 applicatio.properties 配置文件就生效了
# Jsp 模板前缀
spring.mvc.view.prefix = /templates/jsp/
# Jsp 模板后缀
spring.mvc.view.suffix = .jsp
  1. 启动项目 测试访问 http://localhost:8080/
    ZhangchongSR0208


留言

对于问题的解决方案还是是挺多的,送大家一句话 别向这个世界认输 因为你还有个牛逼的梦想

ZhangchongSR0208

  • 1
    点赞
  • 0
    收藏
  • 打赏
    打赏
  • 1
    评论

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

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
©️2022 CSDN 皮肤主题:1024 设计师:我叫白小胖 返回首页
评论 1

打赏作者

大千有道

别向这个世界认输 因为你还有个

¥2 ¥4 ¥6 ¥10 ¥20
输入1-500的整数
余额支付 (余额:-- )
扫码支付
扫码支付:¥2
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值