Spring Boot 与 Web 开发
SpringMVC 快速使用
基于 restful http 接口的 CURD
@RestController @RequestMapping("/user") public class UserController { @Autowired UserService userService; @GetMapping("/{id}") public Result getUser(@PathVariable Integer id) { User user = userService.getUserById(id); return new Result<>(200, "查询成功", user); } @PostMapping("/add") public Result getUser(User user) { userService.add(user); return new Result<>(200, "添加成功"); } @PutMapping("/{id}") public Result editUser(User user) { userService.update(user); return new Result(200, "修改成功"); } @DeleteMapping("/{id}") public Result deleteUser(@PathVariable Integer id) { userService.delete(id); return new Result<>(200, "删除成功"); } }
调用 rest http 接口
通过 restTemplate 调用
RestTemplate 类可用于在应用中调用 rest 服务,它简化了与 http 服务的通信方式,统一了 RESTful 的标准,封装了 http 链接,我们只需要传入 url 及返回值类型即可。
适用于微服务架构下,服务之间的远程调用。 ps:以后使用微服务架构,使用 spring cloud feign 组件
restTemplate 和 webClient 都可以调用远程服务,区别:webclient 依赖 webflux,webclient 请求远程服务是无阻塞的,响应的。RestTemplate 是阻塞的,需要等待请求响应后才能执行下一句代码。
DELETE delete GET getForObject 按照指定 Class 返回对象
getForEntity 返回对象为 ResponseEntity 对象,包含了响应中的一些重要信息,比如响应头、响应状态码、响应体等。HEAD headForHeaders OPTIONS optionForAllow POST postForLocaltion
postForObjectPUT put any
支持任何请求方法类型exchange
execute基于 restTemplate 调用查询
// 声明了 RestTemplate private final RestTemplate restTemplate; // 当 Bean 没有无参构造函数的时候, spring 将自动拿到有参的构造函数,参数进行自动注入 public OrderController(RestTemplateBuilder restTemplateBuilder) { this.restTemplate = restTemplateBuilder.build(); } @RequestMapping("/order") public String order() { Result forObject = restTemplate.getForObject("http://localhost:8080/user/{id}", Result.class, 1); return forObject.toString(); }
基于 restTemplate 调用查询
@RequestMapping("/order") public String order() { //基于 restTemplate 调用查询 User user = new User("AZhang", "wyu"); // url: 请求的远程 rest url // object: post 请求的参数 // Class<T>: 返回的类型 // ...Object: 是 @PathVariable 占位符的参数 ResponseEntity<Result> resultResponseEntity = restTemplate.postForEntity("http://localhost:8080/user/add", user, Result.class); System.out.println(resultResponseEntity); return resultResponseEntity.getBody().toString(); }
基于 restTemplate 调用修改
@RequestMapping("/order") public String order() { //基于 restTemplate 调用修改 User user = new User(1, "AZhang", "wyu"); //restTemplate.put("http://localhost:8080/user/{id}", user, Result.class); HttpEntity<User> httpEntity = new HttpEntity<>(user); ResponseEntity<Result> resultResponseEntity = restTemplate.exchange("http://localhost:8080/user/{id}", HttpMethod.PUT, httpEntity, Result.class, 1); System.out.println(resultResponseEntity); return resultResponseEntity.getBody().toString(); }
基于 restTemplate 调用删除
@RequestMapping("/order") public String order() { //基于 restTemplate 调用删除 ResponseEntity<Result> resultResponseEntity = restTemplate.exchange("http://localhost:8080/user/{id}", HttpMethod.DELETE, null, Result.class, 1); System.out.println(resultResponseEntity); return resultResponseEntity.getBody().toString(); }
- 通过 postman 调用
- 通过 MockMvc 测试
MockMvc 是由 spring-test 包提供,实现了对 Http 请求的模拟,能够直接使用网络的形式,转换到 Controller 的调用,使得测试速度快、不依赖网络环境。同时提供了一套验证的工具,结果的验证十分方便。
SpringMVC 自动配置原理分析
Spring Boot 为 Spring MVC 提供了自动配置,可与大多数应用程序完美配合。
自动配置在 Spring 的默认值之上添加了以下功能:
包含
ContentNegotiatingViewResolver
和BeanNameViewResolver
。ViewResolver 都是 SpringMVC 内置的视图解析器
ContentNegotiatingViewResolver
- 不会解析视图,而是委派给其他视图解析器进行解析。
所有的视图解析器,都会根据返回的视图名称进行解析视图 resolveViewName
public View resolveViewName(String viewName, Locale locale) throws Exception { RequestAttributes attrs = RequestContextHolder.getRequestAttributes(); Assert.state(attrs instanceof ServletRequestAttributes, "No current ServletRequestAttributes"); List<MediaType> requestedMediaTypes = getMediaTypes(((ServletRequestAttributes) attrs).getRequest()); if (requestedMediaTypes != null) { // 获得所有匹配的视图 List<View> candidateViews = getCandidateViews(viewName, locale, requestedMediaTypes); // 获取最终的这个 View bestView = getBestView(candidateViews, requestedMediaTypes, attrs); if (bestView != null) { return bestView; } }
委派给其他视图解析器进行解析:
@Override protected void initServletContext(ServletContext servletContext) { Collection<ViewResolver> matchingBeans = BeanFactoryUtils.beansOfTypeIncludingAncestors(obtainApplicationContext(), ViewResolver.class).values(); if (this.viewResolvers == null) { this.viewResolvers = new ArrayList<>(matchingBeans.size()); for (ViewResolver viewResolver : matchingBeans) { if (this != viewResolver) { this.viewResolvers.add(viewResolver); } } }
BeanNameViewResolver
- 会根据 handler 方法返回的视图名称,去 IOC 容器中找到名字叫 azhang 的一个 Bean,并且这个 Bean要实现了 View接口。
示例:
@GetMapping("/test") public String getUser() { return "azhang"; }
可以配置一个名字叫 azhang 的视图(View)
@Component public class Azhang implements View { @Override public String getContentType() { return "text/html"; } @Override public void render(Map<String, ?> model, HttpServletRequest request, HttpServletResponse response) throws Exception { response.getWriter().print("Welcome to AzhangView"); } }
- 由以上代码可以得出结论,它是从 Spring IOC 容器获得 ViewResolve 类型 Bean,name我们可以自己定制一个 ViewResolver,ContentNegotiatingViewResolver 也会帮我们委派解析:
```java
@Bean
public ViewResolver AZhangViewResolve() {
InternalResourceViewResolver resolver = new InternalResourceViewResolver();
resolver.setPrefix("/");
resolver.setSuffix(".html");
return resolver;
}
```

支持提供静态资源,包括对 WebJars 的支持。
- 以前要访问 jpg / css / js 等这些静态资源文件,需要在 web.xml 配置,但现在在 SpringBoot 中不需要配置,只需要放在约定文件夹中就可以(约定大于配置)
原理:
- WebJars:就是将静态资源放在 jar 包中进行访问
- WebJars 官网:https://www.webjars.org/
@Override public void addResourceHandlers(ResourceHandlerRegistry registry) { if (!this.resourceProperties.isAddMappings()) { logger.debug("Default resource handling disabled"); return; } addResourceHandler(registry, "/webjars/**", "classpath:/META-INF/resources/webjars/"); addResourceHandler(registry, this.mvcProperties.getStaticPathPattern(), (registration) -> { registration.addResourceLocations(this.resourceProperties.getStaticLocations()); if (this.servletContext != null) { ServletContextResource resource = new ServletContextResource(this.servletContext, SERVLET_LOCATION); registration.addResourceLocations(resource); } }); } private void addResourceHandler(ResourceHandlerRegistry registry, String pattern, String... locations) { addResourceHandler(registry, pattern, (registration) -> registration.addResourceLocations(locations)); } private void addResourceHandler(ResourceHandlerRegistry registry, String pattern, Consumer<ResourceHandlerRegistration> customizer) { if (registry.hasMappingForPattern(pattern)) { return; } ResourceHandlerRegistration registration = registry.addResourceHandler(pattern); customizer.accept(registration); registration.setCachePeriod(getSeconds(this.resourceProperties.getCache().getPeriod())); registration.setCacheControl(this.resourceProperties.getCache().getCachecontrol().toHttpCacheControl()); registration.setUseLastModified(this.resourceProperties.getCache().isUseLastModified()); customizeResourceHandlerRegistration(registration); }
当访问 /webjars/** 时,就会去 classpath:/META-INF/resources/webjars/ 对应进行映射
当访问 http://localhost:8080/webjars/jquery/3.5.1/jquery.js 对应映射到 /META-INF/resources/webjars/jquery/3.5.1/jquery.js
- 在 static 文件中访问的静态资源,默认的四个静态资源路径 (对应的映射地址) :
{ "classpath:/META-INF/resources/", "classpath:/resources/", "classpath:/static/",
配置首页欢迎页:
private Resource getWelcomePage() { for (String location : this.resourceProperties.getStaticLocations()) { // 拿到上面静态资源地址 Resource indexHtml = getIndexHtml(location); if (indexHtml != null) { return indexHtml; } } ServletContext servletContext = getServletContext(); // 去里面找一个 index.html 的首页文件 if (servletContext != null) { return getIndexHtml(new ServletContextResource(servletContext, SERVLET_LOCATION)); } return null; } private Resource getIndexHtml(String location) { return getIndexHtml(this.resourceLoader.getResource(location)); } private Resource getIndexHtml(Resource location) { try { Resource resource = location.createRelative("index.html"); if (resource.exists() && (resource.getURL() != null)) { return resource; } } catch (Exception ex) { } return null; }
也可以通过配置文件指定具体的静态资源地址:
自动注册
Converter
,GenericConverter
和Formatter
Bean 类。- 使用方式
支持
HttpMessageConverters
。HttpMessageConverters
负责 http 请求和响应的报文处理。
自动注册
MessageCodesResolver
。- 修改 4xx 错误下格式转换出错、类型转换出错的错误代码
以前的格式是 errorCode + "." + object name + "." + field
例:typeMismatch.user.birthday
- 可以通过
spring.mvc.message-codes-resolver-format=postfix_error_code
将格式修改为:object name + "." + field + "." + errorCode
静态
index.html
支持。在 springboot 中可以直接返回 html 的视图,因为在 WebMvcAutoConfiguration 配置类配置了:
@Bean @ConditionalOnMissingBean public InternalResourceViewResolver defaultViewResolver() { InternalResourceViewResolver resolver = new InternalResourceViewResolver(); resolver.setPrefix(this.mvcProperties.getView().getPrefix()); resolver.setSuffix(this.mvcProperties.getView().getSuffix()); return resolver; }
所以可以通过在配置文件中完成:
spring.mvc.view.prefix=/pages/ spring.mvc.view.suffix=.html
自动使用
ConfigurableWebBindingInitializer
bean。定制 SpringMVC 的自动配置
SpringMVC 的自动配置类:WebMvcAutoConfiguration
- 在大多数情况下,SpringBoot 在自动配置类中标记了很多
@ConditionalOnMissingBean(xxxxxxxxxx.class)
;(意思就是如果容器中没有,当前的 Bean 才会生效)。只需要在自己的配置类中配置对应的一个@Bean
就可以覆盖默认自动配置。 通过 WebMvcConfigurer 进行拓展
- 扩展视图控制器
- 扩展拦截器
- 扩展全局 CORS
@Configuration public class MyWebMvcConfigurer implements WebMvcConfigurer { /*@Override public void configurePathMatch(PathMatchConfigurer configurer) { configurer.setUseTrailingSlashMatch(false); }*/ @Override public void addInterceptors(InterceptorRegistry registry) { registry.addInterceptor(new TimeInterceptor()) // 添加拦截器 .addPathPatterns("/**") // 拦截映射规则 /**拦截所有的请求 .excludePathPatterns("/pages/**"); // 排除 pages 下面所有的请求 } /** * CORS 配置 * 全局跨域请求配置 */ /*@Override public void addCorsMappings(CorsRegistry registry) { registry.addMapping("/user/*") // 映射服务器中哪些 http 接口允许运行跨域访问 .allowedOrigins("http://localhost:8081") // 配置哪些来源有权限跨域 .allowedMethods("GET", "POST", "PUT", "DELETE"); // 配置允许跨域访问的请求方法 }*/ @Bean public InternalResourceViewResolver AZhangViewResolver() { InternalResourceViewResolver resolver = new InternalResourceViewResolver(); resolver.setPrefix("/pages"); resolver.setSuffix(".html"); return resolver; } }
WebMvcConfigurer 原理
实现 WebMvcConfigurer 接口可以扩展 Mvc 实现,又既保留了 SpringBoot 的自动配置
- 在
WebMvcAutoConfiguration
也有一个实现了WebMvcConfigurer
的配置类 WebMvcAutoConfigurationAdapter
它也是利用这种方式去进行扩展,所以我们通过查看这个类我们发现它帮我们实现了其他不常用的方法,帮助我们进行自动配置,我们只需要定制(拦截器、视图控制器、CORS 等在开发中需要额外定制的功能)@Configuration(proxyBeanMethods = false) @Import(EnableWebMvcConfiguration.class) @EnableConfigurationProperties({ WebMvcProperties.class, WebProperties.class }) @Order(0) public static class WebMvcAutoConfigurationAdapter implements WebMvcConfigurer, ServletContextAware {
导入了
EnableWebMvcConfiguration
@Import(EnableWebMvcConfiguration.class)
EnableWebMvcConfiguration
它的父类上 setConfigurers 使用了@AutoWired
注解- 它会去容器中将所有实现了 WebMvcConfigurer 接口的 Bean 都自动注入进来,添加到 configurers 变量中
public class DelegatingWebMvcConfiguration extends WebMvcConfigurationSupport { private final WebMvcConfigurerComposite configurers = new WebMvcConfigurerComposite();
- 在
@Autowired(required = false) public void setConfigurers(List<WebMvcConfigurer> configurers) { if (!CollectionUtils.isEmpty(configurers)) { this.configurers.addWebMvcConfigurers(configurers); } } ``` - 添加到 ``delegates`` 委派器中 ```java public void addWebMvcConfigurers(List<WebMvcConfigurer> configurers) { if (!CollectionUtils.isEmpty(configurers)) { this.delegates.addAll(configurers); } } ``` - 底层调用 WebMvcConfigurer 对应的方法时,就是去拿到之前注入到 delegates 的 WebMvcConfigurer,依次调用 ```java @Override public void addInterceptors(InterceptorRegistry registry) { for (WebMvcConfigurer delegate : this.delegates) { delegate.addInterceptors(registry); } } ```
当添加了
@EnableWebMvc
就不会使用 SpringMVC 自动配置类的默认配置了原理:
在 EnableMvc 中
@Import(DelegatingWebMvcConfiguration.class)
@Retention(RetentionPolicy.RUNTIME) @Target(ElementType.TYPE) @Documented @Import(DelegatingWebMvcConfiguration.class) public @interface EnableWebMvc { }
在 DelegatingWebMvcConfiguration 中继承了 WebMvcConfigurationSupport
@Configuration(proxyBeanMethods = false) public class DelegatingWebMvcConfiguration extends WebMvcConfigurationSupport {
在 WebMvcAutoConfiguration 中
@ConditionalOnMissingBean(WebMvcConfigurationSupport.class)
当容器中不存在 WebMvcConfigurationSupport 这个 Bean 的时候自动配置类才会生效
正因为通过
@EnableWebMvc
导入了 DelegatingWebMvcConfiguration extends WebMvcConfigurationSupport 从而才使自动配置类失效@Configuration(proxyBeanMethods = false) @ConditionalOnWebApplication(type = Type.SERVLET) @ConditionalOnClass({ Servlet.class, DispatcherServlet.class, WebMvcConfigurer.class }) @ConditionalOnMissingBean(WebMvcConfigurationSupport.class) @AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE + 10) @AutoConfigureAfter({ DispatcherServletAutoConfiguration.class, TaskExecutionAutoConfiguration.class, ValidationAutoConfiguration.class }) public class WebMvcAutoConfiguration {
Json 开发
Spring Boot 提供了与三个 JSON 映射库的集成:
Gson
- Jackson 性能最好
- JSON-B
Jackson 是我们使用的默认 json 库
jackson 的使用
@JsonIgnore
- 进行排除 json 序列化,将它标注在属性上将不会进行 json 格式化
- 国际化
- 统一异常处理
- SpringBoot 的嵌入式 Servlet 容器