本文最后更新于 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) { }
servlet 接口定义了 http的处理过程, 接收一个HttpServletRequest 对象,并最终响应
一个HttpServletResponse 返回对象。然后 Servlet的容器(如tomcat,jetty)就可以遵守这个规范完成前置和后续的信息处理。
Spring Boot的HTTP服务构建 Spring 相比传统的Servlet 实现-装配过程提供了
内置Servlet容器。通过集成Servlet容器,到springboot 可以直接启动服务作为HTTP服务,不再需要打包为war包安装到servlet容器中
提供了丰富的过程化管理,通过接口实现可以完成对基础HTTP服务的功能扩展
提供了集成到外部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 --- service --- repository --- 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; public OrderController (RestTemplateBuilder restTemplateBuilder) { this .restTemplate = restTemplateBuilder.build(); } @RequestMapping("/order") public String order () { String url = "<http://localhost:8085/user/1>" ; Result forobject = this .restTemplate.getForObject(url, Result.class,1 ); 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: return responseEntity.getBody().getData().toString(); } }@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" ; } } @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" ; } }
@RequestMapping 用于映射请求路径和方法(支持 GET/POST/PUT/DELETE 等)
1 2 3 4 5 6 7 8 @Controller @RequestMapping("/codfish") public class CodfishController { @RequestMapping(method = RequestMethod.GET ,"/test") public String test () { return "codfish" ; } }
@GetMapping 简化版 @RequestMapping(method = RequestMethod.GET)
1 2 3 4 5 6 7 8 @Controller @RequestMapping("/codfish") public class CodfishController { @GetMapping("/test") 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 () { }
@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 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) 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; 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); }
国际化 国际化是指根据用户请求,向用户响应不同的响应信息
请求语言获取途径
通过头部携带的session,cookie,accept-languaget Header等字段 检索/确认国际化请求
通过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(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); 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); 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 () { if (this .mvcProperties.getLocaleResolver() == WebMvcProperties.LocaleResolver.FIXED){ return new FixedLocaleResolver (this .mvcProperties.getLocale()); } AcceptHeaderLocaleResolver localeResolver = new AcceptHeaderLocaleResolver (); 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.setServlet(new BeanServlet ()); 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) @ConditionOnWebApplication(type = Type.SERVLET) @EnableConfigurationProperties(ServerProperties.class) @Import({ServletWebServerFactoryAutoConfiguration.BeanPostProcessorsRegistrar.class, ServletWebServerFactoryConfiguration.EmbeddedTomcat.class, ServletWebServerFactoryConfiguration.EmbeddedJetty.class, ServletWebServerFactoryConfiguration.EmbeddedUndertow.class, }) 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(); 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{ 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