关于WebApplicationType
Spring的主要开发场景是Web应用,我们将上例改成一个Web应用, 我们首先通过Sping Tools插件添加一个"Spring Web"依赖。
添加完成后,插件在pom.xml文件添加了"spring-boot-starter-web"依赖,并在resources目录下创建了"static"和"templates"子目录。这时我们的代码不做任何修改,再运行一次我们的程序,程序不再像之前一样自动退出,通过程序输出可以看到,程序启动了一个嵌入的Tomcat Web服务器,并持续监听8080端口:
2022-03-17 10:18:15.054 INFO 3364 --- [ main] o.s.b.w.embedded.tomcat.TomcatWebServer : Tomcat initialized with port(s): 8080 (http)
2022-03-17 10:18:15.066 INFO 3364 --- [ main] o.apache.catalina.core.StandardService : Starting service [Tomcat]
2022-03-17 10:18:15.066 INFO 3364 --- [ main] org.apache.catalina.core.StandardEngine : Starting Servlet engine: [Apache Tomcat/9.0.58]
2022-03-17 10:18:15.149 INFO 3364 --- [ main] o.a.c.c.C.[Tomcat].[localhost].[/] : Initializing Spring embedded WebApplicationContext
...
Spring Boot定义了3种Web应用类型, 分别是: NONE, SERVLET, REACTIVE。Web应用类型不同,Sping程序的启动行为也不同。
public enum WebApplicationType {
/**
* The application should not run as a web application and should not start an embedded web server.
* 非WEB应用, 不启动一个嵌入的web服务器;
*/
NONE,
/**
* The application should run as a servlet-based web application and should start an embedded servlet web server.
* 基于servlet的web应用, 将启动一个嵌入的servlet类型web服务器;
*/
SERVLET,
/**
* The application should run as a reactive web application and should start an embedded reactive web server.
* reactive类型web应用, 将启动一个嵌入的reactive web server;
*/
REACTIVE;
}
通过文档我们知道,可以通过SpringApplication的对象方法’setWebApplicationType(WebApplicationType webApplicationType)'显式的指定类型。但我们并没有指定Spring Boot怎么知道的呢?通过源代码知道,SpringApplication会在其构造函数中调用WebApplicationType.deduceFromClasspath()自己判断。
public class SpringApplication {
@SuppressWarnings({ "unchecked", "rawtypes" })
public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) {
...
this.webApplicationType = WebApplicationType.deduceFromClasspath(); // --->
...
}
}
代码就不贴了,判断逻辑也很简单:
1. 如果classpath中存在"org.springframework.web.reactive.DispatcherHandler"类, 但不存在"org.springframework.web.servlet.DispatcherServlet"和"org.glassfish.jersey.servlet.ServletContainer"类, 则为WebApplicationType.REACTIVE类型;
2. 如果classpath中不存在"javax.servlet.Servlet"或者"org.springframework.web.context.ConfigurableWebApplicationContext"类, 则为WebApplicationType.NONE;
3. 否则为WebApplicationType.SERVLET;
我们再看看这些类的来源:
org.springframework.web.reactive.DispatcherHandler -> spring-webflux-*.jar -> spring-boot-starter-webflux (Spring Reactive Web)
org.springframework.web.servlet.DispatcherServlet -> spring-webmvc-*.jar -> spring-boot-stater-web (Spring Web)
org.springframework.web.context.ConfigurableWebApplicationContext -> spring-web-*.jar -> spring-boot-stater-web (Spring Web)
org.glassfish.jersey.servlet.ServletContainer -> jersey-container-servlet-core-*.jar -> spring-boot-starter-jersey (Jersey)
更简单的来说,Spring Boot是基于你引入的依赖(starter)来判断应用类型的:
1. 如果你引入Spring Web或者Jersey的启动器, 这就是一个WebApplicationType.SERVLET类型应用, Spring Boot会启动一个嵌入的servlet web服务器;
2. 如果只引入Spring Reactive Web的启动器,那么就是一个WebApplicationType.REACTIVE类型应用, Spring Boot会启动一个嵌入的reactive web服务器;
3. 如果引入没有任何的Web相关的启动器, 那么就是一个WebApplicationType.NONE类型应用, Spring Boot不会启动任何的嵌入Web服务器;
因为我们引入了"spring-boot-starter-web"依赖,所以Spring Boot以SERVLET类型启动我们的程序。
编写Spring Web应用程序
Spring开发的Web应用主要分为两类:
- Web UI(@Controller), 基于模板引擎(View)开发的传统模式的Web页面;
- REST-style services(@RestController = @Controller+@ResponseBody), 基于http/json的Web服务,不需要模板引擎(View)来渲染;
当下互联网兴起,微服务架构非常流行,Spring Boot开发Restful API非常方便,Spring Boot是当前广泛用来构建Java微服务的框架。但这里我们先讨论传统的Web UI应用程序。
回顾下我们前面学习的Spring MVC,前端控制器(DispatcherServlet)根据配置拦截前端请求,并交给我们编写的Controller(@Controller),视图解析器(ViewResolver)再将我们编写的控制器返回的viewName与实际的视图对应起来,视图进行渲染返回给用户。
我们需要先配置前端控制器(DispatcherServlet)指定拦截什么样的前端请求,我们还需要装载视图解析器(ViewResolver),并配置视图匹配规则,如prefix和suffix, … 最后编写Controller类和模板页面(View)。
Spring Boot提供了对Spring MVC的大部分使用场景的自动配置(参考文档的8.1.1章节),包括了下列servelet模板引擎的的自动配置支持:
- FreeMarker
- Groovy
- Thymeleaf
- Mustache
“When you use one of these templating engines with the default configuration, your templates are picked up automatically from src/main/resources/templates.” //以上模板自动到src/main/resources/templates查找, 不需要额外配置。
使用thymeleaf
我们以Thymeleaf模板引擎为例, Thymeleaf是当前主流的, 开源的, 基于HTML5的, 现代模板引擎, 它通过html标签中嵌入特殊的语法糖方式存在, Thymeleaf视图既可以在浏览器查看页面的静态效果(本身是一个html文件), 又可以在服务器端解析处理后渲染处动态页面。 对前端UI设计人员非常友好。Thymeleaf优势在于:
- 以html属性出现,保证html的完整语法结构不被破坏。
- 浏览器可以直接预览模板文件, 无需服务器端支持。
- 支持html, js, raw等多种模板类型。
前面我们已经为项目添加了"Spring Web"启动器,这里我们还要再添加"Thymeleaf"启动器。
该操作实际上是在pom.xml文件中添加"spring-boot-starter-thymeleaf"依赖。为了避免干扰,我们将代码放在一个独立的org.littlestar.learning4包下。
写一个hello bean:
package org.littlestar.learning4.service;
import org.springframework.stereotype.Component;
@Component
public class HelloThymeleaf {
public String hello() {
return "Hello Thymeleaf";
}
}
编写一个controller, 处理/hello的请求
package org.littlestar.learning4.controller;
import org.littlestar.learning4.service.HelloThymeleaf;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;
@Controller
public class HelloController {
@Autowired HelloThymeleaf helloThymeleaf;
@RequestMapping("/hello")
public String requestHello(Model model) {
model.addAttribute("helloMsg", helloThymeleaf.hello());
return "hello";
}
}
最后编写对应的thymeleaf页面hello.html, 页面需要放在src/main/resources/templates目录下,一些静态资源(css, js, images), 则需要放在src/main/resources/static目录下。hello.html的页面内容:
<!DOCTYPE html>
<html xmlns:th="https://www.thymeleaf.org/">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
<link rel="stylesheet" th:href="@{/css/bootstrap.min.css}"></link>
</head>
<body>
<h2 th:text="'Hello, ' + ${helloMsg} + '!'">这里展示欢迎信息</h2>
</body>
</html>
编写Spring应用程序启动类: App4
package org.littlestar.learning4;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class App4 {
public static void main(String[] args) {
SpringApplication.run(App4.class, args);
}
}
执行App4,浏览器访问http://127.0.0.1:8080/hello,即可得到hello页面:
喵~, 以前大刀长枪舞个半边天,现在用Spring Boot随便几下就搞定了,太欺负人了。我们的hello.html页面时可以直接通过浏览器打开的,这非常方便前端设计人员,所见即所得,这是thymeleaf的最吸引人的地方。
使用jsp模板引擎
Spring Boot也是支持使用JSP模板的,但是对于内嵌的servlet服务有一些限制,Spring官方文档建议你避免使用JSP。
JSP Limitations
When running a Spring Boot application that uses an embedded servlet container (and is packaged
as an executable archive), there are some limitations in the JSP support.
• With Jetty and Tomcat, it should work if you use war packaging. An executable war will work
when launched with java -jar, and will also be deployable to any standard container. JSPs are
not supported when using an executable jar.
• Undertow does not support JSPs.
• Creating a custom error.jsp page does not override the default view for error handling. Custom
error pages should be used instead.
首先要在pom.xml中引入对JSP的支持:
<dependency>
<groupId>org.apache.tomcat.embed</groupId>
<artifactId>tomcat-embed-jasper</artifactId> <!-- 内置Tomcat对JSP的支持-->
</dependency>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>jstl</artifactId> <!-- JSTL支持 -->
</dependency>
<dependency>
<groupId>javax.servlet</groupId> <!-- JSP支持 -->
<artifactId>javax.servlet-api</artifactId>
</dependency>
为了避免干扰,我们将代码放在一个独立的org.littlestar.learning5包下。
写一个hello bean:HelloJSP
package org.littlestar.learning5.service;
import org.springframework.stereotype.Component;
@Component
public class HelloJSP {
public String hello() {
return "Hello JSP";
}
}
编写控制器
package org.littlestar.learning5.controller;
import org.littlestar.learning5.service.HelloJSP;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;
@Controller
public class HelloController {
@Autowired HelloJSP helloJsp;
@RequestMapping("/hello")
public String requestHello(Model model) {
model.addAttribute("helloMsg", helloJsp.hello());
return "hello";
}
}
编写页面JSP页面,首先我们需要创建一个src/main/webapp/目录,用于存放页面,我们将hello.jsp放在src/main/webapp/WEB-INF/view/目录下, 为了方便使用,我们可以把webapp目录设置为源代码目录:
静态资源(css, js, images), 则选择放在webapp的resources目录。如src/main/webapp/resources/css/bootstrap.min.css
hello.jsp
<%@ page contentType="text/html;charset=UTF-8" language="java"%>
<%@taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
<link rel="stylesheet" href="./resources/css/bootstrap.min.css" />
</head>
<body>
<h2>${msg}</h2>
</body>
</html>
JSP使用的是InternalResourceViewResolver视图解析器,我们需要通过配置文件对他进行配置,编辑Spring配置文件src/main/resources/application.properties,添加如下参数:
spring.mvc.view.prefix=/WEB-INF/view/
spring.mvc.view.suffix=.jsp
编写Spring应用程序启动类: App5
package org.littlestar.learning5;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class App5 {
public static void main(String[] args) {
SpringApplication.run(App5.class, args);
}
}
执行App5,浏览器访问http://127.0.0.1:8080/hello,即可得到jsp实现的hello页面: