SpringBoot系列之集成jsp模板引擎

时间:2024-02-01 20:15:52

SpringBoot系列之集成jsp模板引擎

1、模板引擎简介

引用百度百科的模板引擎解释:

模板引擎(这里特指用于Web开发的模板引擎)是为了使用户界面与业务数据(内容)分离而产生的,它可以生成特定格式的文档,用于网站的模板引擎就会生成一个标准的HTML文档。

在JavaEE领域有几中比较常用的模板引擎,分别是Jsp、Velocity、Freemarker、Thymeleaf,不过对于前端页面渲染效率来说,jsp其实还是最快的,Velocity次之。Thymeleaf虽然渲染效率不是很快,但是语法方面是比较轻巧的,Thymeleaf语法比Velocity轻巧,但是渲染效率不如Velocity

2、环境准备

ok,Springboot是一款javaee框架,使用非常简捷,创建工程也是默认打成jar包的,启动jar包就可以直接运行嵌入式的Servlet容器,比如Tomcat等等,不过Springboot要集成模板引擎的话,是默认不支持jsp的,但是并不表示不能使用,首先Springboot项目默认是jar方式运行的,而我们之前的jsp项目大部分都是war包方式,jar方式打包的项目默认就没有webapp等等这些文件,所以我们要创建jsp项目,就可以创建工程时候改成war包形式

ok,进行jsp实验,环境准备:

  • 版本:
    • Maven3.9+
    • SpringBoot2.2.1
  • IDE:
    • IntelliJ IDEA

创建Springboot Initializer项目,打包方式选择war方式
在这里插入图片描述

创建好的项目,默认是没有webapp文件的,所以我们可以手动加上,或者在idea这样做:

在这里插入图片描述
新增web.xml,记得改下默认路径
在这里插入图片描述
创建好之后,我们可以看看自动生成的项目有什么特征:

  • 首先是多了一个ServletInitializer类,这个类是干什么的?后面再详讲
    在这里插入图片描述
  • Pom文件,翻了一下,发现spring-boot-starter-tomcat的作用范围被改成provided的,这个是什么意思?需要补充一下maven的基础知识,maven中三种classpath 编译,测试,运行
    • 1.compile:默认范围,编译测试运行都有效
    • 2.provided:在编译和测试时有效
    • 3.runtime:在测试和运行时有效
    • 4.test:只在测试时有效
    • 5.system:在编译和测试时有效,与本机系统关联,可移植性差
      修改scope为provided,也就是在运行时不起效,也就是打成war包时候,就不引入对应的Tomcat jar包,不使用嵌入式的Tomcat容器,使用外部的Tomcat容器
      在这里插入图片描述

3、外部Servlet容器

Springboot项目创建之后,其实就可以直接创建jsp应用了,然后从其自动生成的配置可以看出我们在创建war包时,是可以使用外部的Tomcat容器的,所以,我们引入一下外部Tomcat
在这里插入图片描述

在这里插入图片描述
部署时候,直接使用暴露的war即可,Application context可以写上也可以不管
在这里插入图片描述
直接创建一个jsp页面
在这里插入图片描述
进行页面跳转,写个Controller类:

@Controller
public class HelloController {

    @RequestMapping(value = {"/success"})
    public String toSuccess(){
        return "success";
    }
}

注意:还要向以前那样定义一下mvc的一下配置:

spring.mvc.view.prefix=/WEB-INF/
spring.mvc.view.suffix=.jsp

ok,我之前博客SpringBoot源码学习系列之嵌入式Servlet容器
已经比较详细地介绍了Springboot嵌入式Servlet容器的知识,所以本博客有必要对比一下嵌入式的Servlet容器和本博客介绍的外部Servlet容器的区别

  • 外部Servlet容器:maven打包是war形式,先启动Servlet容器,在创建ioc容器
  • 嵌入式Servlet容器:maven打包是jar形式,启动时候先创建ioc容器,再启动嵌入式的Servlet容器,比如Tomcat、undertow等等

4、源码原理简介

尚硅谷视频介绍过Servlet的规范,翻下文档,找到如图章节,这个章节介绍了创建war包项目时候会自动创建ServletInitializer类,主要介绍共享库和运行时插件,里面介绍了ServletContainerInitializer,也就是Servlet容器的一个初始化类的使用,启动时候会通过配置在META-INF/services的文件找对应类,通过@HandlesTypes注解在启动时候将需要的类引进来

