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下載鏈接