1、web开发
-
自动配置
-
引入了autoConfigure功能
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency>
-
@EnableAutoConfiguration 注解使用 @Import(AutoConfigurationImportSelector.class)批量导入组件,加载 META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports。文件中配置的所有组件
-
所有自动配置类如下:
org.springframework.boot.autoconfigure.web.client.RestTemplateAutoConfiguration org.springframework.boot.autoconfigure.web.embedded.EmbeddedWebServerFactoryCustomizerAutoConfiguration ============以下是响应式web场景的和现在的没有关系============== org.springframework.boot.autoconfigure.web.reactive.HttpHandlerAutoConfiguration org.springframework.boot.autoconfigure.web.reactive.ReactiveMultipartAutoConfiguration org.springframework.boot.autoconfigure.web.reactive.ReactiveWebServerFactoryAutoConfiguration org.springframework.boot.autoconfigure.web.reactive.WebFluxAutoConfiguration org.springframework.boot.autoconfigure.web.reactive.WebSessionIdResolverAutoConfiguration org.springframework.boot.autoconfigure.web.reactive.error.ErrorWebFluxAutoConfiguration org.springframework.boot.autoconfigure.web.reactive.function.client.ClientHttpConnectorAutoConfiguration org.springframework.boot.autoconfigure.web.reactive.function.client.WebClientAutoConfiguration ============以上没有关系============== org.springframework.boot.autoconfigure.web.servlet.DispatcherServletAutoConfiguration org.springframework.boot.autoconfigure.web.servlet.ServletWebServerFactoryAutoConfiguration org.springframework.boot.autoconfigure.web.servlet.error.ErrorMvcAutoConfiguration org.springframework.boot.autoconfigure.web.servlet.HttpEncodingAutoConfiguration org.springframework.boot.autoconfigure.web.servlet.MultipartAutoConfiguration org.springframework.boot.autoconfigure.web.servlet.WebMvcAutoConfiguration org.springframework.boot.autoconfigure.websocket.reactive.WebSocketReactiveAutoConfiguration org.springframework.boot.autoconfigure.websocket.servlet.WebSocketServletAutoConfiguration org.springframework.boot.autoconfigure.websocket.servlet.WebSocketMessagingAutoConfiguration org.springframework.boot.autoconfigure.webservices.WebServicesAutoConfiguration org.springframework.boot.autoconfigure.webservices.client.WebServiceTemplateAutoConfiguration
-
绑定了配置文件的一堆配置项
-
SpringMvc的所有配置,spring mvc:静态资源访问前缀路径
-
web场景的通用配置,spring.web:
-
静态资源目录
-
静态资源缓存策略
-
-
文件上传配置 spring.servlet.multipart
-
服务器的一些配置 server。比如编码格式
-
Spring boot servlet web
默认配置:
-
包含了 ContentNegotiatingViewResolver和 BeanNameViewResolver 组件,方便视图解析
-
默认的静态资源处理机制: 静态资源放在 static 文件夹下即可直接访问
-
自动注册了Converter,GenericConverter,Formatter组件,适配常见的数据类型转换和格式化需求。
-
支持HttpMessageConverters,可以方便返回json等数据类型(所有json的相应都与他有关)
-
注册MessageCodesResolver,方便国际化及错误消息处理
-
支持 静态 index.html
-
自动使用ConfgurableWebBindingInitializer,实现消息处理、数据绑定、类型转化、数据校验等功能(JavaBean和前段传过来参数相呼应)
重要:
- 如果想保持 boot mvc 的默认配置,并且自定义更多的 mvc配置,如:interceptors,formatters,view controllers 等。可以使用@Configuration注解添加一个 WebMvcConfiqurer 类型的配置类并不要标注 @EnableWebMvc
- 如果想保持 boot mvc 的默认配置,但要自定义核心组件实例,比如:RequestMappingHandlerMapping,RequestMappingHandlerAdapter; 或ExceptionHandlerExceptionResolver,给容器中放一个WebMvcRegistrations 组件即可如果想全面接管 Spring MVC,@Configuration 标注一个配置类,并加上 @EnableWebMvc注解,实现 WebMvcConfiqurer接口
- 如果想全面接管 Spring MVC,@Configuration 标注一个配置类,并加上 @EnableWebMvc注解,实现WebMvcConfigurer接口
三种方式:
总结:
给容器中写个配置类@Configuration,实现webMvcConfigurer,但不要标注@EnableWebMvc注解,实现手自一体的效果。
2、静态资源
规则源码:
public void addResourceHandlers(ResourceHandlerRegistry registry) {
if (!this.resourceProperties.isAddMappings()) {
logger.debug("Default resource handling disabled");
} else {
//规则一
this.addResourceHandler(registry, this.mvcProperties.getWebjarsPathPattern(), "classpath:/META-INF/resources/webjars/");
//规则二
this.addResourceHandler(registry, this.mvcProperties.getStaticPathPattern(), (registration) -> {
registration.addResourceLocations(this.resourceProperties.getStaticLocations());
if (this.servletContext != null) {
ServletContextResource resource = new ServletContextResource(this.servletContext, "/");
registration.addResourceLocations(new Resource[]{resource});
}
});
}
}
-
规则一:访问:/webjars/**路径就去 classpath:/META-INF/resources/webjars/下找资源
- 规则二:访问: /**路径就去静态资源默认的四个位置找资源
private static final String[] CLASSPATH_RESOURCE_LOCATIONS = new String[]{"classpath:/META-INF/resources/", "classpath:/resources/", "classpath:/static/", "classpath:/public/"};
- `classpath:/META-INF/resources/`
- `classpath:/resources/`
- `classpath:/static/`
- `classpath:/public/`
- 规则三:静态资源默认都有缓存规则的设置
- 所有缓存的设置,直接通过配置文件:`spring.web`
- cachePeriod:缓存周期; 多久不用找服务器要新的,默认没有,以秒为单位
- cacheControl:HTTP缓存控制; https://developer.mozilla.org/zh-CN/docs/Web/HTTP/Caching
- useLastModified:是否使用最后一次修改。配合HTTP cache规则
如果浏览器访问一个静态资源 index.js,如果服务器中的静态资源没有发生变化,下次访问时可以直接让浏览器用自己缓存中的东西,而不用服务器发请求
registration.setCachePeriod(this.getSeconds(this.resourceProperties.getCache().getPeriod()));
registration.setCacheControl(this.resourceProperties.getCache().getCachecontrol().toHttpCacheControl());
registration.setUseLastModified(this.resourceProperties.getCache().isUseLastModified());
0.WebMvcAutoConfiguration原理
1、生效条件
@AutoConfiguration(
after = {DispatcherServletAutoConfiguration.class, TaskExecutionAutoConfiguration.class, ValidationAutoConfiguration.class}
)//在这些自动配置之后
@ConditionalOnWebApplication(type = Type.SERVLET)//如果是web应用就生效,类型servlet,reactive响应式web
@ConditionalOnClass({Servlet.class, DispatcherServlet.class, WebMvcConfigurer.class})//当系统中存在这个类
@ConditionalOnMissingBean({WebMvcConfigurationSupport.class})//容器中没有这个bean,自动配置才生效,默认就是没有
@AutoConfigureOrder(-2147483638)//优先级
@ImportRuntimeHints({WebResourcesRuntimeHints.class})
public class WebMvcAutoConfiguration {
2、效果
- 放了两个Filter:
- 第一个叫HiddenHttpMethodFilter 页面表单提交REST请求(GET、POST、PUT、DELETE)
- 第二个叫FormContentFilter 表单内容filter,GET(数据放在URL后面),POST(放在请求体里)。请求可以携带数据。PUT、DELETE的请求体数据会被忽略。
- 给容器中放了 webMvcConfigurer 组件;给SpringMVC添加各种定制功能
- 所有的功能最终都会和配置文件进行绑定
- WebMvcProperties: `spring.mvc`配置文件
- WebProperties:`spring.web`配置文件
webMvcConfigurer接口:
这个接口提供了springMvc组件的所有入口,
@Bean
@ConditionalOnMissingBean({HiddenHttpMethodFilter.class})
@ConditionalOnProperty(prefix = "spring.mvc.hiddenmethod.filter",name = {"enabled"})
public OrderedHiddenHttpMethodFilter hiddenHttpMethodFilter() {
return new OrderedHiddenHttpMethodFilter();
}
//这个方法初始化了OrderedHiddenHttpMethodFilter 这个类
public class OrderedHiddenHttpMethodFilter extends HiddenHttpMethodFilter implements OrderedFilter {
public static final int DEFAULT_ORDER = -10000;
private int order = -10000;
public OrderedHiddenHttpMethodFilter() {
}
public int getOrder() {
return this.order;
}
public void setOrder(int order) {
this.order = order;
}
}
//这个类又继承了HiddenHttpMethodFilter 这个类
public class HiddenHttpMethodFilter extends OncePerRequestFilter {
private static final List<String> ALLOWED_METHODS;
public static final String DEFAULT_METHOD_PARAM = "_method";
private String methodParam = "_method";
public HiddenHttpMethodFilter() {
}
public void setMethodParam(String methodParam) {
Assert.hasText(methodParam, "'methodParam' must not be empty");
this.methodParam = methodParam;
}
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
HttpServletRequest requestToUse = request;
if ("POST".equals(request.getMethod()) && request.getAttribute("jakarta.servlet.error.exception") == null) {
String paramValue = request.getParameter(this.methodParam);
if (StringUtils.hasLength(paramValue)) {
String method = paramValue.toUpperCase(Locale.ENGLISH);
if (ALLOWED_METHODS.contains(method)) {
requestToUse = new HttpMethodRequestWrapper(request, method);
}
}
}
filterChain.doFilter((ServletRequest)requestToUse, response);
}
static {
ALLOWED_METHODS = List.of(HttpMethod.PUT.name(), HttpMethod.DELETE.name(), HttpMethod.PATCH.name());
}
private static class HttpMethodRequestWrapper extends HttpServletRequestWrapper {
private final String method;
public HttpMethodRequestWrapper(HttpServletRequest request, String method) {
super(request);
this.method = method;
}
public String getMethod() {
return this.method;
}
}
}
@Bean
@ConditionalOnMissingBean({FormContentFilter.class})
@ConditionalOnProperty(prefix = "spring.mvc.formcontent.filter",name = {"enabled"},matchIfMissing = true)
public OrderedFormContentFilter formContentFilter() {
return new OrderedFormContentFilter();
}
//这个类实例化了OrderedFormContentFilter 这个类
public class OrderedFormContentFilter extends FormContentFilter implements OrderedFilter {
public static final int DEFAULT_ORDER = -9900;
private int order = -9900;
public OrderedFormContentFilter() {
}
public int getOrder() {
return this.order;
}
public void setOrder(int order) {
this.order = order;
}
}
//这个类右继承了FormContentFilter 这个方法,下面展示部分截取代码
public class FormContentFilter extends OncePerRequestFilter {
private static final List<String> HTTP_METHODS = Arrays.asList("PUT", "PATCH", "DELETE");
private FormHttpMessageConverter formConverter = new AllEncompassingFormHttpMessageConverter();
public FormContentFilter() {
}
public void setFormConverter(FormHttpMessageConverter converter) {
Assert.notNull(converter, "FormHttpMessageConverter is required");
this.formConverter = converter;
}
public void setCharset(Charset charset) {
this.formConverter.setCharset(charset);
}
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
MultiValueMap<String, String> params = this.parseIfNecessary(request);
if (!CollectionUtils.isEmpty(params)) {
filterChain.doFilter(new FormContentRequestWrapper(request, params), response);
} else {
filterChain.doFilter(request, response);
}
}
@Nullable
private MultiValueMap<String, String> parseIfNecessary(final HttpServletRequest request) throws IOException {
if (!this.shouldParse(request)) {
return null;
} else {
HttpInputMessage inputMessage = new ServletServerHttpRequest(request) {
public InputStream getBody() throws IOException {
return request.getInputStream();
}
};
return this.formConverter.read((Class)null, inputMessage);
}
}
//在WebMvcAutoConfiguration里面又有一个WebMvcAutoConfigurationAdapter内部类,以下展示的是部分截取代码
@Configuration(
proxyBeanMethods = false
)
@Import({EnableWebMvcConfiguration.class})
@EnableConfigurationProperties({WebMvcProperties.class, WebProperties.class})
@Order(0)
public static class WebMvcAutoConfigurationAdapter implements WebMvcConfigurer, ServletContextAware {
private static final Log logger = LogFactory.getLog(WebMvcConfigurer.class);
private final WebProperties.Resources resourceProperties;
private final WebMvcProperties mvcProperties;
private final ListableBeanFactory beanFactory;
private final ObjectProvider<HttpMessageConverters> messageConvertersProvider;
private final ObjectProvider<DispatcherServletPath> dispatcherServletPath;
private final ObjectProvider<ServletRegistrationBean<?>> servletRegistrations;
private final ResourceHandlerRegistrationCustomizer resourceHandlerRegistrationCustomizer;
private ServletContext servletContext;
public WebMvcAutoConfigurationAdapter(WebProperties webProperties, WebMvcProperties mvcProperties, ListableBeanFactory beanFactory, ObjectProvider<HttpMessageConverters> messageConvertersProvider, ObjectProvider<ResourceHandlerRegistrationCustomizer> resourceHandlerRegistrationCustomizerProvider, ObjectProvider<DispatcherServletPath> dispatcherServletPath, ObjectProvider<ServletRegistrationBean<?>> servletRegistrations) {
this.resourceProperties = webProperties.getResources();
this.mvcProperties = mvcProperties;
this.beanFactory = beanFactory;
this.messageConvertersProvider = messageConvertersProvider;
this.resourceHandlerRegistrationCustomizer = (ResourceHandlerRegistrationCustomizer)resourceHandlerRegistrationCustomizerProvider.getIfAvailable();
this.dispatcherServletPath = dispatcherServletPath;
this.servletRegistrations = servletRegistrations;
}
public void setServletContext(ServletContext servletContext) {
this.servletContext = servletContext;
}
public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
this.messageConvertersProvider.ifAvailable((customConverters) -> {
converters.addAll(customConverters.getConverters());
});
}
多端内容匹配
一套系统适配多端数据返回
默认规则
spring boot对端内容适配
基于请求头的内容协商:
- 客户端向服务端发送请求,携带HTTP标准的Accept请求头
- Accept: application/json 、text/xml、text/ymal
- 服务端根据客户端请求头期望的数据类型进行动态返回
- 内容协商的底层原理-HttpMessageConverter
- @ResponseBody由HttpMessageConverter处理
标注了@ResponseBody的返回值将由支持他的 HttpMessageConverter 写给浏览器- 如果controller方法的返回值标注了@ResponseBody注解
- 请求进来先到 dispatcherServlet的Dodispatcher()进行处理
- 找到一个 HandlerAdapter 适配器,利用适配器来执行目标方法
- RequestMappingHandlerAdapter来执行,调用invokehandlerMethod() 来执行目标方法
- 目标方法执行之前,准备好两个东西
-
HandlerMethodArgumentResolver:参数解析器,确定目标方法每个参数值
-
HandlerMethodReturnValueHandler:返回值处理器,确定目标方法的返回值该怎么处理
-
-
RequestMappingHandlerAdapter里面的invokeAndHandle()真正执行目标方法
- 目标方法执行完成会返回返回值对象
- 找到一个合适的返回值处理器 HandlerMethodReturnValueHandler
- 最终找到 RequestResponseBodyMethodProcessor 能处理标注了@ResponseBody的注解的方法
-
RequestResponseBodyMethodProcessor 调用writeWithMessageConverters,利用了messageConverter把返回值写出去
- HttpMessageConverter 会先进行内容协商
- 便利所有的messageConverter,看谁支持这种的内容类型的数据
- 默认mesaageConverter有以下几种
- 最终因为要json,所以mappingjackson2httpmessageconverter支持写出Json
- jackson 用 objectMapper 把对象写出去
- webmvcAutoConfiguration 提供几种默认的 httpmessageconverters
- 他们的继承关系:
-
EnableWebMvcConfiguration 通过 addDefaultHttpMessageConverters 添加了默认的messageconverter;如下:
-
ByteArrayHttpMessageConverter: 支持字节数据读写
-
StringHttpMessageConverter:支持字符串读写
-
ResourceHttpMessageConverter:支持资源读写
-
ResourceRegionHttpMessageConverter:支持分区资源写出
-
AllEncompassingFormHttpMessageConverter:支持表单xml/json读写
-
MappingJackson2XmlHttpMessageConverter:支持请求响应体的json读写
-
- 如果controller方法的返回值标注了@ResponseBody注解
- @ResponseBody由HttpMessageConverter处理
系统提供默认的MessageConverter 功能有限,仅用于json或者普通返回数据。额外增加新的内容协商功能,必须增加新的 HttpMessageConverter
- HttpMessageConverter 怎么工作?合适什么工作?定制HttpMessageConverter来实现多端内容协商
- 怎么ymal返回支持类型?
- 导入依赖:
<dependency> <groupId>com.fasterxml.jackson.dataformat</groupId> <artifactId>jackson-dataformat-yaml</artifactId> </dependency>
- 把对象写出成yaml代码
-
Person person = new Person(); person.setAge(1); person.setName("weiwei"); person.setBirthday(new Date()); YAMLFactory yamlFactory = new YAMLFactory().disable(YAMLGenerator.Feature.WRITE_DOC_START_MARKER); ObjectMapper objectMapper = new ObjectMapper(yamlFactory); String s = objectMapper.writeValueAsString(person); System.out.println(s);
-
- 编写配置:
-
spring.mvc.contentnegotiation.media-types.yaml=text/ymal
-
-
增加httpmessageconverter组件,专门负责把对象写出成yaml格式
- 导入依赖:
- 怎么ymal返回支持类型?
- 只需要编写WebMvcConfigurer提供的configureMessageConverters底层,修改底层的MessageConverter
基于请求参数内容协商:
- 发送请求 GET/Projects/spring boot?format=json
- 匹配到@GetMapping("/projects/spring-boot")
- 根据参数协商,优先返回json类型数据【需要开启参数匹配设置】
- 发送请求Get/projects/spring-boot?format=xml,优先返回xml类型数据
spring boot中为什么默认把对象写成json格式?
在pom文件中打开web场景启动器
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency>在start-web包中有引用了 spring-boot-starter-json包
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-json</artifactId> <version>3.0.5</version> <scope>compile</scope> </dependency>而这个包里又引用了jackson.core包
<dependency> <groupId>com.fasterxml.jackson.core</groupId> <artifactId>jackson-databind</artifactId> <version>2.14.2</version> <scope>compile</scope> </dependency>因为默认web场景导入了jackson处理json的包;Jackson-core
所有天然支持将对象写成json格式
jackson也支持吧数据写为xml,但是需要导入xml的相关依赖
- 导入支持xml的包
<dependency> <groupId>com.fasterxml.jackson.dataformat</groupId> <artifactId>jackson-dataformat-xml</artifactId> </dependency>- 在dto类上添加支持xml的注解
@JacksonXmlRootElement 类似这样的:此时得到的效果就是这样的:
还有一种方法能修改数据格式就是开启基于请求参数的内容协商功能。默认参数format
- 在application.properties文件里添加
#开启基于请求参数的内容协商功能。默认参数format spring.mvc.contentnegotiation.favor-parameter=true #指定内容协商时使用的参数名。默认是format spring.mvc.contentnegotiation.parameter-name = type- 重启项目后在浏览器请求栏上输入http://localhost:9000/person?format=json就可得到json格式的响应报文
在浏览器请求栏上输入http://localhost:9000/person?format=xml就可得到xml格式的响应报文