。
全局搜索,在Spring-web项目找到对应配置
在这里插入图片描述

@HandlesTypes(WebApplicationInitializer.class)//SpringServletContainerInitializer 容器类启动时候会一起创建WebApplicationInitializer类
public class SpringServletContainerInitializer implements ServletContainerInitializer {

	@Override
	public void onStartup(@Nullable Set<Class<?>> webAppInitializerClasses, ServletContext servletContext)
			throws ServletException {

		List<WebApplicationInitializer> initializers = new LinkedList<>();

		if (webAppInitializerClasses != null) {
			for (Class<?> waiClass : webAppInitializerClasses) {
				// Be defensive: Some servlet containers provide us with invalid classes,
				// no matter what @HandlesTypes says...
				//检验WebApplicationInitializer不是一个接口、抽象类就进行实例
				if (!waiClass.isInterface() && !Modifier.isAbstract(waiClass.getModifiers()) &&
						WebApplicationInitializer.class.isAssignableFrom(waiClass)) {
					try {
						initializers.add((WebApplicationInitializer)
								ReflectionUtils.accessibleConstructor(waiClass).newInstance());
					}
					catch (Throwable ex) {
						throw new ServletException("Failed to instantiate WebApplicationInitializer class", ex);
					}
				}
			}
		}

		if (initializers.isEmpty()) {
			servletContext.log("No Spring WebApplicationInitializer types detected on classpath");
			return;
		}

		servletContext.log(initializers.size() + " Spring WebApplicationInitializers detected on classpath");
		AnnotationAwareOrderComparator.sort(initializers);
		//遍历获取到的WebApplicationInitializer 类,调用其对应的onStartup方法
		for (WebApplicationInitializer initializer : initializers) {
			initializer.onStartup(servletContext);
		}
	}

}

WebApplicationInitializer 其实就是一个接口类,所以打开其实现类,可以看到SpringBootServletInitializer类
在这里插入图片描述
SpringBootServletInitializer类implements WebApplicationInitializer接口,所以在Servlet容器启动时候也会被创建,同时执行onStartup方法,如图,找关键点:

在这里插入图片描述
createRootApplicationContext方法是创建根据的容器,看看源码:

protected WebApplicationContext createRootApplicationContext(ServletContext servletContext) {
		//创建SpringApplication的构建器
		SpringApplicationBuilder builder = createSpringApplicationBuilder();
		builder.main(getClass());//设置main方法
		ApplicationContext parent = getExistingRootWebApplicationContext(servletContext);
		if (parent != null) {
			this.logger.info("Root context already created (using as parent).");
			servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, null);
			//构建器初始化
			builder.initializers(new ParentContextApplicationContextInitializer(parent));
		}
		builder.initializers(new ServletContextApplicationContextInitializer(servletContext));
		builder.contextClass(AnnotationConfigServletWebServerApplicationContext.class);
		//关键点,调用configure方法
		builder = configure(builder);
		builder.listeners(new WebEnvironmentPropertySourceInitializer(servletContext));
		SpringApplication application = builder.build();
		if (application.getAllSources().isEmpty()
				&& MergedAnnotations.from(getClass(), SearchStrategy.TYPE_HIERARCHY).isPresent(Configuration.class)) {
			application.addPrimarySources(Collections.singleton(getClass()));
		}
		Assert.state(!application.getAllSources().isEmpty(),
				"No SpringApplication sources have been defined. Either override the "
						+ "configure method or add an @Configuration annotation");
		// Ensure error pages are registered
		if (this.registerErrorPageFilter) {
			application.addPrimarySources(Collections.singleton(ErrorPageFilterConfiguration.class));
		}
		//启动Application类
		return run(application);
	}

从这个源码找到关键点configure方法,继续跟一下这个方法,基类的方法很简单,只是返回构造器
在这里插入图片描述
所以,在idea里ctrl+alt+B打开其实现方法,如图看到关键点,这个不就是自动创建的ServletInitializer类?而且重写了configure方法,而且将Springboot的Application类传给构造器类,所以跟到这里就明白了,为什么Servlet容器启动时候就会触发Springboot的ioc容器创建
在这里插入图片描述

代码例子下载:github下载链接