基于Spring Boot 的 HTTP服务构建

本文最后更新于 2025年7月17日 晚上

如何构建一个Web服务

架构与实现

web服务本身是一种 基于BS(Browser-Server)架构的服务提供模式。其中包含了

Browser(Web Proxy): 面向客户的本地应用,提供连接管理,HTTP请求解析,本地存储等功能

HTTP : 客户与服务器间通信的应用层协议,用于规范 用户和服务器间的请求/响应格式和响应逻辑

Server : 运行于远端的监听程序,监听来自网络HTTP请求,并根据请求内心进行对应的动态或静态资源响应

数据过程

数据通信传输过程

MAC(Media Access Controll): 用于设备入网的唯一标识信息。(定义唯一性)

IP(Internet Protocol): 用于网络寻址的设备网络信息。(入网后的可识别地址方便其他设备更好的查询到目标设备)

TCP(Transmission Control Protocol): 用于承载上层的信息传输,它是可靠的(即提供了相关的机制保证了数据的一致性(但是无法解决网络故障导致的数据不一致)),流式的(通过滑动窗口完成的服务端-设备端的 数据写入/写出过程)。

UDP (User Datagram Protocol): 同样用于承载上层信息,但是它是不可靠的,只负责对上层数据进行简单封装,将其转发到对端,包式的(以数据包的形式进行数据传输)

SOCKET (套接字): 用于完成对网络层的抽象,使上层只需要通过传递 网络协议族/网络协议/具体协议信息。 就可以完成网络连接的创建,它通过四元组来特别定义某一个具体的连接(源IP,源端口,目的IP,目的端口)。

I/O : 由SOCKET 接收/写入的内容会被存放在内核缓冲区,最终被通过网卡转发出去或被复制写入用户空间的缓冲区。这时受到的内容是一个字节数组。后续的应用层处理过程就需要对这些字节信息进行解释。

应用层处理

如上所属。后续的处理过程主要是如何将接收到的字节数组进行反序列化的过程。

所以在现代服务器的实现和处理上 一般会分为两阶段

1.将字节数组 反序列化为符合目标协议的序列化内容 (如json)

2.对序列化内容进行识别处理

这里分别考虑 Java和Python的两种实现

Python的web 规范——WSGI

1
2
3
def application(environ, start_response):
...
return [b"response body"]

通过wsgi规范,我们可以只关注 传递过来的 environ 即对请求的对象化封装和一个回调方法start_response , 这样只需要专注于处理请求中的内容,而不必再去考虑具体的解析过程。

Java的web规范 ——— Servlet

1
2
3
4
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) {
// 处理 GET 请求
}

servlet 接口定义了 http的处理过程, 接收一个HttpServletRequest 对象,并最终响应

一个HttpServletResponse 返回对象。然后 Servlet的容器(如tomcat,jetty)就可以遵守这个规范完成前置和后续的信息处理。

Spring Boot的HTTP服务构建

Spring 相比传统的Servlet 实现-装配过程提供了

  1. 内置Servlet容器。通过集成Servlet容器,到springboot 可以直接启动服务作为HTTP服务,不再需要打包为war包安装到servlet容器中
  2. 提供了丰富的过程化管理,通过接口实现可以完成对基础HTTP服务的功能扩展
  3. 提供了集成到外部sevlet容器的,扩展方案, 方便多场景下部署

目录结构

一个基础的Spring Boot Web应用目录结构 :

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
--- java  
--- com.codfish.springweb
--- controller
//接收客户端请求(HTTP、RPC等)
//参数校验与转换
//调用 Service 层处理业务逻辑
//将业务处理结果封装为响应结果返回(如 JSON、XML)
--- service
//封装业务逻辑(处理、计算、组合等)
//协调多个 DAO 或远程服务调用
//处理事务管理(如增删改)
--- repository
//执行增删改查操作(CRUD)
//屏蔽底层数据源的复杂性
--- entity
// 自配置类
--- resources
--- application.properties
// 放置服务所需的配置项信息

请求处理过程

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
浏览器请求

前端控制器 DispatcherServlet(统一入口)

HandlerMapping:查找匹配的控制器和拦截器链

HandlerInterceptor(前置拦截器 preHandle)

Controller(业务控制器)

Service(业务逻辑)

Repository/DAO(数据访问层)

Controller 返回 ModelAndView 或 JSON 对象

HandlerInterceptor(后置拦截器 postHandle/afterCompletion)

ViewResolver(视图解析器)

视图层渲染(JSP/Thymeleaf/Freemarker 等)或直接返回 JSON

DispatcherServlet 响应客户端

数据类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
@Component
public class User {
private String name;
private Integer id ;
private String location ;

public User(Integer id,String name,String location){
this.id = id;
this.name = name;
this.location = location;
}
public User(String name,String location){
this.name = name;
this.location = location;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getLocation() {
return location;
}
public void setLocation(String location) {
this.location = location;
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
package entity;

public class Result<T> {
private Integer code;
private String message;
private T data;

public Integer getCode() {
return code;
}

public void setCode(Integer code) {
this.code = code;
}

public String getMessage() {
return message;
}

public void setMessage(String message) {
this.message = message;
}

public T getData() {
return data;
}

public void setData(T data) {
this.data = data;
}

public Result(Integer code, String message, T data){
this.code = code;
this.message = message;
this.data = data;
}
public Result(Integer code, String message){
this.code = code;
this.message = message;
}
public Result(){

}

@Override
public String toString(){
return "Result{" +
"code=" + code +
", message='" + message + '\\'' +
", data=" + data +
'}';
}
}

Controller

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
@RestController
public class OrderController {

private final RestTemplate restTemplate;
// 定义一个 restTemplate 对象来收发HTTP请求
public OrderController(RestTemplateBuilder restTemplateBuilder) {
this.restTemplate = restTemplateBuilder.build();
// 通过构造器获取restTemplate对象
}

@RequestMapping("/order")
// 绑定请求路径
public String order(){
String url = "<http://localhost:8085/user/1>";
Result forobject = this.restTemplate.getForObject(url, Result.class,1);
// 使用 getForObject 将响应体转换为一个传入的对象结构
return forobject.getData().toString();
}
@RequestMapping("/add")
public String add(){
User user = new User("codfish","hangzhou");
ResponseEntity<Result> responoseentity = this.restTemplate.postForEntity("<http://localhost:8085/user/add",user,Result.class">);
// 传输一个携带包头,响应状态码,响应体信息的结构
return responseentity.getBody().getData().toString();
}

@RequestMapping("/put")
public String put(){
User user = new User(1,"codfish","hangzhou");
HttpEntity<User> httpEntity = new HttpEntity<>(user);
// 创建一个 http 请求对象。解析user为json
ResponseEntity<Result> resultResponseEntity = restTemplate.exchange("<http://localhost:8085/user/>"+user.getId(), HttpMethod.PUT,httpentity,Result.class)
// 使用exchange 裸发送http 请求,指定 URL,请求方法,以及响应体中的json 转换定义。
return responseEntity.getBody().getData().toString();
}
}

// 单元测试中使用 TestRestTemplate 创建对象

@SpringBootTest()
class Application{
@Test
void contextLoads(){
TestRestTemplate restTemplate = new TestRestTemplate();
ResponseEntity<Result> resultResponseEntity = restTemplate.exchange(url,HTTP.get,result.class);
return resultResponseEntity.getBody().toString();

}
}

应用于HTTP服务的一些注解

@Controller 标识一个类为控制器组件,返回的是视图

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
@Controller
@RequestMapping("/codfish")
public class CodfishController {
@GetMapping("/test")
public String test() {
return "codfish";
}
} // 这里不会响应codfish 字段,而是查询对应的codfish 视图

@Component
public class Codfish implements View {
@Override
public String getContentType() {
return "text/html";
}

@Override
public void render(Map<String, ?> model, HttpServletRequest request, HttpServletResponse response) throws Exception {
response.getWriter().write("<html><body><h1>Codfish</h1></body></html>");
}
} // 视图实现

@RestController 是 @Controller + @ResponseBody 的缩写,用于构建 RESTful API,方法返回数据

1
2
3
4
5
6
7
8
@RestController
@RequestMapping("/codfish")
public class CodfishController {
@GetMapping("/test")
public String test() {
return "codfish";
}
} // 最终将字符串"codfish" 作为返回响应

@RequestMapping 用于映射请求路径和方法(支持 GET/POST/PUT/DELETE 等)

1
2
3
4
5
6
7
8
@Controller
@RequestMapping("/codfish") // 映射 <http://x.x.x.x/codfish>
public class CodfishController {
@RequestMapping(method = RequestMethod.GET ,"/test") // 映射 <http://x.x.x.x/codfish/test> GET
public String test() {
return "codfish";
}
}

@GetMapping 简化版 @RequestMapping(method = RequestMethod.GET)

1
2
3
4
5
6
7
8
@Controller
@RequestMapping("/codfish") // 映射 <http://x.x.x.x/codfish>
public class CodfishController {
@GetMapping("/test") // 映射 <http://x.x.x.x/codfish/test> GET
public String test() {
return "codfish";
}
}

@PostMapping 简化版 @RequestMapping(method = RequestMethod.POST)
@PutMapping 简化版 @RequestMapping(method = RequestMethod.PUT)
@DeleteMapping 简化版 @RequestMapping(method = RequestMethod.DELETE)
@PatchMapping 简化版 @RequestMapping(method = RequestMethod.PATCH)

@RequestParam 绑定请求参数到方法参数(通常用于 GET/POST 表单字段)

1
2
3
4
@GetMapping("/search")
public String search(@RequestParam("keyword") String keyword) {
return "搜索关键字是:" + keyword;
}

@PathVariable 绑定 URI 路径中的变量

1
2
3
4
@GetMapping("/user/{id}")
public String getUser(@PathVariable("id") Long userId) {
return "用户ID:" + userId;
}

@RequestBody 将请求体 JSON/XML 数据绑定到方法参数(通常用于 POST/PUT)

1
2
3
4
@PostMapping("/user")
public String createUser(@RequestBody User user) {
return "用户名:" + user.getName();
}

@ResponseBody 将返回值作为响应体输出,不经过视图解析器

1
2
3
4
5
@GetMapping("/hello")
@ResponseBody
public String sayHello() {
return "Hello, World!";
}

@ModelAttribute 用于绑定表单提交的对象数据,也可用于提前在方法调用前处理模型数据

1
2
3
4
@PostMapping("/register")
public String register(@ModelAttribute User user) {
return "注册用户:" + user.getName();
}

@RequestHeader 绑定请求头字段

1
2
3
4
@GetMapping("/agent")
public String userAgent(@RequestHeader("User-Agent") String agent) {
return "你的浏览器信息是:" + agent;
}

@CookieValue 绑定 Cookie 中的值

1
2
3
4
@GetMapping("/showCookie")
public String getCookie(@CookieValue("token") String token) {
return "Cookie中的Token值为:" + token;
}

@RequestPart 用于处理 multipart/form-data 请求中的文件或字段

1
2
3
4
@PostMapping("/upload")
public String uploadFile(@RequestPart("file") MultipartFile file) {
return "上传文件名:" + file.getOriginalFilename();
}

@ResponseStatus 自定义响应状态码(如返回 201、404 等)

1
2
3
4
5
@ResponseStatus(HttpStatus.NOT_FOUND)
@GetMapping("/notfound")
public void notFound() {
// 返回 404
}

@ExceptionHandler 处理控制器层抛出的异常(单个异常处理)

1
2
3
4
@ExceptionHandler(ArithmeticException.class)
public ResponseEntity<String> handleArithmetic(Exception e) {
return ResponseEntity.status(400).body("出现数学错误: " + e.getMessage());
}

@ControllerAdvice 全局异常处理器、数据绑定、模型属性注入等的集中配置组件

1
2
3
4
5
6
7
@ControllerAdvice
public class GlobalControllerAdvice {
@ExceptionHandler(NullPointerException.class)
public String handleNPE() {
return "error/npe"; // 跳转视图页面
}
}

@RestControllerAdvice @ControllerAdvice + @ResponseBody,用于构建 REST 风格全局处理

1
2
3
4
5
6
7
8
9
@RestControllerAdvice
public class GlobalRestAdvice {

@ExceptionHandler(RuntimeException.class)
public String handleRuntime(RuntimeException ex) {
return "全局异常:" + ex.getMessage();
}
}

@SessionAttributes 将模型属性存入 Session 范围中

1
2
3
4
5
6
7
8
9
10
11
@Controller
@SessionAttributes("user")
public class SessionController {

@GetMapping("/store")
public String store(Model model) {
model.addAttribute("user", new User("Tom", 20));
return "ok";
}
}

@SessionAttribute 从 Session 中获取属性绑定到方法参数

1
2
3
4
@GetMapping("/getUser")
public String getUserFromSession(@SessionAttribute("user") User user) {
return "从Session中获取到用户:" + user.getName();
}

@RequestAttribute 绑定 request.setAttribute 设置的属性到方法参数

1
2
3
4
@GetMapping("/useAttr")
public String useAttr(@RequestAttribute("attr") String value) {
return "请求中的属性值:" + value;
}

@InitBinder 初始化数据绑定器,用于自定义参数绑定格式(如日期格式转换)

1
2
3
4
5
@InitBinder
public void initBinder(WebDataBinder binder) {
binder.registerCustomEditor(Date.class, new CustomDateEditor(
new SimpleDateFormat("yyyy-MM-dd"), false));
}

拦截器

在SpringMVC 中可以通过实现 HandlerInterceptor接口创建一个拦截器。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
@Component
public class TimeInterceptor implements HandlerInterceptor {
Logger logger = LoggerFactory.getLogger(TimeInterceptor.class);
LocalDateTime startTime;
LocalDateTime endTime;
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
startTime = LocalDateTime.now();
logger.info("请求" + request.getRequestURI()+"开始执行"+startTime+"时间");
return true;
}
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, @Nullable ModelAndView modelAndView) throws Exception {
endTime = LocalDateTime.now();
}

@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
Duration duration = Duration.between(startTime, endTime);
long l = duration.toMillis();
logger.info("请求" + request.getRequestURI()+"执行时间"+ l+"毫秒");
}

}

再通过实现WebMvcConfigurer 接口完成注册过程

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
@Component
public class MyWebMvcConfigurer implements WebMvcConfigurer {
@Override
public void addViewControllers(ViewControllerRegistry registry){
registry.addViewController("/tuling").setViewName("hello");
}

@Override
public void addInterceptors(InterceptorRegistry registry){
registry.addInterceptor(new TimeInterceptor())
.addPathPatterns("/**")
.excludePathPatterns("/pages/**");
registry.addInterceptor(new LocaleChangeInterceptor())
.addPathPatterns("/**");
}

@Bean
public LocaleResolver localeResolver(){
CookieLocaleResolver localeResolver = new CookieLocaleResolver();
localeResolver.setCookieMaxAge(3600);
localeResolver.setCookieName("locale");
return localeResolver;
}
}

注册消息转换器

Spring MVC 中允许 JSON 或XML 数据类型的请求格式可以自动转换为Java Bean对象

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
@Configuration(proxyBeanMethods = false)
class JacksonHttpMessageConvertersConfiguration{
@Configuration(proxyBeanMethods = false)
@ConditionalOnClass(ObjectMapper.class)
@ConditionalOnBean(ObjectMapper.class)
@ConditionalOnProperty(name = HttpMessageConvertersAutoConfiguration.PREFERRD_MAPPER_PROPERTY,
havingValue = "jackson", matchIfMissing = true )
static class MappingJackSon2HttpMessageConverterConfiguration{
@Bean
@ConditionalOnMissingBean(value = MappingJackson2HttpMessageConverter.class ,
ignoredType = {
"org.springframework.hateoas.server.mvc.TypeConstrainedMappingJackson2HttpMessageConverter",
"org.springframework.data.rest.webmvc.alps.AplsJsonHttpMessageConverter"})
MappingJackson2HttpMessageConverter mappingJackson2HttpMessageConverter(ObjectMapper objectMapper){
return new MappingJackson2HttpMessageConverter(objectMapper);
}
}
@Configuration(proxyBeanMethods = false)
@ConditionalOnClass(XmlMapper.class)
@ConditionalOnBean(Jaconson2ObjectMapperBuilder.class)
protected static class MappingJackson2XmlHttpMessageConverterConfiguration{
@Bean
@ConditionOnMissingBean
public MappingJackson2XmlHttpMessageConverter mappingJackson2XmlHttpMessageConverter(
Jackson2ObjectMapperBuilder builder){
return new MappingJackson2XmlHttpMessageConverter(builder.createXmlMapper(true).build());
}
}
}

注册类型转换器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
@Configuration
public class WebConfig implements WebMvcConfigurer{
@Override
public void addFormatters(FormatterRegistry registry){
registry.addCoverter(new CustomConverter());
}
}

@Slf4j
public class CustomConverter implements Converter<String,String>{
@Override
public String convert(String source){
if(StringUtils.isNotEmpty(source)){
source = source.trim();
}
return source ;
}
}

注册MVC组件

静态注册 Servlet

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public class RegisterServlet extends HttpServlet{
@Override
protected void service(HttpServletRequest req, HttpServletResponse resp) throws IOException {
String name = getServletConfig().getInitParameter("name");
String sex = getServletConfig().getInitParameter("sex");

resp.getOutputStream().println("name is" + name);
resp.getOutputStream().println("sex is" + sex);
}
}

//创建Bean对servlet进行注册

@Bean
public ServletRegistrationBean registerServlet(){
ServletRegistrationBean servletRegistrationBean = new ServletRegistrationBean(
new RegisterServlet(),"/registerServlet");
servletRegistrationBean.addInitParameter("name","javastack");
servletRegistrationBean.addInitParameter("sex", "male");
return servletRegistrationBean;
)

}

动态注册 Servlet

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
public class InitServlet extends HttpServlet{
@Override
protected void service(HttpServletRequest req, HttpServletResponse reps) throws IOException{
String name = getServletConfig().getInitParameter("name");
String sex = getServletConfig().getInitParameter("sex");

resp.getOutputStream().println("name is "+ name);
resp.getOutputStream().println("sex is " + sex);
}
}

@Component
public class ServetletConfig implements ServletContextInitializer{
@Override
public void onStartup(ServletContext servletContext){
ServletRegistration initServlet = servletContext
.addServlet("initServlet", InitServlet.class);
initServlet.addMapping("/initServlet");
initServlet.setInitParameter("name","initServlet");
initServlet.setInitParameter("sex","male");
}
}

@Bean
public ServletContextInitializer servleContextInitializer(){
return (servletContext) -> {
ServletRegistration initServlet = servletContext.addServlet("initServlet", InitServlet.class);
initServlet.addMapping("/initServlet");
initServlet.setInitParameter("name","initServlet");
initServlet.setInitParameter("sex","male");
}
}

异常处理

使用ControllerAdvice进行全局异常处理

1
2
3
4
5
6
7
8
@ControllerAdvice
public class GlobalExceptionHandler{
@ResponseBody
@ExceptionHandler(value = { Exception.class })
public ResponseEntity<?> handleException(HttpServletRequest request, Throwable ex){
return new ResponseEntity<>("global exception", HttpStatus.OK);
}
}

使用视图或restapi接口响应异常

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
@RequestMapping(produces = MediaType.TEXT_HTML_VALUE)
// 当请求的资源类型为TEXT_HTML_VALUE时进行的处理
public ModelAndView errorHtml(HttpServletRequest request , HttpServletResponse response){
// 定义一个函数,最终响应一个视图作为返回
HttpStatus status = getStatus(request);
Map<String,Object> model = Collections
.unmodifiableMap(getErrorAttributes(request , getErrorAttributeOptions(request,MediaType.TEXT_HTML)));
response.setStatus(status.value());
ModelAndView modelAndView = resolveErrorView(request, response, status ,model);
// 查询对应的异常视图
return (modelAndView !=null) ? modelAndView : new ModelAndView("error", model) ;
// 如果视图为空则使用指定视图,如果存在 则返回视图给客户端
}
...
protected ModelAndView resolveErrorView(HttpServletRequest request , HttpServletResponse response ,
Map<String ,Object> model ){
for (ErrorViewResolver resolver : this.errorViewResolvers){
// 遍历所有的视图解释器
ModelAndView modelAndView = resolver.resolverErrorView(request, status , model);
if(modelAndView !=null){
return modelAndView ;
}
}
return null ;
}
...
@Override
public ModelAndView resolveErrorView(HttpServletRequest request , HttpStataus status , Map<String ,Object> model){
ModelAndView modelAndView = resolve(String.valueOf(status.value()),model);
if(modelAndView == null && SERIES_VIEWS.containsKey(status.series())){
// 在每个具体的视图解释器中查询响应信息,匹配响应信息并返回
modelAndView = resolve(SERIES_VIEW.get(status.series()),model);
}
return modelAndView ;
}
...
private ModelAndView resolve(String viewName , Map<String, Object> model){
String errorViewName = "error/" + viewName;
// Spring 默认的 异常视图处理
TemplateAvailabilityProvider provider = this.templateAvailabilityProviders.getProvider(errorViewName,
this.applicationContext);
if(provider != null){
return new ModelAndView(errorViewName , model);
}
return resolveResource(errorViewName , model);
}
...
private ModelAndView resolveResoure(String viewName, Map<String , Object> model){
for(String location : this.resourceProperties.getStaticLocations()){
try{
Resource resource = this.applicationContext.getResource(location);
resource = resource.createRelative(viewName + ".html");
// 从资源地址查询静态资源
if(resource.exists()){
return new ModelAndView(new HtmlResourceView(resource),model);
}
}
catch(Exception ex){
}
}
return null ;
}
...
protected ErrorAttributeOptions getErrorAttributeOptions(HttpServletRequest request,MediaType mediaType){
ErrorAttributeOptions options = ErrorAttributeOptions.defaults();
if(this.errorProperties.isIncludeException()){
options = options.including(Include.EXCEPTION);
}
if(isIncludeStatckTrace(request), mediaType){
options = options.including(Include.STACK_TRACE);
}
if(isIncludeMessag(request,mediaType)){
options = options.including(Include.MESSAGE);
}
if(isIncludeBindingErrors(request,mediaType)){
options = options.including(Include.BINDING_ERRORS);
}
return options ;
}
...

@RequestMapping
public ResponseEntity<Map<String , Object>> error(HttpServletRequest request){
HttpStatus status = getStatus(request);
if(status == HttpStatus.NO_CONTENT){
return new ResponseEntity<>(status);
}
Map<String,Object> body = getErrorAttributes(request,getErrorAttributeOptions(request, MediaType.ALL));
// 获取 异常信息
return new ResponseEntity<>(body,status);
// 直接返回接口响应
}

国际化

国际化是指根据用户请求,向用户响应不同的响应信息

请求语言获取途径

  1. 通过头部携带的session,cookie,accept-languaget Header等字段 检索/确认国际化请求
  2. 通过get请求中的 “?locale= “字段 确认国家化请求

核心实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
@AutoConfiguration
@ConditionalOnMissingBean(name = AbstractApplicationContext.MESSAGE_SOURCE_BEAN_NAME, search = SerarchStrategy.CURRENT)
@AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE)
// @Conditional 自定义条件匹配,会传入一个实现了Condition接口的类-ResouceBudleCondition
// ResourceBundleCondition 会重写matches 方法,自定义匹配规则,返回true 则成功
@Conditional(ResourceBundleCondition.class)
@EnableConfigurationProperties
public class MessageSourceAutoConfiguration {
private static final Resource[] NO_RESOURCES = {};

@Bean
@ConfigurationProperties(prefix = "spring.messages")
public MessageSourceProperties messageSourceProperties(){
return new MessageSourceProperties();
}

@Bean
public MessageSource messageSource(MessageSourceProperties properties){
ResourceBundleMessageSource messageSource = new ResourceBundleMessageSource();
if(StringUtils.hasText(properties.getBasename())){
messageSource.setBasenames(StringUtils.commaDelimitedListToStringArray(StringUtils.trimAllWhitespace(properties.getBasename())));
if(properties.getEncoding()!= null){
messageSource.setDefaultEncoding(properties.getEncoding().name());
}
messageSource.setFallbackToSystemlocale(prperties.isFallbackToSystemLocaole());
Duration cacheDuration = properties.getCacheDuration();
if(cacheDuration != null){
messageSource.setCacheMillis(cacheDuration.toMillis());
}
messageSource.setFallbackToSystemLocale(properties.isFallbackToSystemLocale());
Duration cacheDuration = properties.getCacheDuration();
if(cacheDuration != null) {
messageSource.setCacheMillis(cacheDuration.toMillis());
}
messageSource.setAlwaysUseMessageFormat(properties.isAlwaysUseMessageFormat());
messageSource.setUseCodeAsDefaultMessage(properites.isUseCodeAsDefaultMessage());
return messageSource;
}

}

通过注册一个MessageSource 实例,使用spring.message.*完成自动配置

通过match 方法来查询是否存在对应的语言配置文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
@Override
public final boolean matches(ConditionContext context , AnnotatedTypeMetadata metdata){
String classOrMethodName = getClassOrMethodName(metadata);
try {
ConditionOutcome outcome = getMatchOutcome(context , metadata);
// 通过getMatchOutcone 方法获取匹配结果
logOutcome(classOrMethodName , outcome);
recordEvaluation(context , classOrMethodName,outcome);
return outcome.isMatch();
}
}

@Override
public ConditionOutcome getMatchOutcome(ConditionContext context , AnnotatedTypeMetadata metadata){
String basename = context.getEnviroment().getProperty("spring.message.basename","message");
// 获取环境信息中存储的对应的国际化资源文件
ConditionOutcome outcome = cache.get(basename);
// 加载到内存
if(outcome == null) {
outcome = getMatchOutcomeForBasename(context,basename);
// 调用getMatchOutcomeForBasename 检查是否存在资源文件
cache.put(basename,outcome);
}
return outcome ;
}

private ConditionOutcome getMatchOutcomeForBasename(ConditionContext context , String basename){
ConditionMessage.Builder message = ConditionMessage.forCondition("ResourceBundle");
for (String name : StringUtils.commaDelimitedListToStringArray(StringUtils.trimAllWhitespace(basename))){
for(Resource resource : getResouce(context.getClassLoader(),name)){
if(resouce.exists()){
// 检查路径中是否存在资源文件
return ConditionOutcome.match(message.found("bundle").items(resource));
}
}
}
return ConditionOutcome.noMatch(message.didNotFind("bundle with basename"+basename).atAll());
}

通过注册LocaleResolver 区域解析器完成 国际化切换

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
@Bean
@ConditionalOnMissingBean
@ConditionOnProperty(prefix = "spring.mvc", name = "locale")
public LocaleResolver localeResolver(){
// 配置spring.mvc.locale-resolver=fixed
// spring.mvc.locale= en_US
// 使用配置文件中的本地化语言
if(this.mvcProperties.getLocaleResolver() == WebMvcProperties.LocaleResolver.FIXED){
return new FixedLocaleResolver(this.mvcProperties.getLocale());
}
// 默认使用AcceptHeaderLocaleResolver作为本地化解析器
AcceptHeaderLocaleResolver localeResolver = new AcceptHeaderLocaleResolver();
// 设置 sping.mvc.locale = en_US 作为默认本地化语言
localeResolver.setDefaultLocale(this.mvcProperties.getLocale());
return localResolver;
}

Servlet容器加载过程

servlet 容器注入的多种方法

实现WebServerFactoryCustomizer 接口,对内置容器进行配置

1
2
3
4
5
6
7
8
9
10
@Override
public void customize(ConfigurableServletWebServerFactory factory) {
factory.setContextPath("");
}

public static void main(String[] args) {
SpringApplication springApplication = new SpringApplication(CustomizationBean.class);
springApplication.run(args);
}

自行定义一个容器 ,作为bean纳入spring 管理

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
@Configuration
public class MyWebMvcConfigurer {
@Bean
public ServletRegistrationBean<BeanServlet> servletRegistrationBean(){
ServletRegistrationBean<BeanServlet> bean = new ServletRegistrationBean<>();
//声明一个注册器bean
bean.setServlet(new BeanServlet());
//设置Servlet
bean.setName("BeanServlet");
// 设置名字
bean.addUrlMappings("/BeanServlet");
// 设置资源绑定路径
return bean;
};
}

实现一个HttpServlet接口,即自定义一套路由处理逻辑

1
2
3
4
5
6
7
8
@WebServlet(name="HelloServlet",urlPatterns = "/hello")
public class HelloServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
PrintWriter writer = resp.getWriter();
writer.println("HelloServlet.doGet");
}
}

Servlet的自动装配 由@EnableAutoConfiguration注解触发

@EnableAutoConfiguration 会触发AutoConfigurationImportselector 检索SPI路径中声明的需要装备的配置类。调用 ServletWebServerFactoryAutoConfiguration 完成Servlet容器的自动装配过程

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
@Configuration(proxyBeanMethods = false)
@AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE)
@ConditionOnClass(ServletRequest.class)
// 在类路径中存在ServletRequest.class 才会被加载
@ConditionOnWebApplication(type = Type.SERVLET)
@EnableConfigurationProperties(ServerProperties.class)
@Import({ServletWebServerFactoryAutoConfiguration.BeanPostProcessorsRegistrar.class,
ServletWebServerFactoryConfiguration.EmbeddedTomcat.class,
ServletWebServerFactoryConfiguration.EmbeddedJetty.class,
ServletWebServerFactoryConfiguration.EmbeddedUndertow.class,
})
// 导入嵌入的servlet 容器类,进行初始化过程
public class ServletWebServerFactoryAutoConfiguration{

}

对应的servlet容器初始化过程

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
@Configuration(
proxyBeanMethods = false
)
@ConditionalOnClass({Servlet.class, Tomcat.class, UpgradeProtocol.class})
@ConditionalOnMissingBean(
value = {ServletWebServerFactory.class},
search = SearchStrategy.CURRENT
)
static class EmbeddedTomcat {
@Bean
TomcatServletWebServerFactory tomcatServletWebServerFactory(ObjectProvider<TomcatConnectorCustomizer> connectorCustomizers, ObjectProvider<TomcatContextCustomizer> contextCustomizers, ObjectProvider<TomcatProtocolHandlerCustomizer<?>> protocolHandlerCustomizers) {
TomcatServletWebServerFactory factory = new TomcatServletWebServerFactory();
factory.getTomcatConnectorCustomizers().addAll(connectorCustomizers.orderedStream().toList());
// 获取容器配置信息,完成配置
factory.getTomcatContextCustomizers().addAll(contextCustomizers.orderedStream().toList());
factory.getTomcatProtocolHandlerCustomizers().addAll(protocolHandlerCustomizers.orderedStream().toList());
return factory;
}
}

当存在ServletWebServerFactory时 , 不再加载内置容器 。

1
2
3
4
@Bean
public ServiceWebServerFactoryCustomizer servletWebServerFactoryCustomizer(ServerProperties serverProperties){
return new ServicetWebServerFactoryCustomizer(serverProperties);
}

自定义的容器类和内嵌tomcat 都继承自 WebServerFactory 创建对应的容器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
@Override
public void customize(ConfigurationServletWebServerFactory factory){
PropertyMapper map = PropertyMapper.get().alwaysApplyingWhenNonNull();
/* if(serverProperties.getPort()!=null){
factor.serPort(serverProperties.getPort())
} */
map.from(this.serverProperties::getPort).to(factory::setPort);
map.from(this.serverProperties::getAddress).to(factory::setAddress);
map.from(this.serverProperties::getServlet()::getContextPath).to(factory::setContextPath);
map.from(this.serverProperties::getServlet()::getApplicationDisplayName).to(factory::setDisplayName);
map.from(this.serverProperties::getServlet()::isRegisterDefaultServlet).to(factory::setRegisterDefaultServlet);
map.from(this.serverProperties::getServlet()::getSession).to(factory::setSession);
map.from(this.serverProperties::getSsl).to(factory::setSsl);
map.from(this.serverProperties.getServlet()::getJsp).to(factory::setJsp);
}

从配置文件中获取信息,配置到容器中

1
2
3
4
5
6
7
8
9
@Override
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata,
BeanDefinitionRegistry registry){
if(this.beanFactory == null){
return ;
}
registerSyntheticBeanIfMissing(registry, WebServerFactoryCustomizerBeanPostProcessor.class);
registerSyntheticBeanIfMissing(registry, ErrorPageRegistrarBeanPostProcessor.class);
}

使用注册器,将对应的servlet容器 加入到Spring中进行管理

1
2
3
4
5
6
7
8
private Collection<WebServerFactoryCustomizer<?>> getCustomizers(){
if(this.customizers == null){
this.customizers = new ArrayList<>(getWebServerFactoryCustomizerBeans());
this.customizers.sort(AnnotationAwareOrderComparator.INSTANCE);
this.customizers = Collections.unmodifiableList(this.customizers);
}
return this.customizers;
}

将所有servlet容器 放入容器中管理,之后调用 每个具体容器实现的customize()方法完成响应容器的配置初始化

在对应embedding启动时, 会在里面配置一个WebServerFactory类型的一个Bean 负责对应的容器和启动

1
2
3
4
5
6
7
8
@Override
public Object postProcessBeforeInitialization(Object bean , String beanName) throws BeansException{
// 判断当前创建的bean 是不是配置类型
if(bean instance of WebServerFactory){
postProcessBeforeInitialization((WebServerFactory) bean);
}
return bean ;
}

自动配置中根据不同的依赖,启动对应的一个Embeded , 然后配置了一个对应的servlet容器工厂类

在springboot应用启动的时候 就回调用容器refresh方法,调用getWebServer 并启动

使用外部servlet 容器

1 下载tomcat服务

2 设置maven打包方式

1
<packaging>war</packaging>

3 设置tomcat相关依赖不参与打包部署

1
2
3
4
5
<dependency>
<artifactId>spring-boot-starter</artifactId>
<groupId>org.springframework.boot</groupId>
<scope>provided</scope>
</dependency>

4 配置启动类定义

1
2
3
4
5
6
public class TomcatStartSpringBoot extends SpringBootServletInitializer{
@Override
protected SpringApplicationBuilder configure(SpringApplicationBuilder builder){
return builder.sources(Application.class);
}
}

5 在idea中运行

1
2
3
配置 URL 
选择tomcat版本
指定 部署war包

外部容器启动原理

打包后指定启动类 对java程序进行启动

tomcat不会主动启动Spring 应用,所以在启动过程中tomcat 调用了SpringBootServletInitializer的SpringApplicationBuilder 。

当servlet容器启动的时候 就会去META-INF/services文件夹中找到javax.servlet.ServletContainerInitializer 这个文件对应绑定了ServletContainerInitalizer.从该文件中找到对应的实现类创建实例,调用 on startup

tomcat → 启动WEB-INF/lib → 查找对应接口的实现类

WebApplicationInitializer

AbstractContextLoaderInitialzer SpringBootServletInitializer

AbstractDispatcherServerletInitializer TomcatStartSpringBoot


基于Spring Boot 的 HTTP服务构建
http://gadoid.io/2025/07/16/基于Spring-Boot-的-HTTP服务构建/
作者
Codfish
发布于
2025年7月16日
许可协议