常用注解 @Import
: 可以在第三方包的类直接导入ioc容器中
1 2 3 4 5 6 @Import(StringUtils.class) @Configuration @SpringBootConfiguration public class AppConfig { }
自动配置原理 场景启动器 : springboot提供了很多场景启动器,命名规则是spring-boot-starter-xx
。如:
1 2 3 4 5 6 7 8 <dependency > <groupId > org.springframework.boot</groupId > <artifactId > spring-boot-starter-web</artifactId > </dependency > <dependency > <groupId > org.springframework.boot</groupId > <artifactId > spring-boot-starter-data-redis</artifactId > </dependency >
场景启动器的启动器 : 所有场景启动器都会引入
-spring-boot-starter
1 2 3 4 5 6 <dependency > <groupId > org.springframework.boot</groupId > <artifactId > spring-boot-starter</artifactId > <version > 3.1.0</version > <scope > compile</scope > </dependency >
启动器会帮我们自动加载所有配置,这些配置都在/org/springframework/boot/spring-boot-autoconfigure/3.1.0/spring-boot-autoconfigure-3.1.0.jar!/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
WebMvcAutoConfiguration 注解 1 2 3 4 5 6 7 8 9 10 @AutoConfiguration(after = { DispatcherServletAutoConfiguration.class, TaskExecutionAutoConfiguration.class, ValidationAutoConfiguration.class }) @ConditionalOnWebApplication(type = Type.SERVLET) @ConditionalOnClass({ Servlet.class, DispatcherServlet.class, WebMvcConfigurer.class }) @ConditionalOnMissingBean(WebMvcConfigurationSupport.class) @AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE + 10) @ImportRuntimeHints(WebResourcesRuntimeHints.class) public class WebMvcAutoConfiguration { }
静态资源 org.springframework.boot.autoconfigure.web.servlet.WebMvcAutoConfiguration.WebMvcAutoConfigurationAdapter.addResourceHandlers
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 @Override public void addResourceHandlers (ResourceHandlerRegistry registry) { if (!this .resourceProperties.isAddMappings()) { logger.debug("Default resource handling disabled" ); return ; } addResourceHandler(registry, this .mvcProperties.getWebjarsPathPattern(), "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); } }); }
规则1: 如果访问webjars/webjars/**
就去classpath:/META-INF/resources/webjars/
寻找资源
1 2 3 4 private String webjarsPathPattern = "/webjars/**" ;
可以去webjars的官网找自己想要的资源,如css
等。网址: https://www.webjars.org/
规则2: 所有静态资源需要放到指定的目录下
1 2 3 4 5 private String[] staticLocations = CLASSPATH_RESOURCE_LOCATIONS;
规则3: 静态资源的缓存设置
1 2 3 4 5 6 7 8 9 10 11 12 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); }
所有静态资源缓存设置都在WebProperties下进行配置setCachePeriod
: 缓存周期,单位ssetCacheControl
: http缓存控制setUseLastModified
: 是否使用最后一次修改。即:先访问拿到静态资源的最后一次修改时间,可以来判断是否用本地资源。
缓存实验 相关配置
1 2 3 4 5 6 7 8 9 10 spring.web.resources.add-mappings =true spring.web.resources.cache.use-last-modified =true spring.web.resources.cache.period =3600 spring.web.resources.cache.cachecontrol.max-age =7200
可以通过请求静态资源,来查看f12控制台。
欢迎页 handlermapping
: 处理器映射,根据path找到对应的控制器方法。 欢迎页bean
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 @Bean public WelcomePageHandlerMapping welcomePageHandlerMapping (ApplicationContext applicationContext, FormattingConversionService mvcConversionService, ResourceUrlProvider mvcResourceUrlProvider) { return createWelcomePageHandlerMapping(applicationContext, mvcConversionService, mvcResourceUrlProvider, WelcomePageHandlerMapping::new ); } private <T extends AbstractUrlHandlerMapping> T createWelcomePageHandlerMapping ( ApplicationContext applicationContext, FormattingConversionService mvcConversionService, ResourceUrlProvider mvcResourceUrlProvider, WelcomePageHandlerMappingFactory<T> factory) { TemplateAvailabilityProviders templateAvailabilityProviders = new TemplateAvailabilityProviders( applicationContext); String staticPathPattern = this .mvcProperties.getStaticPathPattern(); T handlerMapping = factory.create(templateAvailabilityProviders, applicationContext, getIndexHtmlResource(), staticPathPattern); handlerMapping.setInterceptors(getInterceptors(mvcConversionService, mvcResourceUrlProvider)); handlerMapping.setCorsConfigurations(getCorsConfigurations()); return handlerMapping; }
同样的,this.mvcProperties.getStaticPathPattern()
会在/**
下找静态资源,方法getIndexHtmlResource()
寻找欢迎页
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 private String[] staticLocations = CLASSPATH_RESOURCE_LOCATIONS; public String[] getStaticLocations() { return this .staticLocations; } private Resource getIndexHtmlResource () { for (String location : this .resourceProperties.getStaticLocations()) { Resource indexHtml = getIndexHtmlResource(location); if (indexHtml != null ) { return indexHtml; } } ServletContext servletContext = getServletContext(); if (servletContext != null ) { return getIndexHtmlResource(new ServletContextResource(servletContext, SERVLET_LOCATION)); } return null ; } private Resource getIndexHtmlResource (String location) { return getIndexHtmlResource(this .resourceLoader.getResource(location)); } private Resource getIndexHtmlResource (Resource location) { try { Resource resource = location.createRelative("index.html" ); if (resource.exists() && (resource.getURL() != null )) { return resource; } } catch (Exception ex) { } return null ; } @Bean public WebMvcConfigurer webMvcConfigurer () { return new WebMvcConfigurer() { @Override public void addResourceHandlers (ResourceHandlerRegistry registry) { WebMvcConfigurer.super .addResourceHandlers(registry); } }; }
在指定路径下放一个index.html
则项目启动时就会访问。
favicon 同样,放在指定静态资源路径下,则会生效。这是因为chrome浏览器在访问网页时会自动去访问网站的favicon.ico
自定义配置 可以在application.properties
中进行配置。
1 2 3 4 5 6 7 spring.web.resources.static-locations =classpath:/demo/ spring.mvc.webjars-path-pattern =/mj/** spring.mvc.static-path-pattern =/static/**
也可以用代码的方式配置
1 2 3 4 5 6 7 8 9 10 11 12 13 @Configuration @EnableWebMvc public class DemoWebConfig implements WebMvcConfigurer { @Override public void addResourceHandlers (ResourceHandlerRegistry registry) { WebMvcConfigurer.super .addResourceHandlers(registry); registry.addResourceHandler("/static/**" ) .addResourceLocations("classpath:/demo/" ) ; } }
在WebMvcAutoConfiguration
中,有一个开启自动配置类EnableWebMvcConfiguration
。
EnableWebMvcConfiguration
继承DelegatingWebMvcConfiguration
。并且DelegatingWebMvcConfiguration
有一个属性WebMvcConfigurerComposite configurers
。
会通过@Autowired
自动注入到容器中。会去遍历调用所有WebMvcConfigurer
的方法。
路径匹配规则 在Spring5.3,SpringMVC实现controller的路径匹配规则是通过策略AntPathMatcher
,但是现在已经支持PathPatternParser
。并且可以通过配置来指定。 路径匹配规则:*
: 匹配多个或0字符;?
: 匹配一个或0字符;/**
: 匹配多个目录;[a-z]+
: 匹配a-z多个字符。 SpringBoot中默认的策略是PathPatternParser
,与AntPathMatcher
不同的是:/**
在PathPatternParser
中匹配只能用在末尾。 可以通过配置来改变使用的路径匹配策略
1 spring.mvc.pathmatch.matching-strategy =ant_path_matcher
如果使用的是PathPatternParser
,在路径匹配中存在非末尾的/**
,则会在项目启动时报错。Fix this pattern in your application or switch to the legacy parser implementation with 'spring.mvc.pathmatch.matching-strategy=ant_path_matcher'.
在配置文件中修改策略即可。
一个接口适配多端数据返回(内容协商)
基于请求头的内容协商 客户端向服务端发送请求时,在请求头中通过Accept
来指定需要接收的响应格式,如Accept:application/text
、Accept:application/json
。
基于请求参数的内容协商 (默认禁用 ) 例如在发送请求时,在请求参数中添加?format=json
来指定需要接收的响应 开启方式:
1 2 spring.mvc.contentnegotiation.favor-parameter =true
spring默认支持json
内容协商原理-HttpMessageConverter
定制HttpMessageConverter
来实现多端内容协商WebMvcConfigurer
提供了configureMessageConverters
,修改底层的messageConverter
@ResponseBody由底层的HttpMessageConverter
处理 所有请求到达服务器,都是由DispatcherServlet
拦截来进行路径映射到对应的controller
来处理。
方法:org.springframework.web.servlet.DispatcherServlet.doDispatch
—>> 获取处理器适配器 HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());
—>> 由适配器调用实际的控制处理器 mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
—>> 实际处理方法org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.invokeHandlerMethod
—>> 处理请求参数解析HandlerMethodArgumentResolverComposite argumentResolvers
—>> 处理返回值解析HandlerMethodReturnValueHandlerComposite returnValueHandlers
—>> 执行真正的方法 org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod.invokeAndHandle
—>> 会调用我们写的controller org.springframework.web.method.support.InvocableHandlerMethod.invokeForRequest
—>> 处理返回值 org.springframework.web.method.support.HandlerMethodReturnValueHandlerComposite.handleReturnValue
通过方法org.springframework.web.method.support.HandlerMethodReturnValueHandlerComposite.selectHandler
来选择满足要求的返回值处理器 判断方式: 遍历容器中的所有返回值处理器,与调用实际方法的返回值进行判断,找到能够处理返回值的处理器
而判断条件正式是否含有`@ResponseBody`
RequestResponseBodyMethodProcessor#handleReturnValue
处理返回值; 实际处理方法: writeWithMessageConverters
拿到要求的响应体类型,确定最后的MediaType
后,遍历所有的List<HttpMessageConverter<?>> messageConverters
来处理返回值由对应的`converter`来处理
WebMvcConfigurationSupport 提供了很多默认的配置
内容协商示例-YAML
依赖
1 2 3 4 <dependency > <groupId > com.fasterxml.jackson.dataformat</groupId > <artifactId > jackson-dataformat-yaml</artifactId > </dependency >
代码demo
1 2 3 4 5 6 7 8 9 10 11 12 public static void main (String[] args) throws JsonProcessingException { YAMLFactory yamlFactory = new YAMLFactory(); yamlFactory.disable(YAMLGenerator.Feature.WRITE_DOC_START_MARKER); ObjectMapper mapper = new ObjectMapper(yamlFactory); User user = new User(); user.setAge(12 ); user.setId(2L ); user.setName("jack" ); String s = mapper.writeValueAsString(user); System.out.println(s); }
在配置文件中新增内容协商的类型
1 2 spring.mvc.contentnegotiation.media-types.yaml =application/yaml
新建自定义的converter
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 public class DemoYamlMessageConverter extends AbstractHttpMessageConverter <Object > { private final ObjectMapper mapper; public DemoYamlMessageConverter () { super (new MediaType("application" , "yaml" , StandardCharsets.UTF_8)); mapper = new ObjectMapper(new YAMLFactory().disable(YAMLGenerator.Feature.WRITE_DOC_START_MARKER)); } @Override protected boolean supports (Class clazz) { return true ; } @Override protected Object readInternal (Class clazz, HttpInputMessage inputMessage) throws IOException, HttpMessageNotReadableException { return null ; } @Override protected void writeInternal (Object o, HttpOutputMessage outputMessage) throws IOException, HttpMessageNotWritableException { try (OutputStream os = outputMessage.getBody()) { this .mapper.writeValue(os, o); } } }
注意: 我们需要告知我们的converter
是用来处理什么类型的,我们在构造器中调用父类构造器来进行指定,所指定的类型需要和配置文件中保持一致
将自定义的converter加入到容器中
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 @Configuration public class DemoWebConfig implements WebMvcConfigurer { @Override public void addResourceHandlers (ResourceHandlerRegistry registry) { WebMvcConfigurer.super .addResourceHandlers(registry); registry.addResourceHandler("/static/**" ) .addResourceLocations("classpath:/demo/" ) ; } @Override public void configureMessageConverters (List<HttpMessageConverter<?>> converters) { WebMvcConfigurer.super .configureMessageConverters(converters); converters.add(new DemoYamlMessageConverter()); } }
整合thymeleaf 同样的,我们可以找到ThymeleafAutoConfiguration
,查看相关的配置参数。ThymeleafProperties
中有两个属性。请求时会去/templates
下找.html
文件
1 2 3 4 5 6 7 8 private String prefix = DEFAULT_PREFIX;private String suffix = DEFAULT_SUFFIX;
在html
中引入会有相关thymeleaf
提示<html lang="en" xmlns:th="http://www.thymeleaf.org">
thymeleaf基本语法
th:text
: 标签体内文本值渲染th:text
: 会转义html标签th:utext
: 不会转义,会显示html标签,以及各种设置好的样式,如服务端在Model
中放了<span style='color:red'>Jack</span>
则会显示红色字体。而th:text
则会转义。
th:属性
: 标签体内属性渲染th可以在任意html标签,这样可以做到动态替换
1 <img th:src ="${imgUrl}" style ="width:250px" >
th:attr
: 标签体内任意值渲染 任意属性指定
1 2 <img th:attr ="src=${imgUrl}, style=${myStyle}" >
th:if
th:each
… 条件判断、遍历等…
表达式
${} 取各种属性值
@{} 专门用来取路径,会动态的根据项目的跟路径进行路径的变化
1 <img th:src ="@{${imgUrl}}" />
热部署 1 2 3 4 <dependency > <groupId > org.springframework.boot</groupId > <artifactId > spring-boot-devtools</artifactId > </dependency >
thymeleaf的一些配置 1 2 3 4 5 6 spring.thymeleaf.prefix =classpath:/templates/ spring.thymeleaf.suffix =.html spring.thymeleaf.cache =true
SpringBoot3核心原理 1. 事件与监听器 生命周期监听 监听器: 自定义SpringApplicationRunListener
编写SpringApplicationRunListener
实现类
在META-INF/spring.factories
中配置org.springframework.boot.SpringApplicationRunListener=xxxListener
,还可以指定一个有参构造器,
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 package com.turbo.springboot3web.config;import org.springframework.boot.*;import org.springframework.context.ConfigurableApplicationContext;import org.springframework.core.env.ConfigurableEnvironment;import java.time.Duration;public class CustomRunListener implements SpringApplicationRunListener { @Override public void starting (ConfigurableBootstrapContext bootstrapContext) { System.out.println("======正在启动====" ); } @Override public void environmentPrepared (ConfigurableBootstrapContext bootstrapContext, ConfigurableEnvironment environment) { System.out.println("=======环境准备完成======" ); } @Override public void contextPrepared (ConfigurableApplicationContext context) { System.out.println("=======IOC上下文准备完成======" ); } @Override public void contextLoaded (ConfigurableApplicationContext context) { System.out.println("=======容器加载完成======" ); } @Override public void started (ConfigurableApplicationContext context, Duration timeTaken) { System.out.println("=======启动完成======" ); } @Override public void ready (ConfigurableApplicationContext context, Duration timeTaken) { System.out.println("=======准备就绪======" ); } @Override public void failed (ConfigurableApplicationContext context, Throwable exception) { System.out.println("=======应用启动失败" ); } }
SpringApplcationRunListener
可以感知Spring的全生命周期,并且可以对容器进行操作
ApplicationListener 也可以感知全阶段,基于事件监听
1 2 3 4 5 6 7 8 9 10 @FunctionalInterface public interface ApplicationListener <E extends ApplicationEvent > extends EventListener { void onApplicationEvent (E event) ; static <T> ApplicationListener<PayloadApplicationEvent<T>> forPayload(Consumer<T> consumer) { return event -> consumer.accept(event.getPayload()); } }
ApplicationRunner 感知特定阶段: 感知应用就绪(ready)
CommandLineRunner 感知特定阶段: 感知应用就绪(ready)
BootstrapRegistryInitializer 感知特定阶段: 感知引导启动器(starting)关键字SpringFactories
,方法中包含这个关键字,都是从spring.factories
配置文件里面去获取, 所以如果自定义,需要在spring.factories
中配置 如下代码: this.bootstrapRegistryInitializers = new ArrayList<>(getSpringFactoriesInstances(BootstrapRegistryInitializer.class));
1 2 3 4 5 6 7 8 9 10 public SpringApplication (ResourceLoader resourceLoader, Class<?>... primarySources) { this .resourceLoader = resourceLoader; Assert.notNull(primarySources, "PrimarySources must not be null" ); this .primarySources = new LinkedHashSet<>(Arrays.asList(primarySources)); this .webApplicationType = WebApplicationType.deduceFromClasspath(); this .bootstrapRegistryInitializers = new ArrayList<>(getSpringFactoriesInstances(BootstrapRegistryInitializer.class)); setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class)); setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class)); this .mainApplicationClass = deduceMainApplicationClass(); }
ApplicationContextInitializer 感知特定阶段: 感知容器准备(contextPrepared)
基于事件驱动开发(ApplicationListener
)
事件发布者: 事件发布者可以发送事件,实现接口ApplicationEventPublisherAware
, 实现方法注入事件发布者
事件监听者 实现自己的事件? extends ApplicationEvent
, 通过两种方式:
实现ApplitionListener
使用注解@EventListener