关于WebApplicationType

Spring的主要开发场景是Web应用,我们将上例改成一个Web应用, 我们首先通过Sping Tools插件添加一个"Spring Web"依赖。

springboot启动添加vm springboot添加webapp_springboot启动添加vm


添加完成后,插件在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"启动器。

springboot启动添加vm springboot添加webapp_Web_02


该操作实际上是在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页面:

springboot启动添加vm springboot添加webapp_spring_03


喵~, 以前大刀长枪舞个半边天,现在用Spring Boot随便几下就搞定了,太欺负人了。我们的hello.html页面时可以直接通过浏览器打开的,这非常方便前端设计人员,所见即所得,这是thymeleaf的最吸引人的地方。

springboot启动添加vm springboot添加webapp_springboot启动添加vm_04

使用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目录设置为源代码目录:

springboot启动添加vm springboot添加webapp_spring_05


静态资源(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页面:

springboot启动添加vm springboot添加webapp_spring boot_06