第3章
SpringBoot
目前主流的互联网公司在开发Java项目时基本会使用SpringBoot快速构建应用,
相
比以前的项目,使用Spring开发需要一系列的配置,SpringBoot提供了Spring运行的默
认
配置,相当于将汽车手动挡改为自动挡,这种方式旨在让开发者能够更专注于业务实现。
本
7min
章从SpringBoot的实战集成入手,介绍集成SpringBoot的3种方式及SpringBoot底
层
运行的工作原理。希望读者能够对SpringBoot框架有一个清楚的认识,深入理解它的
底
层运行机制
。
3.自动配置/依赖管理
1
在古代的某个“Spring村(春风村)”,居民们过着平静的生活。随着时代的进步,人
们
对方便的生活方式的要求越来越高,为此,春风村推出了一种名为“春风吹又生”的新科技
,
让人们可以更轻松地完成每件事。SpringBoot是一个开放源码的架构,旨在为Spring程
序
的创建、配置和运行提供更简单、更高效的方法。它可以让Spring程序更容易创建、配置
和
运行,同时也让Spring程序更加容易管理。有了SpringBoot的“冲锋枪”,开发人员可以
更
快、更容易地完成事情。汤姆就是这样一个例子,他决定为自己的咖啡馆开发一款网络
软
件。他开始建立一个简单的Maven工程,很快就建立起了这个软件的基础结构,然后汤
姆
使用SpringBoot提供的自动设置功能设置应用中缺省的数据库连接和网络MVC,实现
了
一个完整的网络应用。SpringBoot也被称为Starters,它可以使从属关系管理变得更简单
。
有了Starters,Tom 可以简单地增加一个依赖性获取自己想要的类库和能力。SpringBoot
让汤姆在他的“咖啡屋”网络应用软件中变得更加容易工作,而不用去考虑那些烦琐的组
态
和从属关系管理。另外,“汤姆咖啡”网站的成功例子也使SpringBoot在春风村中变得
非
常有影响力。现在,SpringBoot已成为开发人员最喜欢的框架之一,它可以让开发人员
在
不同的应用中更容易地进行开发和部署
。
3.实战集成
2
快速构建一个SpringBoot项目,通常只需往pom 文件中添加依赖。本节将介绍如何
使用sprngbo-tre-prnspig-otdpednis、o.prng.tfrm 这3种方式
i-otsatraet、rnbo-eneceisiplao
1 08
快速集成SpringBoot项目。
3.2.1 使用spring-boot-starter-parent
SpringBoot官方提供的示例项目依赖于spring-boot-starter-parent进行依赖管理,但
在企业级微服务架构中,每个模块只能有一个parent,多个微服务间的继承关系可能导致扩
展性问题,开发者需要采取其他手段(如修改父类依赖项或通过import导入)实现。在
SpringBoot的各个发布版本中包含了许多默认版本的依赖项,对开发者而言是一个便利,
只需专注于核心功能的开发。
下面去证实这一说法:如何在SpringBoot项目中添加依赖。具体方法是新建一个继
承SpringBoot的项目,并在pom.xml文件中添加依赖,代码如下:
//第3 章/3.2.1 继承spring-boot-starter-parent 代码
org.springframework.boot
spring-boot-starter-parent
2.6.11
在IDEA 开发环境中,通过按住Ctrl键并同时单击spring-boot-starter-parent,可以进
入其对应的pom.xml文件进行查看。通过查看可知,spring-boot-starter-parent作为一个
父项目,它继承了spring-boot-dependencies作为其父依赖管理,代码如下:
//第3 章/3.2.1 继承spring-boot-dependencies 代码
org.springframework.boot
spring-boot-dependencies
2.6.11
在IDEA 开发环境中,按住Ctrl键并单击spring-boot-dependencies,即可进入其对应
的pom.xml文件进行查看。在该文件的properties部分,开发者可以发现许多与依赖管理
相关的版本号配置,spring-boot-dependencies管理版本号如图3-1所示。
这也是所有SpringBoot的Starter无须指定版本号的原因,但如果开发者不想使用默
认版本号,则可以在项目中使用property的方式覆盖原有依赖项。
本节提到,企业级开发很少使用spring-boot-starter-parent作为依赖管理,因为企业通
常定义自己的parent,因此,在这种情况下,继承spring-boot-starter-parent并不适用。为了
解决这个问题,开发者可以在dependencyManagement部分使用import的
方式进行依赖管理,代码如下:
1 09
图3-1 spring-boot-dependencies管理版本号
//第3 章/3.2.1 在dependencyManagement 里面导入import 代码
org.springframework.boot
spring-boot-starter-parent
2.6.11
pom
import
然而,这种方法也存在一定的限制,因为它无法直接覆盖原始的依赖项配置。为了解决
这个问题,开发者可以采用一种相对复杂的策略:将之前引入的依赖项移至自己的
dependencyManagement部分,并使用spring-boot-dependencies进行替换,然而,这种方法
在企业级开发中并不常见。
除了继承spring-boot-dependencies之外,spring-boot-starter-parent还添加了一些默
认配置,例如设置JDK版本、使用占位符@、指定编译和打包时使用的JDK 版本及将编码
设置为UTF-8。具体示例,代码如下:
//第3 章/3.2.1 spring-boot-starter-parent 默认配置代码
1.8
@
${java.version}
1 10
${java.version}
UTF-8
< project. reporting. outputEncoding > UTF - 8
此外,还设置了默认读取的配置文件目录和文件,以减少每个微服务都需要开发者自行
设置配置文件目录和文件的工作量,代码如下:
//第3 章/3.2.1 读取配置文件目录和文件代码
${basedir}/src/main/resources
true
**/application*.yml
**/application*.yaml
**/application*.properties
${basedir}/src/main/resources
**/application*.yml
**/application*.yaml
**/application*.properties
spring-boot-starter-parent还覆盖了spring-boot-dependencies中的某些插件。
3.2.2 使用spring-boot-dependencies
spring-boot-dependencies同样是通过继承parent和导入(import)实现的。
第1种方式是通过继承Parent实现,代码如下:
//第3 章/3.2.2 继承parent 代码
org.springframework.boot
spring-boot-dependencies
2.6.11
第2种方式是通过导入(import)的方式实现,代码如下:
//第3 章/3.2.2 导入import 代码
1 11
org.springframework.boot
spring-boot-dependencies
2.6.11
pom
import
在引用spring-boot-starter-web时,可以省略版本号,具体示例,代码如下:
org.springframework.boot
spring-boot-starter-web
以上所述的依赖项涉及Web模块,该模块包含大量相关依赖,例如Spring相关库和内
置的Tomcat服务器等。有了这些依赖,开发人员就能够使用SpringBoot进行Web开发。
SpringBoot的spring-boot-dependencies引入了许多插件。在此,将简要介绍3个关键
插件:maven-help-plugin插件用于获取帮助信息;maven-resources-plugin插件用于处理资
源文件;maven-compiler-plugin插件用于编译Java代码,具体示例,代码如下:
//第3 章/3.2.2 maven-help-plugin 插件代码
org.apache.maven.plugins
maven-help-plugin
${maven-help-plugin.version}
xml-maven-plugin插件是用于处理XML的Maven插件,代码如下:
//第3 章/3.2.2 xml-maven-plugin 插件代码
org.codehaus.mojo
xml-maven-plugin
${xml-maven-plugin.version}
build-helper-maven-plugin插件可以用来设置主源代码、测试源代码、主资源文件、测
试资源文件等目录,代码如下:
//第3 章/3.2.2 build-helper-maven-plugin 插件代码
org.codehaus.mojo
build-helper-maven-plugin
${build-helper-maven-plugin.version}
综上可得出结论,spring-boot-dependencies插件的主要作用是管理依赖项的版本号、
1 12
管理插件的版本号及引入辅助插件。
3.2.3 使用io.spring.platform
io.spring.platform 作为SpringBoot的基础平台,承担着继承spring-boot-starterparent
的角色。同时,spring-boot-starter-parent继承了spring-boot-dependencies,共同构
成了SpringBoot项目的根依赖管理。在日常开发中,开发者经常需要处理多个依赖项的
集成,很可能会遇到版本冲突或不兼容的问题。
为满足这种需求,一个重要目标是将已经过集成测试的依赖项整合在一起。由于这些
依赖项都经过了全面的集成测试,因此在使用过程中出现问题的概率相对较低。这也是
io.spring.platform 诞生的背景。
实现这一目标的方式是通过继承parent和使用import,以确保项目的依赖管理得到统
一和优化。这种方法有助于简化开发者的工作流程,减少潜在的问题,并提高应用程序的稳
定性和性能。
第1种方式是继承parent,代码如下:
//第3 章/3.2.3 继承parent 代码
io.spring.platform
platform-bom
Brussels-SR7
这种方式的缺点在于,需要明确地添加插件,因为它需要继承一些插件管理。以Spring
Boot为例,需要显式地添加插件,代码如下:
//第3 章/3.2.3 显式地添加plugin 代码
org.springframework.boot
spring-boot-maven-plugin
第2种方式是通过导入(import)实现,代码如下:
//第3 章/3.2.3 导入import 代码
io.spring.platform
platform-bom
Brussels-SR6
pom
1 13
import
SpringBoot已集成了许多开源框架,旨在帮助开发者简化第三方依赖管理,然而,在实
际开发过程中,仍有很多依赖未包含在内。在大型互联网项目中,各个模块之间的关系往往
错综复杂,维护工作可能变得枯燥乏味且具有较高的工作量。
为解决这一问题,io.spring.platform 应运而生,它有助于连接各个依赖。例如,假设开
发者需要升级某个依赖,只需更新相应的版本,无须担心版本兼容性问题。如今,一些大型
互联网项目会维护自己的基础项目platform。
3.3 手写一个简易版的SpringBoot
许多开发者渴望了解SpringBoot框架的内部运行机制,但由于阅读源码能力有限,难
以深入理解其底层工作原理。为帮助读者更好地理解SpringBoot框架,本节将通过手写
一个简易版本的SpringBoot来阐述其底层运行原理。在学习本节内容之前,建议读者先
熟悉SpringMVC的工作流程及SpringIOC的控制反转概念。
3.3.1 Java代码直接启动Tomcat
SpringBoot框架以内置的Tomcat作为其Web容器,为Web应用提供服务,这是
SpringBoot的一个显著特点。本节将通过一个简化的示例工程展示如何启动Tomcat。
1.工程介绍
先创建一个名为simple-springboot的父工程,然后构建两个模块:springboot-module和
user-module。springboot-module模块定义了一个自定义注解ExampleSpringBootApplication,
并创建了启动类ExampleSpringApplication。user-module模块在UserApplication中引入
了springboot-module模块的注解和启动类。最后,通过main方法运行user-module模块。
项目结构图如图3-2所示。
simple-springboot的父工程需要将springboot-module模块和user-module模块添加
到其依赖中。父工程的pom.xml文件示例,代码如下:
//第3 章/3.3.1 在simple-springboot 父工程将springboot-module 模块和user-module
//模块的依赖添加进来
xml version="1.0" encoding="UTF-8"? >
4.0.0
org.example
1 14
图3-2 项目结构图
simple-springboot
pom
1.0-SNAPSHOT
springboot-module
user-module
user-module模块的pom.xml文件需引入springboot-module模块的依赖,代码如下:
//第3 章/3.3.1 在user-module 模块的pom.xml 文件中引入springboot-module 模块的依赖
xml version="1.0" encoding="UTF-8"? >
simple-springboot
org.example
1.0-SNAPSHOT
4.0.0
user-module
1 15
org.example
springboot-module
1.0-SNAPSHOT
springboot-module模块引入的是Spring、Servlet及与Tomcat相关的依赖。其pom.xml
文件示例,代码如下:
//第3 章/3.3.1 springboot-module 模块引入了Spring、Servlet 及与Tomcat 相关的依赖
xml version="1.0" encoding="UTF-8"? >
simple-springboot
org.example
1.0-SNAPSHOT
4.0.0
springboot-module
org.springframework
spring-core
5.3.18
org.springframework
spring-context
5.3.18
org.springframework
spring-web
5.3.18
org.springframework
spring-aop
5.3.18
org.springframework
spring-webmvc
1 16
5.3.18
javax.servlet
javax.servlet-api
4.0.1
org.apache.tomcat.embed
tomcat-embed-core
9.0.60
2.自定义注解
参考SpringBoot框架,可以自定义一个注解,该注解作用于启动类上。以下是该注解
的示例,代码如下:
//第3 章/3.3.1 向启动类添加自定义注解
package com.example.springboot;
import java.lang.annotation.*;
/**
* 自定义注解:启动类注解
*/
@Target(ElementType.TYPE)//作用于类上面
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited//一个类用上了@Inherited 修饰的注解子类继承这个注解
public @interface ExampleSpringBootApplication {
}
3.自定义启动类
当浏览器发送请求时,需要启动Tomcat才能接受请求。以下示例用于展示如何启动
Tomcat,代码如下:
//第3 章/3.3.1 自定义启动类
package com.example.springboot;
import org.apache.catalina.*;
import org.apache.catalina.connector.Connector;
import org.apache.catalina.core.StandardContext;
import org.apache.catalina.core.StandardEngine;
import org.apache.catalina.core.StandardHost;
import org.apache.catalina.startup.Tomcat;
import org.springframework.context.annotation.Bean;
import org.springframework.web.context.WebApplicationContext;
import org.springframework.web.context.support.AnnotationConfigWebApplicationContext;
import org.springframework.web.servlet.DispatcherServlet;
1 17
import java.util.Map;
/**
* 自定义启动类
*/
public class ExampleSpringApplication {
public static void run(Class clazz){
startTomcat();
}
private static void startTomcat(){
Tomcat tomcat =new Tomcat();
Server server =tomcat.getServer();
Service service =server.findService("Tomcat");
Connector connector =new Connector();
connector.setPort(8080);
Engine engine =new StandardEngine();
engine.setDefaultHost("localhost");
Host host =new StandardHost();
host.setName("localhost");
String contextPath ="";
Context context =new StandardContext();
context.setPath(contextPath);
context.addLifecycleListener(new Tomcat.FixContextListener());
host.addChild(context);
engine.addChild(host);
service.setContainer(engine);
service.addConnector(connector);
try {
tomcat.start();
} catch (LifecycleException e) {
e.printStackTrace();
}
}
}
4.自定义启动类
user-module模块的启动类应该使用自定义的注解和自定义的启动类,在main方法中
运行run方法。以下是该启动类的示例,代码如下:
//第3 章/3.3.1 user-module 模块的启动类
package com.example.user;
import com.example.springboot.ExampleSpringApplication;
import com.example.springboot.ExampleSpringBootApplication;
@ExampleSpringBootApplication
public class UserApplication {
public static void main(String[] args) {
ExampleSpringApplication.run(UserApplication.class);
}
}
1 18
5.运行项目
启动项目后,可以在控制台查看日志,如图3-3所示。
图3-3 控制台日志
6.请求处理流程
既然Tomcat已经成功启动,那么接下来就需要处理请求了。以下是处理请求的示例,
代码如下:
tomcat.addServlet(contextPath,"dispatcher",new
DispatcherServlet(webApplicationContext));
context.addServletMappingDecoded("/*","dispatcher");//拦截所有请求给
//DispatcherServlet 处理
对于熟悉Spring MVC 工作流程的开发人员可能会立即联想到前端控制器
DispatcherServlet。在该框架中,所有的请求都会先经过DispatcherServlet进行处理。
注意:为了防止一些读者不熟悉SpringMVC的工作流程,本节通过一个小故事进行阐述。
在丰富多彩的互联网世界中,有一个名叫SpringMVC的小镇,其居民热情好客。这个小镇
充满生机和活力,居民和谐相处,共同分享美好生活。某一天,一位名叫“请求”的游客慕名
而来,想要参加一场特别的盛会。在这场盛会中,SpringMVC小镇的居民需要设计出最美
1 19
的舞蹈和最华丽的舞池,以期望能够迎接这场难得的盛会。
作为小镇的前端控制,DispatcherServlet以谨慎的态度接待请求。当收到请求时,
DispatcherServlet认识到其重要性,首先对请求进行详尽解析,将URL转换为URI,并调用
HandlerMapping将请求与相关的控制器和拦截器联系在一起。DispatcherServlet将联系
在一起的对象封装为HandlerExecutionChain对象,并选择一个适宜的HandlerAdapter处
理请求。这位HandlerAdapter犹如专业的舞蹈教练,娴熟地将请求转换为优美的舞蹈。
在处理请求的过程中,HandlerAdapter承担着关键的辅助工作,如数据转换、数据验证
和消息转换,确保游客的请求以最佳形式呈现。经过一番努力,Handler完成任务,向
DispatcherServlet返回一个包含视图名的ModelAndView 对象,表明舞池和舞蹈已经准备
就绪。DispatcherServlet根据ModelAndView对象中的视图名,选择合适的ViewResolver
解析视图。这位ViewResolver犹如熟练掌握各种舞蹈的舞蹈指导,能够将游客的请求转换
为各种美丽的舞蹈画面。
在解析视图名的过程中,ViewResolver找到了一个最匹配的视图———一个名为View
的小镇居民。View利用模型数据(如舞池和舞蹈),渲染出美丽的舞蹈画面。在渲染过程
中,View与模型数据完美结合,精心布置了舞池,编排了一支优雅的舞蹈。游客们被这美丽
的舞蹈所吸引,纷纷拿出相机记录下这难忘的时刻。渲染结束后,View 将渲染后的舞蹈画
面传递给DispatcherServlet。DispatcherServlet将这些画面呈现在游客们的眼前,引发他
们的欢呼雀跃,感叹小镇居民们的才华与热情。舞会结束后,游客们与SpringMVC小镇的
居民们建立了深厚的友谊,这段美好的回忆成为永恒的佳话。从此,SpringMVC小镇名声
远扬,吸引了越来越多的游客,SpringMVC的故事,也成了互联网世界中一段不朽的传奇。
在本流程中,DispatcherServlet会对请求的URL 进行解析,并查找对应的Controller
方法。事实上,每个Controller都是Spring容器中的一个Bean,因此,为了处理请求,需要
将一个Spring容器传递给DispatcherServlet。那么,这个容器从何而来呢?
通过查阅SpringMVC源代码,找到DispatcherServlet的有参数构造方法,代码如下:
public DispatcherServlet(WebApplicationContext webApplicationContext) {
super(webApplicationContext);
this.setDispatchOptionsRequest(true);
}
在这种方法中,需要使用WebApplicationContext容器。了解应该使用哪种容器后,便
可以直接创建一个Spring容器,并将UserApplication启动类注册进来,代码如下:
//第3 章/3.3.1 自定义启动类
package com.example.springboot;
import org.apache.catalina.*;
import org.apache.catalina.connector.Connector;
import org.apache.catalina.core.StandardContext;
import org.apache.catalina.core.StandardEngine;
import org.apache.catalina.core.StandardHost;
1 20
import org.apache.catalina.startup.Tomcat;
import org.springframework.context.annotation.Bean;
import org.springframework.web.context.WebApplicationContext;
import org.springframework.web.context.support.AnnotationConfigWebApplicationContext;
import org.springframework.web.servlet.DispatcherServlet;
import java.util.Map;
/**
* 自定义启动类
*/
public class ExampleSpringApplication {
public static void run(Class clazz){
//创建一个Spring 容器
AnnotationConfigWebApplicationContext webApplicationContext = new
AnnotationConfigWebApplicationContext();
//注册启动类
webApplicationContext.register(clazz);
webApplicationContext.refresh();
//启动Tomcat
startTomcat(webApplicationContext);
}
private static void startTomcat(WebApplicationContext webApplicationContext){
Tomcat tomcat =new Tomcat();
Server server =tomcat.getServer();
Service service =server.findService("Tomcat");
Connector connector =new Connector();
connector.setPort(8080);
Engine engine =new StandardEngine();
engine.setDefaultHost("localhost");
Host host =new StandardHost();
host.setName("localhost");
String contextPath ="";
Context context =new StandardContext();
context.setPath(contextPath);
context.addLifecycleListener(new Tomcat.FixContextListener());
host.addChild(context);
engine.addChild(host);
service.setContainer(engine);
service.addConnector(connector);
tomcat.addServlet(contextPath,"dispatcher",new DispatcherServlet
(webApplicationContext));
//拦截所有请求,交给DispatcherServlet 处理
context.addServletMappingDecoded("/*","dispatcher");
try {
tomcat.start();
} catch (LifecycleException e) {
e.printStackTrace();
}
}
}
1 21
这样,在启动Tomcat时,容器会解析UserApplication类,并解析其上的自定义注解
ExampleSpringBootApplication,然后开发者将@ ComponentScan 注解添加到
ExampleSpringBootApplication中,代码如下:
//第3 章/3.3.1 自定义注解
package com.example.springboot;
import org.springframework.context.annotation.ComponentScan;
import java.lang.annotation.*;
/**
* 自定义注解:启动类注解
*/
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
//@Inherited 是一个标识,用来修饰注解
@Inherited
//扫描UserApplication 类所在的包路径
@ComponentScan
public @interface ExampleSpringBootApplication {
}
由于没有指定具体的扫描路径,所以容器会扫描ExampleSpringBootApplication注解
作用的类UserApplication,并解析其包路径com.example.user。进一步将扫描范围扩大到
该包下的所有Controller。
为了测试上述处理过程,在user-module模块下创建一个TestController,并编写一个
简单的接口,代码如下:
//第3 章/3.3.1 TestController 类
package com.example.user.controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class TestController {
@GetMapping("/test")
public String test(){
return "test";
}
}
运行项目,通过浏览器访问接口校验代码的正确性,如图3-4所示。
为了更好地理解整个流程,下面通过脑图进行总结,如图3-5所示。
3.3.2 多态实现WebServer
在3.3.1节中,SpringBoot使用的是一种固定的方式来启动Tomcat,无法切换到其他
Web容器,例如Jetty。假设项目需要实现切换到Jetty容器的功能,应该如何实现呢?
首先,提供Tomcat和Jetty 的依赖,然后根据依赖情况来确定项目中使用的是
1 22
图3-4 浏览器请求接口
图3-5 请求处理流程脑图
TomcatWebServer还是JettyWebServer的Bean,进而决定使用哪种Web容器进行执行。
在确保项目可以正常切换Web容器之后,再进行代码优化。这就是实现这一功能的基本思
路。下面对3.3.1节的代码进行修改。
1.引入Jetty依赖
在引入Tomcat依赖的springboot-module模块中,若要引入Jetty依赖,则需要在依赖
中添加配置true,表示该依赖不会被传递给调用服务,即usermodule
服务。由于springboot-module模块需要支持多种Web容器(Tomcat/Jetty),所以
调用端只能使用其中一种,否则会出现错误,代码如下:
//第3 章/3.3.2 引入Tomcat 和Jetty 依赖
org.apache.tomcat.embed
tomcat-embed-core
9.0.60
1 23
org.eclipse.jetty
jetty-server
9.4.48.v20220622
true
Jetty依赖应添加至springboot-module模块中,而user-module模块仅依赖于springbootmodule
模块,并默认使用Tomcat依赖,因此,Jetty依赖无法传递至user-module模块。如
果user-module模块需要使用Jetty依赖,就需要在springboot-module模块中排除Tomcat
依赖,并添加Jetty依赖,代码如下:
//第3 章/3.3.2 排除Tomcat 依赖
org.example
springboot-module
1.0-SNAPSHOT
org.apache.tomcat.embed
tomcat-embed-core
org.eclipse.jetty
jetty-server
9.4.48.v20220622
2.创建WebServer接口
已知该项目需要同时使用Tomcat和Jetty容器,为了避免在后续引入其他Web容器
时产生混淆,开发者定义了一个WebServer接口,用于抽象出Web容器的启动功能,代码
如下:
//第3 章/3.3.2 创建WebServer 接口
package com.example.springboot;
import org.springframework.web.context.WebApplicationContext;
/**
* Web 服务接口
*/
public interface WebServer {
public void start(WebApplicationContext applicationContext);
}
1 24
3.创建TomcatWebServer实现类
将startTomcat方法的实现移至实现类的start方法中,代码如下:
//第3 章/3.3.2 创建TomcatWebServer 实现类
package com.example.springboot;
import org.apache.catalina.*;
import org.apache.catalina.connector.Connector;
import org.apache.catalina.core.StandardContext;
import org.apache.catalina.core.StandardEngine;
import org.apache.catalina.core.StandardHost;
import org.apache.catalina.startup.Tomcat;
import org.springframework.web.context.WebApplicationContext;
import org.springframework.web.servlet.DispatcherServlet;
/**
* Web 服务:启动Tomcat 相关代码
*/
public class TomcatWebServer implements WebServer{
@Override
public void start(WebApplicationContext applicationContext) {
System.out.println("============启动Tomcat=============");
Tomcat tomcat =new Tomcat();
Server server =tomcat.getServer();
Service service =server.findService("Tomcat");
Connector connector =new Connector();
connector.setPort(9081);
Engine engine =new StandardEngine();
engine.setDefaultHost("localhost");
Host host =new StandardHost();
host.setName("localhost");
String contextPath ="";
Context context =new StandardContext();
context.setPath(contextPath);
context.addLifecycleListener(new Tomcat.FixContextListener());
host.addChild(context);
engine.addChild(host);
service.setContainer(engine);
service.addConnector(connector);
tomcat.addServlet(contextPath, "dispatcher", new
DispatcherServlet(applicationContext));
context.addServletMappingDecoded("/*", "dispatcher");
try {
tomcat.start();
} catch (LifecycleException e) {
e.printStackTrace();
}
}
}
1 25
4.创建JettyWebServer实现类
Jetty启动过程,代码如下:
//第3 章/3.3.2 创建JettyWebServer 实现类
package com.example.springboot;
import org.springframework.web.context.WebApplicationContext;
/**
* Web 服务:启动Jetty 相关代码
*/
public class JettyWebServer implements WebServer{
@Override
public void start(WebApplicationContext applicationContext) {
System.out.println("启动Jetty");
//省略Jetty 启动过程的代码,不重点讲解
}
}
5.改造ExampleSpringApplication类
获取Tomcat或者Jetter容器,代码如下:
//第3 章/3.3.2 改造ExampleSpringApplication 类
package com.example.springboot;
import org.springframework.web.context.WebApplicationContext;
import org.springframework.web.context.support.AnnotationConfigWebApplicationContext;
import java.util.Map;
/**
* 自定义启动类
*/
public class ExampleSpringApplication {
public static void run(Class clazz){
//创建一个Spring 容器AnnotationConfigWebApplicationContext 支持SpringMVC
AnnotationConfigWebApplicationContext applicationContext =new
AnnotationConfigWebApplicationContext();
applicationContext.register(clazz);//注册一个类进来
applicationContext.refresh();
//启动Web 服务器(Tomcat、Jetty)
WebServer webServer =getWebServer(applicationContext);
webServer.start(applicationContext);
}
/**
* 获取Web 服务:获取Tomcat 或者Jetty 容器
* @param applicationContext
* @return
*/
private static WebServer getWebServer(WebApplicationContext applicationContext) {
Map< String, WebServer> beansOfType = applicationContext.getBeansOfType
(WebServer.class);
//两个都没有定义: UserApplication 类中没有定义TomcatWebServer 或者
//JettyWebServer
1 26
if (beansOfType.size() ==0) {
throw new NullPointerException();
}
//定义了两个: UserApplication 类中有定义TomcatWebServer 和JettyWebServer
//会报错
if (beansOfType.size() >1) {
throw new IllegalStateException();
}
//定义第1 个: UserApplication 类中有定义TomcatWebServer 或者JettyWebServer
//其中一个
return beansOfType.values().stream().findFirst().get();
}
}
6.改造UserApplication类
在UserApplication类中只能定义TomcatWebServer或JettyWebServer其中之一,代
码如下:
//第3 章/3.3.2 改造UserApplication 类
package com.example.user;
import com.example.springboot.ExampleSpringApplication;
import com.example.springboot.ExampleSpringBootApplication;
import com.example.springboot.JettyWebServer;
import com.example.springboot.TomcatWebServer;
import org.springframework.context.annotation.Bean;
/**
* TomcatWebServer 和JettyWebServer 只能定义其中一个。弊端:比较麻烦,需要有一个自动
的配置类识别我要用什么类型的Web 服务容器。解决方案:WebServerAutoConfiguration
*/
@ExampleSpringBootApplication
public class UserApplication {
//@Bean
//public TomcatWebServer tomcatWebServer(){
//return new TomcatWebServer();
//}
@Bean
public JettyWebServer jettyWebServer(){
return new JettyWebServer();
}
public static void main(String[] args) {
ExampleSpringApplication.run(UserApplication.class);
}
}
7.运行项目
启动项目并查看日志,如图3-6所示。
读者可以注释JettyWebServer或TomcatWebServer,或同时使用它们,以检查功能是
否可实现。