title: Spring Boot 总结
date: 2022-06-14 02:56:36
tags:
Spring Boot makes it easy to create stand-alone, production-grade Spring based Applications that you can “just run”.
能快速创建出生产级别的 Spring 应用
Spring Boot 是整合 Spring 技术栈的一站式框架,简化 Spring 技术栈的快速开发脚手架
James Lewis and Martin Fowler (2014) 提出微服务完整概念:https://martinfowler.com/microservices/
In short, the microservice architectural style is an approach to developing a single application as a suite of small services, each running in its own process and communicating with lightweight mechanisms, often an HTTP resource API. These services are built around business capabilities and independently deployable by fully automated deployment machinery. There is a bare minimum of centralized management of these services, which may be written in different programming languages and use different data storage technologies.-- James Lewis and Martin Fowler (2014)
困难:
解决:Spring Boot + Spring Cloud
原生应用如何上云。困难:
<parent>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-parentartifactId>
<version>2.6.2version>
<relativePath/>
parent>
<dependencies>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-webartifactId>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-testartifactId>
<scope>testscope>
dependency>
dependencies>
@SpringBootApplication
public class SpringbootTestApplication {
public static void main(String[] args) {
SpringApplication.run(SpringbootTestApplication.class, args);
}
}
server:
port: 8081
@Controller
public class TestController {
@RequestMapping("/index")
@ResponseBody
public String index(){
return "Hello SpringBoot";
}
}
直接运行 SpringbootTestApplication 主程序的 Main 方法
<build>
<plugins>
<plugin>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-maven-pluginartifactId>
<configuration>
<excludes>
<exclude>
<groupId>org.projectlombokgroupId>
<artifactId>lombokartifactId>
exclude>
excludes>
configuration>
plugin>
plugins>
build>
把项目打成jar包,直接在目标服务器执行即可。java -jar SpringbootTest-0.0.1-SNAPSHOT.jar
父项目做依赖管理
依赖管理
<parent>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-parentartifactId>
<version>2.6.4version>
parent>
它的父项目
<parent>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-dependenciesartifactId>
<version>2.6.4version>
parent>
几乎声明了所有开发中常用的依赖的版本号,自动版本仲裁机制
spring-boot-starter-*
: *
就某种场景*-spring-boot-starter
: 第三方为我们提供的简化开发的场景启动器<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starterartifactId>
<version>2.3.4.RELEASEversion>
<scope>compilescope>
dependency>
1、查看 spring-boot-dependencies 里面规定当前依赖的版本用的 key。
2、在当前项目里面重写配置
<properties>
<mysql.version>8.0.19mysql.version>
properties>
自动配好 Tomcat
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-tomcatartifactId>
<version>2.3.4.RELEASEversion>
<scope>compilescope>
dependency>
自动配好 SpringMVC
自动配好 Web 常见功能,如:字符编码问题
默认的包结构
@SpringBootApplication(scanBasePackages="fan")
,扩大层级,或者 @ComponentScan 指定扫描路径@SpringBootApplication
等同于
@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan("fan.springboottest")
各种配置拥有默认值
按需加载所有自动配置项
…
@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan(excludeFilters = { @Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),
@Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) })
public @interface SpringBootApplication{
}
@SpringBootConfiguration
@Configuration。代表当前是一个配置类
@ComponentScan
指定扫描哪些 Spring 注解
@EnableAutoConfiguration
@AutoConfigurationPackage
@Import(AutoConfigurationImportSelector.class)
public @interface EnableAutoConfiguration {
}
@Import(AutoConfigurationPackages.Registrar.class) // 给容器中导入一个组件
public @interface AutoConfigurationPackage {
}
// 利用 Registrar 给容器中导入一系列组件
// 将指定的一个包下的所有组件导入进来,MainApplication 所在包下
getAutoConfigurationEntry(annotationMetadata);
给容器中批量导入一些组件List configurations = getCandidateConfigurations(annotationMetadata, attributes)
获取到所有需要导入到容器中的配置类Map> loadSpringFactories(@Nullable ClassLoader classLoader);
得到所有的组件META-INF/spring.factories
位置来加载一个文件META-INF/spring.factories
位置的文件spring-boot-autoconfigure-2.6.4.jar
包里面也有 META-INF/spring.factories
文件里面写死了 Spring Boot 一启动就要给容器中加载的所有配置类
spring-boot-autoconfigure-2.3.4.RELEASE.jar/META-INF/spring.factories
# Auto Configure
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
org.springframework.boot.autoconfigure.admin.SpringApplicationAdminJmxAutoConfiguration,\
org.springframework.boot.autoconfigure.aop.AopAutoConfiguration,\
org.springframework.boot.autoconfigure.amqp.RabbitAutoConfiguration,\
org.springframework.boot.autoconfigure.batch.BatchAutoConfiguration,\
org.springframework.boot.autoconfigure.cache.CacheAutoConfiguration,\
org.springframework.boot.autoconfigure.cassandra.CassandraAutoConfiguration,\
org.springframework.boot.autoconfigure.context.ConfigurationPropertiesAutoConfiguration,\
org.springframework.boot.autoconfigure.context.LifecycleAutoConfiguration,\
org.springframework.boot.autoconfigure.context.MessageSourceAutoConfiguration,\
org.springframework.boot.autoconfigure.context.PropertyPlaceholderAutoConfiguration,\
org.springframework.boot.autoconfigure.couchbase.CouchbaseAutoConfiguration,\
org.springframework.boot.autoconfigure.dao.PersistenceExceptionTranslationAutoConfiguration,\
org.springframework.boot.autoconfigure.data.cassandra.CassandraDataAutoConfiguration,\
org.springframework.boot.autoconfigure.data.cassandra.CassandraReactiveDataAutoConfiguration,\
org.springframework.boot.autoconfigure.data.cassandra.CassandraReactiveRepositoriesAutoConfiguration,\
org.springframework.boot.autoconfigure.data.cassandra.CassandraRepositoriesAutoConfiguration,\
org.springframework.boot.autoconfigure.data.couchbase.CouchbaseDataAutoConfiguration,\
org.springframework.boot.autoconfigure.data.couchbase.CouchbaseReactiveDataAutoConfiguration,\
org.springframework.boot.autoconfigure.data.couchbase.CouchbaseReactiveRepositoriesAutoConfiguration,\
org.springframework.boot.autoconfigure.data.couchbase.CouchbaseRepositoriesAutoConfiguration,\
org.springframework.boot.autoconfigure.data.elasticsearch.ElasticsearchDataAutoConfiguration,\
org.springframework.boot.autoconfigure.data.elasticsearch.ElasticsearchRepositoriesAutoConfiguration,\
org.springframework.boot.autoconfigure.data.elasticsearch.ReactiveElasticsearchRepositoriesAutoConfiguration,\
org.springframework.boot.autoconfigure.data.elasticsearch.ReactiveElasticsearchRestClientAutoConfiguration,\
org.springframework.boot.autoconfigure.data.jdbc.JdbcRepositoriesAutoConfiguration,\
org.springframework.boot.autoconfigure.data.jpa.JpaRepositoriesAutoConfiguration,\
org.springframework.boot.autoconfigure.data.ldap.LdapRepositoriesAutoConfiguration,\
org.springframework.boot.autoconfigure.data.mongo.MongoDataAutoConfiguration,\
org.springframework.boot.autoconfigure.data.mongo.MongoReactiveDataAutoConfiguration,\
org.springframework.boot.autoconfigure.data.mongo.MongoReactiveRepositoriesAutoConfiguration,\
org.springframework.boot.autoconfigure.data.mongo.MongoRepositoriesAutoConfiguration,\
org.springframework.boot.autoconfigure.data.neo4j.Neo4jDataAutoConfiguration,\
org.springframework.boot.autoconfigure.data.neo4j.Neo4jRepositoriesAutoConfiguration,\
org.springframework.boot.autoconfigure.data.solr.SolrRepositoriesAutoConfiguration,\
org.springframework.boot.autoconfigure.data.r2dbc.R2dbcDataAutoConfiguration,\
org.springframework.boot.autoconfigure.data.r2dbc.R2dbcRepositoriesAutoConfiguration,\
org.springframework.boot.autoconfigure.data.r2dbc.R2dbcTransactionManagerAutoConfiguration,\
org.springframework.boot.autoconfigure.data.redis.RedisAutoConfiguration,\
org.springframework.boot.autoconfigure.data.redis.RedisReactiveAutoConfiguration,\
org.springframework.boot.autoconfigure.data.redis.RedisRepositoriesAutoConfiguration,\
org.springframework.boot.autoconfigure.data.rest.RepositoryRestMvcAutoConfiguration,\
org.springframework.boot.autoconfigure.data.web.SpringDataWebAutoConfiguration,\
org.springframework.boot.autoconfigure.elasticsearch.ElasticsearchRestClientAutoConfiguration,\
org.springframework.boot.autoconfigure.flyway.FlywayAutoConfiguration,\
org.springframework.boot.autoconfigure.freemarker.FreeMarkerAutoConfiguration,\
org.springframework.boot.autoconfigure.groovy.template.GroovyTemplateAutoConfiguration,\
org.springframework.boot.autoconfigure.gson.GsonAutoConfiguration,\
org.springframework.boot.autoconfigure.h2.H2ConsoleAutoConfiguration,\
org.springframework.boot.autoconfigure.hateoas.HypermediaAutoConfiguration,\
org.springframework.boot.autoconfigure.hazelcast.HazelcastAutoConfiguration,\
org.springframework.boot.autoconfigure.hazelcast.HazelcastJpaDependencyAutoConfiguration,\
org.springframework.boot.autoconfigure.http.HttpMessageConvertersAutoConfiguration,\
org.springframework.boot.autoconfigure.http.codec.CodecsAutoConfiguration,\
org.springframework.boot.autoconfigure.influx.InfluxDbAutoConfiguration,\
org.springframework.boot.autoconfigure.info.ProjectInfoAutoConfiguration,\
org.springframework.boot.autoconfigure.integration.IntegrationAutoConfiguration,\
org.springframework.boot.autoconfigure.jackson.JacksonAutoConfiguration,\
org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration,\
org.springframework.boot.autoconfigure.jdbc.JdbcTemplateAutoConfiguration,\
org.springframework.boot.autoconfigure.jdbc.JndiDataSourceAutoConfiguration,\
org.springframework.boot.autoconfigure.jdbc.XADataSourceAutoConfiguration,\
org.springframework.boot.autoconfigure.jdbc.DataSourceTransactionManagerAutoConfiguration,\
org.springframework.boot.autoconfigure.jms.JmsAutoConfiguration,\
org.springframework.boot.autoconfigure.jmx.JmxAutoConfiguration,\
org.springframework.boot.autoconfigure.jms.JndiConnectionFactoryAutoConfiguration,\
org.springframework.boot.autoconfigure.jms.activemq.ActiveMQAutoConfiguration,\
org.springframework.boot.autoconfigure.jms.artemis.ArtemisAutoConfiguration,\
org.springframework.boot.autoconfigure.jersey.JerseyAutoConfiguration,\
org.springframework.boot.autoconfigure.jooq.JooqAutoConfiguration,\
org.springframework.boot.autoconfigure.jsonb.JsonbAutoConfiguration,\
org.springframework.boot.autoconfigure.kafka.KafkaAutoConfiguration,\
org.springframework.boot.autoconfigure.availability.ApplicationAvailabilityAutoConfiguration,\
org.springframework.boot.autoconfigure.ldap.embedded.EmbeddedLdapAutoConfiguration,\
org.springframework.boot.autoconfigure.ldap.LdapAutoConfiguration,\
org.springframework.boot.autoconfigure.liquibase.LiquibaseAutoConfiguration,\
org.springframework.boot.autoconfigure.mail.MailSenderAutoConfiguration,\
org.springframework.boot.autoconfigure.mail.MailSenderValidatorAutoConfiguration,\
org.springframework.boot.autoconfigure.mongo.embedded.EmbeddedMongoAutoConfiguration,\
org.springframework.boot.autoconfigure.mongo.MongoAutoConfiguration,\
org.springframework.boot.autoconfigure.mongo.MongoReactiveAutoConfiguration,\
org.springframework.boot.autoconfigure.mustache.MustacheAutoConfiguration,\
org.springframework.boot.autoconfigure.orm.jpa.HibernateJpaAutoConfiguration,\
org.springframework.boot.autoconfigure.quartz.QuartzAutoConfiguration,\
org.springframework.boot.autoconfigure.r2dbc.R2dbcAutoConfiguration,\
org.springframework.boot.autoconfigure.rsocket.RSocketMessagingAutoConfiguration,\
org.springframework.boot.autoconfigure.rsocket.RSocketRequesterAutoConfiguration,\
org.springframework.boot.autoconfigure.rsocket.RSocketServerAutoConfiguration,\
org.springframework.boot.autoconfigure.rsocket.RSocketStrategiesAutoConfiguration,\
org.springframework.boot.autoconfigure.security.servlet.SecurityAutoConfiguration,\
org.springframework.boot.autoconfigure.security.servlet.UserDetailsServiceAutoConfiguration,\
org.springframework.boot.autoconfigure.security.servlet.SecurityFilterAutoConfiguration,\
org.springframework.boot.autoconfigure.security.reactive.ReactiveSecurityAutoConfiguration,\
org.springframework.boot.autoconfigure.security.reactive.ReactiveUserDetailsServiceAutoConfiguration,\
org.springframework.boot.autoconfigure.security.rsocket.RSocketSecurityAutoConfiguration,\
org.springframework.boot.autoconfigure.security.saml2.Saml2RelyingPartyAutoConfiguration,\
org.springframework.boot.autoconfigure.sendgrid.SendGridAutoConfiguration,\
org.springframework.boot.autoconfigure.session.SessionAutoConfiguration,\
org.springframework.boot.autoconfigure.security.oauth2.client.servlet.OAuth2ClientAutoConfiguration,\
org.springframework.boot.autoconfigure.security.oauth2.client.reactive.ReactiveOAuth2ClientAutoConfiguration,\
org.springframework.boot.autoconfigure.security.oauth2.resource.servlet.OAuth2ResourceServerAutoConfiguration,\
org.springframework.boot.autoconfigure.security.oauth2.resource.reactive.ReactiveOAuth2ResourceServerAutoConfiguration,\
org.springframework.boot.autoconfigure.solr.SolrAutoConfiguration,\
org.springframework.boot.autoconfigure.task.TaskExecutionAutoConfiguration,\
org.springframework.boot.autoconfigure.task.TaskSchedulingAutoConfiguration,\
org.springframework.boot.autoconfigure.thymeleaf.ThymeleafAutoConfiguration,\
org.springframework.boot.autoconfigure.transaction.TransactionAutoConfiguration,\
org.springframework.boot.autoconfigure.transaction.jta.JtaAutoConfiguration,\
org.springframework.boot.autoconfigure.validation.ValidationAutoConfiguration,\
org.springframework.boot.autoconfigure.web.client.RestTemplateAutoConfiguration,\
org.springframework.boot.autoconfigure.web.embedded.EmbeddedWebServerFactoryCustomizerAutoConfiguration,\
org.springframework.boot.autoconfigure.web.reactive.HttpHandlerAutoConfiguration,\
org.springframework.boot.autoconfigure.web.reactive.ReactiveWebServerFactoryAutoConfiguration,\
org.springframework.boot.autoconfigure.web.reactive.WebFluxAutoConfiguration,\
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
虽然 127 个场景的所有自动配置启动的时候默认全部加载。xxxxAutoConfiguration
按照条件装配规则(@Conditional),最终会按需配置
@Bean
@ConditionalOnBean(MultipartResolver.class) // 容器中有这个类型组件
@ConditionalOnMissingBean(name = DispatcherServlet.MULTIPART_RESOLVER_BEAN_NAME) //容器中没有这个名字 multipartResolver 的组件
public MultipartResolver multipartResolver(MultipartResolver resolver) {
// 给 @Bean 标注的方法传入了对象参数,这个参数的值就会从容器中找
// SpringMVC multipartResolver。防止有些用户配置的文件上传解析器不符合规范
// Detect if the user has created a MultipartResolver but named it incorrectly
return resolver;
}
给容器中加入了文件上传解析器
Spring Boot 默认会在底层配好所有的组件。但是如果用户自己配置了以用户的优先
@Bean
@ConditionalOnMissingBean
public CharacterEncodingFilter characterEncodingFilter() {
}
xxxxxAutoConfiguration —> 组件 —> xxxxProperties 里面拿值 ----> application.properties
最佳实践
告诉Spring Boot这是一个配置类 == 配置文件,配置类本身也是组件。配置类里面使用 @Bean 标注在方法上给容器注册组件,默认是单实例的
Full 模式与 Lite 模式
proxyBeanMethods:代理 bean 的方法:
Full(proxyBeanMethods = true)
【保证每个 @Bean 方法被调用多少次返回的组件都是单实例的,Spring Boot 总会检查这个组件是否在容器中有,获取的都是之前注册容器中的单实例对象】Lite(proxyBeanMethods = false)
【每个 @Bean 方法被调用多少次返回的组件都是新创建的】组件依赖必须使用Full模式默认。其他默认是否Lite模式
配置类
@Configuration(proxyBeanMethods = true /*false*/) // 代理
public class MyConfig {
@Bean
public User user01(){
User user = new User("张三", 17);
user.setPet(petCat());
return user;
}
@Bean("cat")
public Pet petCat(){
return new Pet("tom",78);
}
}
Bean
public class User {
private String username;
private int age;
private Pet pet;
}
@SpringBootApplication
public class SpringinitApplication {
public static void main(String[] args) {
ConfigurableApplicationContext run = SpringApplication.run(SpringbootTestApplication.class, args);
User user01 = (User) run.getBean("user01", User.class);
System.out.println(user01);
System.out.println("用户的宠物:" + user01.getPet());
Pet pet = (Pet) run.getBean("cat",Pet.class);
System.out.println(pet);
System.out.println(user01.getPet() == pet);
}
}
条件注解,需要某些 Bean 满足某种条件才加载
@Configuration(proxyBeanMethods = true) // 告诉SpringBoot这是一个配置类 == 配置文件
// @ConditionalOnBean(name = "tom") 存在名称为 tom 的 Bean 才去做某些事情
@ConditionalOnMissingBean(name = "tom")
public class MyConfig {
/**
* Full:外部无论对配置类中的这个组件注册方法调用多少次获取的都是之前注册容器中的单实例对象
* @return
*/
@Bean // 给容器中添加组件。以方法名作为组件的id。返回类型就是组件类型。返回的值,就是组件在容器中的实例
public User user01(){
User zhangsan = new User("zhangsan", 18);
//user组件依赖了Pet组件
zhangsan.setPet(tomcatPet());
return zhangsan;
}
@Bean("tom22")
public Pet tomcatPet(){
return new Pet("tomcat");
}
}
注解 | 作用 |
---|---|
@ConditionalOnProperty | application.properties 或 application.yml 中是否有满足条件的配置 |
@ConditionalOnBean | Bean 已经存在应用上下文时才会加载 |
@ConditionalOnMissingBean | Bean 不存在应用上下文时才会加载 |
@ConditionalOnClass | 某个类存在于 classpath 中才加载 |
@ConditionalOnMissingClass | 某个类不存在于 classpath 中才加载 |
@ConditionalOnExpression | 当条件为 true 时才加载 |
@ConditionalOnSingleCandidate | 只有指定类已存在于 BeanFactory 中,并且可以确定单个 |
@ConditionalOnResource | 加载的 bean 依赖指定资源存在于 classpath |
@ConditionalOnJndi | 只有指定的资源通过 JNDI 加载后才加载 bean |
@ConditionalOnJava | 只有运行指定版本的 Java 才会加载 Bean |
@ConditionalOnWebApplication | 只有运行在 web 应用里才会加载这个 bean |
@ConditionalOnNotWebApplication | 只有运行在非 web 应用里才会加载这个 bean |
@ConditionalOnCloudPlatform | 只有运行在指定的云平台上才加载指定的 bean,CloudPlatform 是 org.springframework.boot.cloud 下一个 enum 类型的类 |
例:
@ConditionalOnProperty
:application.properties
或 application.yml
中是否有满足条件的配置
配置文件:
fan.property=true
使用:
// @ConditionalOnProperty(prefix = "fan", name = "property", havingValue = "true")
@ConditionalOnProperty(value = "fan.property", havingValue = "true")
@Import:
// 给容器中自动创建出这两个类型的组件、默认组件的名字就是全类名
@Import({User.class, DBHelper.class})
@SpringBootApplication
public class SpringbootTestApplication {
public static void main(String[] args) {
ConfigurableApplicationContext run = SpringApplication.run(SpringbootTestApplication.class, args);
String[] users = run.getBeanNamesForType(User.class);
for (String user : users) {
System.out.println(user);
}
}
}
@ImportResource("classpath:beans.xml")
设置 Bean,给容器中添加组件,以方法名作为组件的 id,返回类型就是组件类型,返回值就是组件的容器中的实例。配置类里面使用 @Bean 标注在方法上给容器注册组件,默认是单实例的
@Bean
public User user01(){
User user = new User("张三", 17);
user.setPet(petCat());
return user;
}
@Bean("cat")
public Pet petCat(){
return new Pet("tom",78);
}
@SpringBootApplication
public class SpringbootTestApplication {
public static void main(String[] args) {
ConfigurableApplicationContext run = SpringApplication.run(SpringbootTestApplication.class, args);
String[] beanDefinitionNames = run.getBeanDefinitionNames();
for (String beanDefinitionName : beanDefinitionNames) {
System.out.println(beanDefinitionName); // 打印输出所有组件名
}
}
}
如何使用Java读取到配置文件properties或yml中的内容,并且把它封装到JavaBean中,以供随时使用
@Component + @ConfigurationProperties
/**
* 只有在容器中的组件,才会拥有SpringBoot提供的强大功能
*/
@Data
@Component // 注入容器
@ConfigurationProperties(prefix = "user")
public class User {
private Integer id;
private String name;
private String gender;
private String[] hobby;
}
user:
id: 1
name: 张三
hobby: [篮球, 游泳]
@Controller
public class TestController {
@Autowired
private User user;
@RequestMapping("/index")
@ResponseBody
public User index(){
return user;
}
}
@EnableConfigurationProperties + @ConfigurationProperties
@Data
@ConfigurationProperties(prefix = "user")
public class User {
private Integer id;
private String name;
private String gender;
private String[] hobby;
}
@Configuration
@EnableConfigurationProperties(User.class)
// 1、开启Car配置绑定功能
// 2、把这个Car这个组件自动注册到容器中
public class MyConfig {
}
自定义的类和配置文件绑定一般没有提示
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-configuration-processorartifactId>
<optional>trueoptional>
dependency>
<build>
<plugins>
<plugin>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-maven-pluginartifactId>
<configuration>
<excludes>
<exclude>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-configuration-processorartifactId>
exclude>
excludes>
configuration>
plugin>
plugins>
build>
@Value 和 @ConfigurationProperties 注解都能读取配置文件中的属性值并绑定到 JavaBean 中。当我们只需要读取配置文件中的某一个配置时,可以通过 @Value 注解获取
使用位置不同
@ConfigurationProperties:标注在 JavaBean 的类名上
@Value:标注在 JavaBean 的属性上
功能不同
@ConfigurationProperties:用于批量绑定配置文件中的配置
@Value:只能一个一个的指定需要绑定的配置
松散绑定支持不同
@ConfigurationProperties:支持松散绑定(松散语法),例如实体类 Person 中有一个属性为 lastName,那么配置文件中的属性名支持以下写法:
@Vaule:不支持松散绑定
应用场景不同
@Value 和 @ConfigurationProperties 两个注解之间,并没有明显的优劣之分,它们只是适合的应用场景不同而已。
如果将所有的配置都集中到 application.properties 或 application.yml 中,那么这个配置文件会十分的臃肿且难以维护,因此我们通常会将与 Spring Boot 无关的配置(例如自定义配置)提取出来,写在一个单独的配置文件中,并在对应的 JavaBean 上使用 @PropertySource 注解指向该配置文件
@PropertySource(value = "classpath:person.properties") //指向对应的配置文件
@Component
@ConfigurationProperties(prefix = "person")
public class Person {
private String lastName;
private Integer age;
private Boolean boss;
private Date birth;
private Map<String, Object> maps;
private List<Object> lists;
private Dog dog;
}
Spring Boot provides auto-configuration for Spring MVC that works well with most applications.(大多场景我们都无需自定义配置)
The auto-configuration adds the following features on top of Spring’s defaults(添加如下功能):
If you want to keep those Spring Boot MVC customizations and make more MVC customizations (interceptors, formatters, view controllers, and other features), you can add your own @Configuration class of type WebMvcConfigurer but without @EnableWebMvc.
不用 @EnableWebMvc 注解。使用 @Configuration + WebMvcConfigurer 自定义规则
If you want to provide custom instances of RequestMappingHandlerMapping, RequestMappingHandlerAdapter, or ExceptionHandlerExceptionResolver, and still keep the Spring Boot MVC customizations, you can declare a bean of type WebMvcRegistrations and use it to provide custom instances of those components.
声明 WebMvcRegistrations 改变默认底层组件
If you want to take complete control of Spring MVC, you can add your own @Configuration annotated with @EnableWebMvc, or alternatively add your own @Configuration-annotated DelegatingWebMvcConfiguration as described in the Javadoc of @EnableWebMvc.
使用 @EnableWebMvc + @Configuration + DelegatingWebMvcConfiguration 全面接管 SpringMVC
http://localhost:xxx/aaa.png
server:
port: xxxx // 端口号
只要静态资源放在类路径下: called /static (or /public or /resources or /META-INF/resources
它们的优先级顺序为:classpath:/META-INF/resources/ > classpath:/resources/ > classpath:/static/ > classpath:/public/
访问 : 当前项目根路径/ + 静态资源名。原理: 静态映射/
请求进来,先去找 Controller 看能不能处理。不能处理的所有请求又都交给静态资源处理器。静态资源也找不到则响应 404 页
改变默认的静态资源路径后,原先的默认路径就失效了
spring:
web:
resources:
static-locations: [classpath:/aaa/]
# static-locations: classpath:/aaa/ classpath之后的不能加空格
设置后在访问的静态资源路径前必须加上 /xxx。http://localhost:8080/xxx/aaa.png
spring:
mvc:
static-path-pattern: /xxx/** // 路径 http://localhost:8080/xxx/**
自动映射 /webjars/。https://www.webjars.org/
<dependency>
<groupId>org.webjarsgroupId>
<artifactId>jqueryartifactId>
<version>3.5.1version>
dependency>
访问地址:http://localhost:8081/webjars/jquery/3.5.1/jquery.js
后面地址要按照依赖里面的包路径
设置后浏览器访问的路径前必须加上 /xxx
。http://localhost:8080/world/res/aaa.png
/访问路径/静态资源路径
server:
servlet:
context-path: /xxx // 路径 http://localhost:8080/xxx/
静态资源路径下 index.html
spring:
# mvc:
# static-path-pattern: /res/** 这个会导致welcome page功能失效
resources:
static-locations: [classpath:/haha/]
Controller 能处理 /index
favicon.ico 放在静态资源目录下即可
spring:
# mvc:
# static-path-pattern: /res/** 这个会导致 Favicon 功能失效
@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 {}
@Configuration(proxyBeanMethods = false)
@Import(EnableWebMvcConfiguration.class)
@EnableConfigurationProperties({ WebMvcProperties.class, ResourceProperties.class })
@Order(0)
public static class WebMvcAutoConfigurationAdapter implements WebMvcConfigurer {}
WebMvcProperties==spring.mvc
、ResourceProperties==spring.resources
// 有参构造器所有参数的值都会从容器中确定
// ResourceProperties resourceProperties;获取和spring.resources绑定的所有的值的对象
// WebMvcProperties mvcProperties 获取和spring.mvc绑定的所有的值的对象
// ListableBeanFactory beanFactory Spring的beanFactory
// HttpMessageConverters 找到所有的HttpMessageConverters
// ResourceHandlerRegistrationCustomizer 找到 资源处理器的自定义器。=========
// DispatcherServletPath
// ServletRegistrationBean 给应用注册Servlet、Filter....
public WebMvcAutoConfigurationAdapter(ResourceProperties resourceProperties, WebMvcProperties mvcProperties,
ListableBeanFactory beanFactory, ObjectProvider<HttpMessageConverters> messageConvertersProvider,
ObjectProvider<ResourceHandlerRegistrationCustomizer> resourceHandlerRegistrationCustomizerProvider,
ObjectProvider<DispatcherServletPath> dispatcherServletPath,
ObjectProvider<ServletRegistrationBean<?>> servletRegistrations) {
this.resourceProperties = resourceProperties;
this.mvcProperties = mvcProperties;
this.beanFactory = beanFactory;
this.messageConvertersProvider = messageConvertersProvider;
this.resourceHandlerRegistrationCustomizer = resourceHandlerRegistrationCustomizerProvider.getIfAvailable();
this.dispatcherServletPath = dispatcherServletPath;
this.servletRegistrations = servletRegistrations;
}
@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
if (!this.resourceProperties.isAddMappings()) {
logger.debug("Default resource handling disabled");
return;
}
Duration cachePeriod = this.resourceProperties.getCache().getPeriod();
CacheControl cacheControl = this.resourceProperties.getCache().getCachecontrol().toHttpCacheControl();
// webjars的规则
if (!registry.hasMappingForPattern("/webjars/**")) {
customizeResourceHandlerRegistration(registry.addResourceHandler("/webjars/**")
.addResourceLocations("classpath:/META-INF/resources/webjars/")
.setCachePeriod(getSeconds(cachePeriod)).setCacheControl(cacheControl));
}
//
String staticPathPattern = this.mvcProperties.getStaticPathPattern();
if (!registry.hasMappingForPattern(staticPathPattern)) {
customizeResourceHandlerRegistration(registry.addResourceHandler(staticPathPattern)
.addResourceLocations(getResourceLocations(this.resourceProperties.getStaticLocations()))
.setCachePeriod(getSeconds(cachePeriod)).setCacheControl(cacheControl));
}
}
spring:
# mvc:
# static-path-pattern: /res/**
resources:
add-mappings: false 禁用所有静态资源规则
@ConfigurationProperties(prefix = "spring.resources", ignoreUnknownFields = false)
public class ResourceProperties {
private static final String[] CLASSPATH_RESOURCE_LOCATIONS = { "classpath:/META-INF/resources/",
"classpath:/resources/", "classpath:/static/", "classpath:/public/" };
/**
* Locations of static resources. Defaults to classpath:[/META-INF/resources/,
* /resources/, /static/, /public/].
*/
private String[] staticLocations = CLASSPATH_RESOURCE_LOCATIONS;
HandlerMapping:处理器映射。保存了每一个Handler能处理哪些请求。
@Bean
public WelcomePageHandlerMapping welcomePageHandlerMapping(ApplicationContext applicationContext,
FormattingConversionService mvcConversionService, ResourceUrlProvider mvcResourceUrlProvider) {
WelcomePageHandlerMapping welcomePageHandlerMapping = new WelcomePageHandlerMapping(
new TemplateAvailabilityProviders(applicationContext), applicationContext, getWelcomePage(),
this.mvcProperties.getStaticPathPattern());
welcomePageHandlerMapping.setInterceptors(getInterceptors(mvcConversionService, mvcResourceUrlProvider));
welcomePageHandlerMapping.setCorsConfigurations(getCorsConfigurations());
return welcomePageHandlerMapping;
}
WelcomePageHandlerMapping(TemplateAvailabilityProviders templateAvailabilityProviders,
ApplicationContext applicationContext, Optional<Resource> welcomePage, String staticPathPattern) {
if (welcomePage.isPresent() && "/**".equals(staticPathPattern)) {
// 要用欢迎页功能,必须是 /**
logger.info("Adding welcome page: " + welcomePage.get());
setRootViewName("forward:index.html");
} else if (welcomeTemplateExists(templateAvailabilityProviders, applicationContext)) {
// 调用Controller /index
logger.info("Adding welcome page template: index");
setRootViewName("index");
}
}
激活,设置为true
spring:
mvc:
hiddenmethod:
filter:
enabled: true
@Bean
public HiddenHttpMethodFilter hiddenHttpMethodFilter(){
HiddenHttpMethodFilter hiddenHttpMethodFilter = new HiddenHttpMethodFilter();
hiddenHttpMethodFilter.setMethodParam("xxx"); // 自定义值
return hiddenHttpMethodFilter;
}
<form action="/user" method="post">
// 默认值为_method
<input name="xxx" type="hidden" value="DELETE"> // 设置后自定义值xxx
<input value="reset_delete" type="submit">
form>
<form action="/user" method="post">
// 默认值为_method
<input name="xxx" type="hidden" value="PUT"> // 设置后自定义值xxx
<input value="reset_put" type="submit">
@RestController
public class Rest_Controller {
// @RequestMapping(value = "/user",method = RequestMethod.GET)
@GetMapping("/user")
public String getUser(){
return "GET-张三";
}
// @RequestMapping(value = "/user",method = RequestMethod.POST)
@PostMapping("/user")
public String saveUser(){
return "POST-张三";
}
// @RequestMapping(value = "/user",method = RequestMethod.PUT)
@PutMapping("/user")
public String putUser(){
return "PUT-张三";
}
// @RequestMapping(value = "/user",method = RequestMethod.DELETE)
@DeleteMapping("/user")
public String deleteUser(){
return "DELETE-张三";
}
}
YAML 文件,双引号 “张三” 和单引号 ‘张三’ 与直接写 张三 一样,但是 \n 在单引号中会作为字符串输出,在双引号中会作为换行输出,双引号不会转义,单引号会转义
person:
user-name: 张三
boss: true
birth: 2001/12/5
age: 15
# String[]
interests: # intersts: [篮球,足球]
- 篮球
- 足球
- 17
# List
animal: [猫,狗]
# score:
# english: 80
# math: 70
# Map
score: {math:79,english:80}
# Set
salarys:
- 8888
- 5555
pet:
name: 猫
weight: 99
# Map>
allPets:
sick:
- {name: 狗, weight: 88.4}
- name: 猫
weight: 54.3
health: [{name: 乌龟, weight: 55.3}, {name: 鱼, weight: 44.2}]
配置Bean(1)
Bean
@Component // 需要加容器
@ConfigurationProperties(prefix = "person")
public class Person {
private String userName;
private Boolean boss;
private Date birth;
private Integer age;
private Pet pet;
private String[] interests;
private List<String> animal;
private Map<String, Object> score;
private Set<Integer> salarys;
private Map<String, List<Pet>> allPets;
}
配置类和Bean(2)
配置类
@Configuration
@EnableConfigurationProperties(Car.class) // 设置了Bean的class,则不需要加容器
public class MyConfig {
}
Bean
//@Component
@ConfigurationProperties(prefix = "mycar")
public class Car {
private String brand;
}
@Controller
public class TestController {
@Autowired
private User user;
@RequestMapping("/index")
@ResponseBody
public User index(){
return user;
}
}
@xxxMapping
REST 风格支持(使用 HTTP 请求方式动词来表示对资源的操作)
@RequestMapping(value = "/user",method = RequestMethod.GET)
public String getUser(){
return "GET-张三";
}
@RequestMapping(value = "/user",method = RequestMethod.POST)
public String saveUser(){
return "POST-张三";
}
@RequestMapping(value = "/user",method = RequestMethod.PUT)
public String putUser(){
return "PUT-张三";
}
@RequestMapping(value = "/user",method = RequestMethod.DELETE)
public String deleteUser(){
return "DELETE-张三";
}
@Bean
@ConditionalOnMissingBean(HiddenHttpMethodFilter.class)
@ConditionalOnProperty(prefix = "spring.mvc.hiddenmethod.filter", name = "enabled", matchIfMissing = false)
public OrderedHiddenHttpMethodFilter hiddenHttpMethodFilter() {
return new OrderedHiddenHttpMethodFilter();
}
// 自定义filter
@Bean
public HiddenHttpMethodFilter hiddenHttpMethodFilter(){
HiddenHttpMethodFilter methodFilter = new HiddenHttpMethodFilter();
methodFilter.setMethodParam("_m"); // 将_method属性自定义为_m
return methodFilter;
}
REST 原理(表单提交要使用 REST 的时候)
REST 使用客户端工具
如 Postman 直接发送 Put、DELETE 等方式请求,无需 Filter
spring:
mvc:
hiddenmethod:
filter:
enabled: true #开启页面表单的Rest功能
SpringMVC 功能分析都从 org.springframework.web.servlet.DispatcherServlet-》doDispatch ()
protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
HttpServletRequest processedRequest = request;
HandlerExecutionChain mappedHandler = null;
boolean multipartRequestParsed = false;
WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
try {
ModelAndView mv = null;
Exception dispatchException = null;
try {
processedRequest = checkMultipart(request);
multipartRequestParsed = (processedRequest != request);
// 找到当前请求使用哪个Handler(Controller的方法)处理
mappedHandler = getHandler(processedRequest);
// HandlerMapping:处理器映射。/xxx->>xxxx
RequestMappingHandlerMapping:保存了所有 @RequestMapping 和 handler 的映射规则
所有的请求映射都在 HandlerMapping 中
protected HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
if (this.handlerMappings != null) {
for (HandlerMapping mapping : this.handlerMappings) {
HandlerExecutionChain handler = mapping.getHandler(request);
if (handler != null) {
return handler;
}
}
}
return null;
}
获取路径中 {xxx}
的值。http://localhost:8080/car/111/owner/zhangsan
@RequestMapping("car/{id}/owner/{username}")
public void getCar(@PathVariable("id") int id,
@PathVariable("username") String username,
@PathVariable Map<String,String> pv)
获取请求头的值
@RequestMapping("car/{id}/owner/{username}")
public void get(@RequestHeader("User-Agent") String userAgent,
@RequestHeader Map<String,String> header){
Map<String,Object> map = new HashMap<>();
map.put("userAgent",userAgent);
map.put("headers",header);
return map;
}
获取 Cookie 的值
@RequestMapping("car/{id}/owner/{username}")
public void get(@CookieValue("xxx") String cookie1, // xxx为cookie名
@CookieValue("xxx") Cookie cookie){
String s = cookie.getName() + cookie.getValue()
}
获取传递参数的值。http://localhost:8080/car/111/owner/zhangsan?age=18&inters=sing&inters=play
@RequestMapping("car/{id}/owner/{username}")
public void get(@RequestParam("age") int age,
@RequestParam("inters") List<String> inters,
@RequestParam Map<String,String> param){
}
@RequestMapping("/goto")
public String gotoPage(HttpServletRequest request){
request.setAttribute("code",200);
return "forward:/success";
}
@ResponseBody
@RequestMapping("/success")
public Map success(@RequestAttribute(value = "msg",required = false) String msg,
@RequestAttribute("code") int code){
}
获取 request 请求域的值,也可通过 HttpServletRequest 直接获取 request,然后通过 request 获取值。设置 required = false,获取的值为非必须,即请求域中可以不存在该值
@RequestMapping("/success")
public Map success(@RequestAttribute(value = "msg",required = false) String msg,
@RequestAttribute("code") int code,
HttpServletRequest request){
Object msg1 = request.getAttribute("msg");
Object code1 = request.getAttribute("code");
}
在 request 请求域中,可以传递 Map、Model 和 request、response 对象,同时 Map 和 Model 对象都可以直接通过 request 在请求域中获取到。传递的 Map 和 Model 底层其实是一个对象
@RequestMapping("/goto")
public String gotoPage(HttpServletRequest request,
Map<String,Object> map,
Model model,
HttpServletResponse response){
map.put("map","map");
model.addAttribute("model","model");
request.setAttribute("code",200);
Cookie cookie = new Cookie("c1","v1");
response.addCookie(cookie);
return "forward:/success";
}
@ResponseBody
@RequestMapping("/success")
public Map success(HttpServletRequest request,
@CookieValue("c1") String c1){
Object map = request.getAttribute("map");
Object model = request.getAttribute("model");
Object code = request.getAttribute("code");
}
/cars/sell;low=34;brand=byd,audi,yd
session.set(a,b) —> jsessionid —> cookie —> 每次发送请求携带
url 重写:/abc;jsessionid=xxx
把 Cookie 的值使用矩阵变量进行传递
默认移除 url 地址中的 ; 符号,设置值为 false 则开启矩阵变量
@Configuration
public class MyConfig implements WebMvcConfigurer{
@Bean
public WebMvcConfigurer webMvcConfigurer(){
return new WebMvcConfigurer() {
@Override
public void configurePathMatch(PathMatchConfigurer configurer) {
UrlPathHelper urlPathHelper = new UrlPathHelper();
urlPathHelper.setRemoveSemicolonContent(false);
configurer.setUrlPathHelper(urlPathHelper);
}
};
}
}
@Configuration
public class MyConfig implements WebMvcConfigurer{
@Override
public void configurePathMatch(PathMatchConfigurer configurer) {
UrlPathHelper urlPathHelper = new UrlPathHelper();
urlPathHelper.setRemoveSemicolonContent(false);
configurer.setUrlPathHelper(urlPathHelper);
}
}
http://localhost:8080/cars/sell;low=34;brand=byd,audi,yd
http://localhost:8080/cars/sell;low=34;brand=byd;brand=audi;brand=yd
@RequestMapping("/cars/{path}") // 包括后面的分号内容也算路径
public Map<String,Object> carsSell(@MatrixVariable("low") Integer low,
@MatrixVariable("brand") List<String> brand,
@PathVariable("path") String path){
}
http://localhost:8080/boss/1;age=10/2;age=20
@RequestMapping("/boss/{bossId}/{empId}")
public Map<String,Object> boss(@MatrixVariable(value = "age",pathVar = "bossId") int bossAge,
@MatrixVariable(value = "age",pathVar = "empId") int empAge){
}
WebRequest、ServletRequest、MultipartRequest、 HttpSession、javax.servlet.http.PushBuilder、Principal、InputStream、Reader、HttpMethod、Locale、TimeZone、ZoneId
ServletRequestMethodArgumentResolver 以上的部分参数:
@Override
public boolean supportsParameter(MethodParameter parameter) {
Class<?> paramType = parameter.getParameterType();
return (WebRequest.class.isAssignableFrom(paramType) ||
ServletRequest.class.isAssignableFrom(paramType) ||
MultipartRequest.class.isAssignableFrom(paramType) ||
HttpSession.class.isAssignableFrom(paramType) ||
(pushBuilder != null && pushBuilder.isAssignableFrom(paramType)) ||
Principal.class.isAssignableFrom(paramType) ||
InputStream.class.isAssignableFrom(paramType) ||
Reader.class.isAssignableFrom(paramType) ||
HttpMethod.class == paramType ||
Locale.class == paramType ||
TimeZone.class == paramType ||
ZoneId.class == paramType);
}
Map、Model(map、model里面的数据会被放在request的请求域 request.setAttribute)、Errors/BindingResult、RedirectAttributes( 重定向携带数据)、ServletResponse(response)、SessionStatus、UriComponentsBuilder、ServletUriComponentsBuilder
Map<String,Object> map, Model model, HttpServletRequest request 都是可以给request域中放数据,
request.getAttribute();
Map、Model类型的参数,会返回 mavContainer.getModel();—> BindingAwareModelMap 是 Model 也是 Map
mavContainer.getModel(); 获取到值的
可以自动类型转换与格式化,可以级联封装,ServletModelAttributeMethodProcessor
HTML 页面,提交后可自动绑定到 Person 类
<form action="/saveUser" method="post">
姓名:<input name="username" value="zhangsan" /> <br>
年龄:<input name="age" value="16"><br>
<input name="pet" value="狗,3">
<input type="submit">
form>
Controller 类
Bean
public class Person {
private String username;
private int age;
private Pet pet;
}
Controller
@ResponseBody
@RequestMapping("/saveUser")
public Person saveUser(Person person){
return person;
}
,配置类设置 convert,将传进来的值以
,
进行分割,第一个为姓名,第二个为年龄
@Bean
public WebMvcConfigurer webMvcConfigurer(){
return new WebMvcConfigurer() {
@Override
public void addFormatters(FormatterRegistry registry) {
registry.addConverter(new Converter<String, Pet>() {
@Override
public Pet convert(String s) {
if (!s.isEmpty()){
Pet pet = new Pet();
String[] split = s.split(",");
pet.setName(split[0]);
pet.setAge(Integer.parseInt(split[1]));
return pet;
}
return null;
}
});
}
};
}
0 - 支持方法上标注 @RequestMapping
1 - 支持函数式编程的
xxxxxx
// Actually invoke the handler.
//DispatcherServlet -- doDispatch
mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
mav = invokeHandlerMethod(request, response, handlerMethod); //执行目标方法
//ServletInvocableHandlerMethod
Object returnValue = invokeForRequest(webRequest, mavContainer, providedArgs);
//获取方法的参数值
Object[] args = getMethodArgumentValues(request, mavContainer, providedArgs);
============InvocableHandlerMethod==========================
protected Object[] getMethodArgumentValues(NativeWebRequest request, @Nullable ModelAndViewContainer mavContainer,
Object... providedArgs) throws Exception {
MethodParameter[] parameters = getMethodParameters();
if (ObjectUtils.isEmpty(parameters)) {
return EMPTY_ARGS;
}
Object[] args = new Object[parameters.length];
for (int i = 0; i < parameters.length; i++) {
MethodParameter parameter = parameters[i];
parameter.initParameterNameDiscovery(this.parameterNameDiscoverer);
args[i] = findProvidedArgument(parameter, providedArgs);
if (args[i] != null) {
continue;
}
if (!this.resolvers.supportsParameter(parameter)) {
throw new IllegalStateException(formatArgumentError(parameter, "No suitable resolver"));
}
try {
args[i] = this.resolvers.resolveArgument(parameter, mavContainer, request, this.dataBinderFactory);
}
catch (Exception ex) {
// Leave stack trace for later, exception may actually be resolved and handled...
if (logger.isDebugEnabled()) {
String exMsg = ex.getMessage();
if (exMsg != null && !exMsg.contains(parameter.getExecutable().toGenericString())) {
logger.debug(formatArgumentError(parameter, exMsg));
}
}
throw ex;
}
}
return args;
}
@Nullable
private HandlerMethodArgumentResolver getArgumentResolver(MethodParameter parameter) {
HandlerMethodArgumentResolver result = this.argumentResolverCache.get(parameter);
if (result == null) {
for (HandlerMethodArgumentResolver resolver : this.argumentResolvers) {
if (resolver.supportsParameter(parameter)) {
result = resolver;
this.argumentResolverCache.put(parameter, result);
break;
}
}
}
return result;
}
调用各自 HandlerMethodArgumentResolver 的 resolveArgument 方法即可
ServletModelAttributeMethodProcessor 这个参数处理器支持是否为简单类型
public static boolean isSimpleValueType(Class<?> type) {
return (Void.class != type && void.class != type &&
(ClassUtils.isPrimitiveOrWrapper(type) ||
Enum.class.isAssignableFrom(type) ||
CharSequence.class.isAssignableFrom(type) ||
Number.class.isAssignableFrom(type) ||
Date.class.isAssignableFrom(type) ||
Temporal.class.isAssignableFrom(type) ||
URI.class == type ||
URL.class == type ||
Locale.class == type ||
Class.class == type));
}
@Override
@Nullable
public final Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer,
NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) throws Exception {
Assert.state(mavContainer != null, "ModelAttributeMethodProcessor requires ModelAndViewContainer");
Assert.state(binderFactory != null, "ModelAttributeMethodProcessor requires WebDataBinderFactory");
String name = ModelFactory.getNameForParameter(parameter);
ModelAttribute ann = parameter.getParameterAnnotation(ModelAttribute.class);
if (ann != null) {
mavContainer.setBinding(name, ann.binding());
}
Object attribute = null;
BindingResult bindingResult = null;
if (mavContainer.containsAttribute(name)) {
attribute = mavContainer.getModel().get(name);
}
else {
// Create attribute instance
try {
attribute = createAttribute(name, parameter, binderFactory, webRequest);
}
catch (BindException ex) {
if (isBindExceptionRequired(parameter)) {
// No BindingResult parameter -> fail with BindException
throw ex;
}
// Otherwise, expose null/empty value and associated BindingResult
if (parameter.getParameterType() == Optional.class) {
attribute = Optional.empty();
}
bindingResult = ex.getBindingResult();
}
}
if (bindingResult == null) {
// Bean property binding and validation;
// skipped in case of binding failure on construction.
WebDataBinder binder = binderFactory.createBinder(webRequest, attribute, name);
if (binder.getTarget() != null) {
if (!mavContainer.isBindingDisabled(name)) {
bindRequestParameters(binder, webRequest);
}
validateIfApplicable(binder, parameter);
if (binder.getBindingResult().hasErrors() && isBindExceptionRequired(binder, parameter)) {
throw new BindException(binder.getBindingResult());
}
}
// Value type adaptation, also covering java.util.Optional
if (!parameter.getParameterType().isInstance(attribute)) {
attribute = binder.convertIfNecessary(binder.getTarget(), parameter.getParameterType(), parameter);
}
bindingResult = binder.getBindingResult();
}
// Add resolved attribute and BindingResult at the end of the model
Map<String, Object> bindingResultModel = bindingResult.getModel();
mavContainer.removeAttributes(bindingResultModel);
mavContainer.addAllAttributes(bindingResultModel);
return attribute;
}
GenericConversionService:在设置每一个值的时候,找它里面的所有 converter 哪个可以将这个数据类型(request 带来参数的字符串)转换到指定的类型(JavaBean – Integer)
byte – > file
@FunctionalInterfacepublic interface Converter
可以给 WebDataBinder 里面自定义 Converter。private static final class StringToNumber
自定义 Converter
// 1、WebMvcConfigurer定制化SpringMVC的功能
@Bean
public WebMvcConfigurer webMvcConfigurer(){
return new WebMvcConfigurer() {
@Override
public void configurePathMatch(PathMatchConfigurer configurer) {
UrlPathHelper urlPathHelper = new UrlPathHelper();
// 不移除;后面的内容。矩阵变量功能就可以生效
urlPathHelper.setRemoveSemicolonContent(false);
configurer.setUrlPathHelper(urlPathHelper);
}
@Override
public void addFormatters(FormatterRegistry registry) {
registry.addConverter(new Converter<String, Pet>() {
@Override
public Pet convert(String source) {
// 啊猫,3
if(!StringUtils.isEmpty(source)){
Pet pet = new Pet();
String[] split = source.split(",");
pet.setName(split[0]);
pet.setAge(Integer.parseInt(split[1]));
return pet;
}
return null;
}
});
}
};
}
将所有的数据都放在 ModelAndViewContainer;包含要去的页面地址 View。还包含 Model 数据
processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
renderMergedOutputModel(mergedModel, getRequestToExpose(request), response);
InternalResourceView:
@Override
protected void renderMergedOutputModel(
Map<String, Object> model, HttpServletRequest request, HttpServletResponse response) throws Exception {
// Expose the model object as request attributes.
exposeModelAsRequestAttributes(model, request);
// Expose helpers as request attributes, if any.
exposeHelpers(request);
// Determine the path for the request dispatcher.
String dispatcherPath = prepareForRendering(request, response);
// Obtain a RequestDispatcher for the target resource (typically a JSP).
RequestDispatcher rd = getRequestDispatcher(request, dispatcherPath);
if (rd == null) {
throw new ServletException("Could not get RequestDispatcher for [" + getUrl() +
"]: Check that the corresponding file exists within your web application archive!");
}
// If already included or response already committed, perform include, else forward.
if (useInclude(request, response)) {
response.setContentType(getContentType());
if (logger.isDebugEnabled()) {
logger.debug("Including [" + getUrl() + "]");
}
rd.include(request, response);
}
else {
// Note: The forwarded resource is supposed to determine the content type itself.
if (logger.isDebugEnabled()) {
logger.debug("Forwarding to [" + getUrl() + "]");
}
rd.forward(request, response);
}
}
暴露模型作为请求域属性
// Expose the model object as request attributes.
exposeModelAsRequestAttributes(model, request);
protected void exposeModelAsRequestAttributes(Map<String, Object> model,
HttpServletRequest request) throws Exception {
//model中的所有数据遍历挨个放在请求域中
model.forEach((name, value) -> {
if (value != null) {
request.setAttribute(name, value);
}
else {
request.removeAttribute(name);
}
});
}
jackson.jar + @ResponseBody,给前端自动返回 JSON 数据
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-webartifactId>
dependency>
web 场景自动引入了 json 场景
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-jsonartifactId>
<version>2.6.4version>
<scope>compilescope>
dependency>
try {
this.returnValueHandlers.handleReturnValue(
returnValue, getReturnValueType(returnValue), mavContainer, webRequest);
}
@Override
public void handleReturnValue(@Nullable Object returnValue, MethodParameter returnType,
ModelAndViewContainer mavContainer, NativeWebRequest webRequest) throws Exception {
HandlerMethodReturnValueHandler handler = selectHandler(returnValue, returnType);
if (handler == null) {
throw new IllegalArgumentException("Unknown return value type: " + returnType.getParameterType().getName());
}
handler.handleReturnValue(returnValue, returnType, mavContainer, webRequest);
}
RequestResponseBodyMethodProcessor
@Override
public void handleReturnValue(@Nullable Object returnValue, MethodParameter returnType,
ModelAndViewContainer mavContainer, NativeWebRequest webRequest)
throws IOException, HttpMediaTypeNotAcceptableException, HttpMessageNotWritableException {
mavContainer.setRequestHandled(true);
ServletServerHttpRequest inputMessage = createInputMessage(webRequest);
ServletServerHttpResponse outputMessage = createOutputMessage(webRequest);
// Try even with null return value. ResponseBodyAdvice could get involved.
// 使用消息转换器进行写出操作
writeWithMessageConverters(returnValue, returnType, inputMessage, outputMessage);
}
ModelAndView
Model
View
ResponseEntity
ResponseBodyEmitter
StreamingResponseBody
HttpEntity
HttpHeaders
Callable
DeferredResult
ListenableFuture
CompletionStage
WebAsyncTask
有 @ModelAttribute 且为对象类型的
@ResponseBody 注解 ---> RequestResponseBodyMethodProcessor;
Accept
传递可支持什么格式的返回内容,q
代表权重,优先级。*/*
表示所有格式都支持返回Accept:text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9
http://localhost:8080/test/person?format=json
spring:
contentnegotiation:
favor-parameter: true
<dependency>
<groupId>com.fasterxml.jackson.dataformatgroupId>
<artifactId>jackson-dataformat-xmlartifactId>
dependency>
判断当前响应头中是否已经有确定的媒体类型。MediaType
获取客户端(PostMan、浏览器)支持接收的内容类型。(获取客户端 Accept请求头字段)【application/xml】
遍历循环所有当前系统的 MessageConverter,看谁支持操作这个对象(Person)
找到支持操作 Person 的 converter,把 converter 支持的媒体类型统计出来
进行内容协商的最佳匹配媒体类型
用支持将对象转为最佳匹配媒体类型的 converter。调用它进行转化
导入了 jackson 处理 xml 的包,xml 的 converter 就会自动进来
WebMvcConfigurationSupport
jackson2XmlPresent = ClassUtils.isPresent("com.fasterxml.jackson.dataformat.xml.XmlMapper", classLoader);
if (jackson2XmlPresent) {
Jackson2ObjectMapperBuilder builder = Jackson2ObjectMapperBuilder.xml();
if (this.applicationContext != null) {
builder.applicationContext(this.applicationContext);
}
messageConverters.add(new MappingJackson2XmlHttpMessageConverter(builder.build()));
}
public class XMessageConverter implements HttpMessageConverter<Person> { // 返回Person格式
@Override
public boolean canRead(Class aClass, MediaType mediaType) {
return false;
}
@Override
public boolean canWrite(Class aClass, MediaType mediaType) { // 返回
return aClass.isAssignableFrom(Person.class);
}
@Override
public List<MediaType> getSupportedMediaTypes() {
return MediaType.parseMediaTypes("application/x"); // 设置支持为x格式 即 ?format=x
}
@Override
public Person read(Class<? extends Person> aClass, HttpInputMessage httpInputMessage) throws IOException, HttpMessageNotReadableException {
return null;
}
@Override
public void write(Person person, MediaType mediaType, HttpOutputMessage httpOutputMessage) throws IOException, HttpMessageNotWritableException {
String data = person.getUsername() + ";" + person.getAge();
OutputStream body = httpOutputMessage.getBody();
body.write(data.getBytes()); // 定义返回数据
}
}
public class MyConfig {
@Bean
public WebMvcConfigurer webMvcConfigurer(){
return new WebMvcConfigurer() {
@Override
public void extendMessageConverters(List<HttpMessageConverter<?>> converters) {
converters.add(new XMessageConverter());
}
};
}
}
将可传递的格式添加进 Map 集合里,添加了 json、xml 和自定义 x 格式,请求参数 json、xml和 x 格式则可正常返回。此时设置的是基于参数的策略 ParameterContentNegotiationStrategy
http://localhost:8080/test/person?format=x
public class MyConfig {
@Bean
public WebMvcConfigurer webMvcConfigurer(){
return new WebMvcConfigurer() {
@Override
public void extendMessageConverters(List<HttpMessageConverter<?>> converters) {
converters.add(new XMessageConverter());
}
@Override
public void configureContentNegotiation(ContentNegotiationConfigurer configurer) {
Map<String, MediaType> mediaTypes = new HashMap<>();
mediaTypes.put("json",MediaType.APPLICATION_JSON);
mediaTypes.put("xml",MediaType.APPLICATION_XML);
mediaTypes.put("x",MediaType.parseMediaType("application/x"));
ParameterContentNegotiationStrategy strategy = new ParameterContentNegotiationStrategy(mediaTypes);
configurer.strategies(Arrays.asList(strategy));
}
};
}
}
由于没有添加基于请求头的策略,这样设置会覆盖请求头策略,请求头的返回格式会失效。此时直接请求,不加 format 设置请求参数,无论请求头是什么返回格式都为添加进 Map 集合的第一个元素格式,即为 json 格式。优先级。假如想要请求头生效,需要再加一个基于请求头的策略 HeaderContentNegotiationStrategy。同样的,还可以添加更多的策略。
http://localhost:8080/test/person
public class MyConfig {
@Bean
public WebMvcConfigurer webMvcConfigurer(){
return new WebMvcConfigurer() {
@Override
public void extendMessageConverters(List<HttpMessageConverter<?>> converters) {
converters.add(new XMessageConverter());
}
@Override
public void configureContentNegotiation(ContentNegotiationConfigurer configurer) {
Map<String, MediaType> mediaTypes = new HashMap<>();
mediaTypes.put("json",MediaType.APPLICATION_JSON);
mediaTypes.put("xml",MediaType.APPLICATION_XML);
mediaTypes.put("x",MediaType.parseMediaType("application/x"));
ParameterContentNegotiationStrategy strategy = new ParameterContentNegotiationStrategy(mediaTypes);
HeaderContentNegotiationStrategy headerStrategy = new HeaderContentNegotiationStrategy();
configurer.strategies(Arrays.asList(strategy,headerStrategy));
}
};
}
}
https://www.thymeleaf.org/documentation.html
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-thymeleafartifactId>
dependency>
public static final String DEFAULT_PREFIX = "classpath:/templates/";
public static final String DEFAULT_SUFFIX = ".html"; //xxx.html
DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head> head>
<body> body>
<a href="comments.html"
th:href="@{/product/comments(prodId=${prod.id})}"
th:if="${not #lists.isEmpty(prod.comments)}">viewa>
<div th:switch="${user.role}">
<p th:case="'admin'">User is an administratorp>
<p th:case="#{roles.manager}">User is a managerp>
<p th:case="*">User is some other thingp>
div>
<tr th:each="prod,status : ${prods}">
<td th:text="${status.count}">idtd> // 计数
<td th:text="${prod.name}">nametd>
<td th:text="${prod.price}">pricetd>
<td th:text="${prod.inStock}? #{true} : #{false}">isStocktd>
tr>
前面加 / ,会自动将访问路径添加进来,只需要写后面的资源路径就行
<a th:href="@{/xxx}"> a>
<form th:action="@{/xxx}"> form>
携带参数在路径后面加上括号,如:@{/xxx(id = xxx)}
<a th:href="@{/xxx(id = ${user.id})}"> a>
public class LoginInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
//登录检查逻辑
HttpSession session = request.getSession();
Object loginUser = session.getAttribute("loginUser");
if(loginUser != null){
return true;
}
//拦截住。未登录。跳转到登录页
request.setAttribute("msg","请先登录");
// re.sendRedirect("/");
request.getRequestDispatcher("/").forward(request,response);
return false;
}
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
}
}
同样的在 WebMvcConfigurer 里进行拦截器配置
@Configuration
public class AdminWebConfig implements WebMvcConfigurer {
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new LoginInterceptor())
.addPathPatterns("/**") // 所有请求都被拦截包括静态资源
.excludePathPatterns("/","/login","/css/**","/fonts/**","/images/**","/js/**"); // 放行的请求
}
}
过滤器和拦截器非常相似,但是它们有很大的区别
拦截器是在 DispatcherServlet 这个 Servlet 中执行的,因此所有的请求最先进入 Filter,最后离开 Filter。其顺序如下:
Filter -> Interceptor.preHandle -> Handler -> Interceptor.postHandle -> Interceptor.afterCompletion -> Filter
拦截器本质上是面向切面编程(AOP),符合横切关注点的功能都可以放在拦截器中来实现, 主要的应用场景包括:
设置 type 为 file,多文件上传需要设置 multiple 属性,表单提交方式需要为 post ,并且需要加 enctype 属性
<form th:action="@{/upload}" method="post" enctype="multipart/form-data">
<div class="form-group">
<label for="exampleInputFile">单文件上传label>
<input type="file" name="headerImg" id="exampleInputFile">
<p class="help-block">Example block-level help text here.p>
div>
<div class="form-group">
<label for="exampleInputFile">多文件上传label>
<input type="file" name="photos" multiple>
<p class="help-block">Example block-level help text here.p>
div>
form>
spring:
servlet:
multipart:
max-file-size: 10MB // 单个文件的最大大小
max-request-size: 50MB // 请求的总文件最大大小
使用 @RequestPart 接收文件,使用 transferTo 写出文件
@Controller
public class FormTestController {
@PostMapping("/upload")
public String upload(@RequestParam("email") String email, // 参数值
@RequestPart("headerImg") MultipartFile headerImg, // 文件上传接收
@RequestPart("photos") MultipartFile[] photos){ // 多文件则数组接收
if (!headerImg.isEmpty()){
String originalFilename = headerImg.getOriginalFilename(); // 获取文件原始名
try {
headerImg.transferTo(new File("E:\\" + originalFilename)); // 服务器创建文件
} catch (IOException e) {
e.printStackTrace();
}
}
if (photos.length > 0){
for (MultipartFile photo : photos) {
String originalFilename = photo.getOriginalFilename();
try {
photo.transferTo(new File("E:\\" + originalFilename));
} catch (IOException e) {
e.printStackTrace();
}
}
}
return "main";
}
}
默认规则
ExceptionHandlerExceptionResolver
支持的ResponseStatusExceptionResolver
,把 @ResponseStatus 注解的信息底层调用 response.sendError(statusCode, resolvedReason);
Tomcat 发送的 /errorDefaultHandlerExceptionResolver
处理框架底层的异常response.sendError(HttpServletResponse.SC_BAD_REQUEST, ex.getMessage());
可以通过以下 3 种方式定制 Spring Boot 错误页面:
在 templates 文件夹或静态资源文件夹下,创建一个 error 文件夹,存放错误界面,会自动解析,跳转到自定义的错误界面
可以直接在模板引擎文件夹(/resources/templates
)下创建 error.html ,覆盖 Spring Boot 默认的错误视图页面(Whitelabel Error Page)
上述 5 种方式均可以定制 Spring Boot 错误页面,且它们的优先级顺序为:
创建一个全局异常处理类,加上 @ControllerAdvice 注解,增强。同时加上 @ExceptionHandler 注解。会跳转到返回的页面,返回异常属性要放入请求域中
int num = 1/0; // 空指针
// 处理整个web controller 的异常
@ControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler({ArithmeticException.class,NullPointerException.class}) // 处理全局异常
public String handleArithExcepyion(){
return "login"; // 视图地址
}
}
加上 @ResponseStatus 注解,继承 RuntimeException ,运行时异常。手动抛出异常
if (list.size() > 3){ // 越界
throw new UserTooManyException();
}
@ResponseStatus(value = HttpStatus.FORBIDDEN,reason = "用户数量太多")
public class UserTooManyException extends RuntimeException{ // 越界异常
public UserTooManyException(){
}
public UserTooManyException(String message){
super(message);
}
}
会跳转到 /error 目录下的异常页面,然后输出异常信息
<h1 th:text="${message}">h1>
<h1 th:text="${error}">h1>
<h1 th:text="${status}">h1>
设置最高优先级后,所有的异常全部都会被自定义的异常解析器解析,所有的异常都会变成自定义异常解析器的异常。相当于全局异常处理规则。上面定义的所有异常都失效,全成为自定义异常解析器定义的异常。
@Order(value = Ordered.HIGHEST_PRECEDENCE) // 设置最高优先级
@Component
public class CustomerHandleException implements HandlerExceptionResolver {
@Override
public ModelAndView resolveException(HttpServletRequest request, HttpServletResponse response,
Object handler, Exception ex) {
try {
response.sendError(511,"自定义错误");
} catch (IOException e) {
e.printStackTrace();
}
return new ModelAndView();
}
}
自定义错误属性处理工具
@Component
public class MyErrorAttributes extends DefaultErrorAttributes {
@Override
public Map<String, Object> getErrorAttributes(WebRequest webRequest, ErrorAttributeOptions options) {
Map<String, Object> errorAttributes = super.getErrorAttributes(webRequest, options);
// 添加自定义的错误数据
errorAttributes.put("company", "www.biancheng.net");
// 获取 MyExceptionHandler 传入 request 域中的错误数据
Map ext = (Map) webRequest.getAttribute("ext", 0);
errorAttributes.put("ext", ext);
return errorAttributes;
}
}
创建 Servlet ,在启动类加上 @ServletComponentScan 注解,配置包扫描
@ServletComponentScan(basePackages = "fan")
@SpringBootApplication
public class AdminmanagerApplication {
public static void main(String[] args) {
SpringApplication.run(AdminmanagerApplication.class, args);
}
}
直接访问即可响应,http://localhost:8081/MyServlet
@WebServlet(name = "MyServlet", value = "/MyServlet")
// @WebServlet(urlPatterns = "/my")
public class MyServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
this.doPost(request,response);
}
@Override
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
response.getWriter().write("6666");
}
}
直接创建原生组件即可使用
@ServletComponentScan(basePackages = "com.atguigu.admin")
:指定原生Servlet组件都放在那里@WebServlet(urlPatterns = "/my")
:效果:直接响应,没有经过 Spring 的拦截器@WebFilter(urlPatterns={"/css/*","/images/*"})
@WebListener
@WebFilter(filterName = "MyFilter", urlPatterns = "/images/*")
public class MyFilter implements Filter {
@Override
public void init(FilterConfig config) throws ServletException {
System.out.println("MyFilter初始化完成");
}
@Override
public void destroy() {
System.out.println("MyFilter销毁");
}
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws ServletException, IOException {
System.out.println("MyFilter工作");
chain.doFilter(request, response);
}
}
@WebListener
public class MyListener implements ServletContextListener, HttpSessionListener, HttpSessionAttributeListener {
public MyListener() {
}
@Override
public void contextInitialized(ServletContextEvent sce) {
/* This method is called when the servlet context is initialized(when the Web application is deployed). */
}
@Override
public void contextDestroyed(ServletContextEvent sce) {
/* This method is called when the servlet Context is undeployed or Application Server shuts down. */
}
}
创建一个配置类,注入 RegistrationBean 的 Bean。不使用 @WebServlet 等注解,在 RegistrationBean 里注入
@Configuration
public class MyRegisterConfig {
@Bean
public ServletRegistrationBean myServlet(){
MyServlet myServlet = new MyServlet(); // 创建的Servlet
return new ServletRegistrationBean(myServlet,"/myServlet","/my"); // servlet多路径
}
}
@Configuration
public class MyRegisterConfig {
@Bean
public ServletRegistrationBean myServlet(){
MyServlet myServlet = new MyServlet(); // 创建的Servlet
return new ServletRegistrationBean(myServlet,"/myServlet","/my"); // servlet多路径
}
@Bean
public FilterRegistrationBean myFilter(){
MyFilter myFilter = new MyFilter(); // 创建的Filter
// return new FilterRegistrationBean(myFilter,myServlet()); // 直接配置过滤servlet
FilterRegistrationBean filterRegistrationBean = new FilterRegistrationBean(myFilter);
filterRegistrationBean.setUrlPatterns(Arrays.asList("/my","/css/*")); // 配置自定义路径
return filterRegistrationBean;
}
}
@Configuration
public class MyRegisterConfig {
@Bean
public ServletListenerRegistrationBean myListener(){
MyListener myListener = new MyListener(); // 创建的Listener
return new ServletListenerRegistrationBean(myListener);
}
}
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-webartifactId>
<exclusions>
<exclusion>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-tomcatartifactId>
exclusion>
exclusions>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-undertowartifactId>
dependency>
WebServerFactoryCustomizer
@Component
public class CustomizationBean implements WebServerFactoryCustomizer<ConfigurableServletWebServerFactory> {
@Override
public void customize(ConfigurableServletWebServerFactory server) {
server.setPort(9000);
}
}
public class DelegatingWebMvcConfiguration extends WebMvcConfigurationSupport
@ConditionalOnMissingBean(WebMvcConfigurationSupport.class)
场景 starter - xxxxAutoConfiguration - 导入 xxx 组件 - 绑定 xxxProperties – 绑定配置文件项
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-data-jdbcartifactId>
dependency>
数据源的自动配置-HikariDataSource
自动配置的类:
DataSourceAutoConfiguration : 数据源的自动配置
@Configuration(proxyBeanMethods = false)
@Conditional(PooledDataSourceCondition.class)
@ConditionalOnMissingBean({ DataSource.class, XADataSource.class })
@Import({ DataSourceConfiguration.Hikari.class, DataSourceConfiguration.Tomcat.class,
DataSourceConfiguration.Dbcp2.class, DataSourceConfiguration.OracleUcp.class,
DataSourceConfiguration.Generic.class, DataSourceJmxConfiguration.class })
protected static class PooledDataSourceConfiguration
DataSourceTransactionManagerAutoConfiguration: 事务管理器的自动配置
JdbcTemplateAutoConfiguration: JdbcTemplate 的自动配置,可以来对数据库进行crud
@ConfigurationProperties(prefix = "spring.jdbc")
来修改JdbcTemplateJndiDataSourceAutoConfiguration: jndi 的自动配置
XADataSourceAutoConfiguration: 分布式事务相关的
Spring Boot自带版本仲裁,因此可以不用写版本信息,目前默认版本为 8.0.26 。同时 MySQL 驱动高版本兼容低版本。即使电脑版本为 MySQL 5 也可使用 MySQL 8 的驱动。修改版本直接加版本信息即可
<dependency>
<groupId>mysqlgroupId>
<artifactId>mysql-connector-javaartifactId>
dependency>
修改配置项,url 、username 、 password 、driver-class-name 属性
spring:
datasource:
url: jdbc:mysql:///xxx
username: root
password: xxx
driver-class-name: com.mysql.jdbc.Driver # 会自动适配高版本写法
# driver-class-name: com.mysql.cj.jdbc.Driver // 高版本写法
spring:
jdbc:
template:
query-timeout: 3 // 单位为秒
@Autowired
JdbcTemplate jdbcTemplate;
@ResponseBody
@GetMapping("/sql")
public String sql(){
String sql = "select count(*) from user";
Integer integer = jdbcTemplate.queryForObject(sql, Integer.class);
return integer.toString();
}
https://github.com/alibaba/druid/wiki/常见问题
<dependency>
<groupId>com.alibabagroupId>
<artifactId>druidartifactId>
<version>1.1.14version>
dependency>
@Configuration
public class MyDataSourceConfig {
@Bean
public DataSource dataSource() throws SQLException {
DruidDataSource druidDataSource = new DruidDataSource();
druidDataSource.setUrl();
druidDataSource.setUsername();
druidDataSource.setPassword();
return druidDataSource;
}
}
需要设置 url、username、password 等属性,可以使用配置文件来进行配置,由于与 JDBC 配置属性相同,可以使用 @ConfigurationProperties 注解引入 JDBC 的配置信息
@Configuration
public class MyDataSourceConfig {
@ConfigurationProperties("spring.datasource")
@Bean
public DataSource dataSource() throws SQLException {
DruidDataSource druidDataSource = new DruidDataSource();
return druidDataSource;
}
}
可使用 XML 配置文件方式
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource"
destroy-method="close">
<property name="url" value="${jdbc.url}" />
<property name="username" value="${jdbc.username}" />
<property name="password" value="${jdbc.password}" />
<property name="maxActive" value="20" />
<property name="initialSize" value="1" />
<property name="maxWait" value="60000" />
<property name="minIdle" value="1" />
<property name="timeBetweenEvictionRunsMillis" value="60000" />
<property name="minEvictableIdleTimeMillis" value="300000" />
<property name="testWhileIdle" value="true" />
<property name="testOnBorrow" value="false" />
<property name="testOnReturn" value="false" />
<property name="poolPreparedStatements" value="true" />
<property name="maxOpenPreparedStatements" value="20" />
bean>
http://localhost:8080/druid
开启监控统计和防火墙
@Configuration
public class MyDataSourceConfig {
@ConfigurationProperties("spring.datasource")
@Bean
public DataSource dataSource() throws SQLException {
DruidDataSource druidDataSource = new DruidDataSource();
// druidDataSource.setFilters("stat,wall"); // 开启监控统计、防火墙
return druidDataSource;
}
}
同样可在配置文件中配置 filters 属性
spring:
datasource:
url: jdbc:mysql:///db1
username: root
password: fan223
driver-class-name: com.mysql.cj.jdbc.Driver
filters: stat,wall // 配置监控统计、防火墙
StatViewServlet
提供监控信息展示的 HTML 页面、提供监控信息的 JSON API
可以在 StatViewServlet 中设置登录用户名和密码,设置后输入用户名和密码才可以进行监控
@Configuration
public class MyDataSourceConfig {
@Bean
public ServletRegistrationBean statViewServlet(){
StatViewServlet statViewServlet = new StatViewServlet();
ServletRegistrationBean<StatViewServlet> registrationBean = new ServletRegistrationBean<>(statViewServlet,"/druid/*");
registrationBean.addInitParameter("loginUsername","admin"); // 登录用户名
registrationBean.addInitParameter("loginPassword","123"); // 登录密码
return registrationBean;
}
}
<servlet>
<servlet-name>DruidStatViewservlet-name>
<servlet-class>com.alibaba.druid.support.http.StatViewServletservlet-class>
servlet>
<servlet-mapping>
<servlet-name>DruidStatViewservlet-name>
<url-pattern>/druid/*url-pattern>
servlet-mapping>
StatFilter
用于统计监控信息,如 SQL 监控、URL 监控
@Configuration
public class MyDataSourceConfig {
@Bean
public FilterRegistrationBean webStatFilter(){
WebStatFilter webStatFilter = new WebStatFilter();
FilterRegistrationBean<WebStatFilter> webStatFilterFilterRegistrationBean = new FilterRegistrationBean<>(webStatFilter);
webStatFilterFilterRegistrationBean.setUrlPatterns(Arrays.asList("/*")); // 拦截路径
webStatFilterFilterRegistrationBean.addInitParameter("exclusions","*.js,*.gif,*.jpg,*.png,*.css,*.ico,/druid/*"); // 释放路径
return webStatFilterFilterRegistrationBean;
}
}
需要给数据源中配置如下属性;可以允许多个filter,多个用,分割;如:
<property name="filters" value="stat,slf4j" />
系统中所有 Filter:| 别名 | Filter类名 |
| ------------- | ------------------------------------------------------- |
| default | com.alibaba.druid.filter.stat.StatFilter |
| stat | com.alibaba.druid.filter.stat.StatFilter |
| mergeStat | com.alibaba.druid.filter.stat.MergeStatFilter |
| encoding | com.alibaba.druid.filter.encoding.EncodingConvertFilter |
| log4j | com.alibaba.druid.filter.logging.Log4jFilter |
| log4j2 | com.alibaba.druid.filter.logging.Log4j2Filter |
| slf4j | com.alibaba.druid.filter.logging.Slf4jLogFilter |
| commonlogging | com.alibaba.druid.filter.logging.CommonsLogFilter |
慢 SQL 记录配置:
<bean id="stat-filter" class="com.alibaba.druid.filter.stat.StatFilter">
<property name="slowSqlMillis" value="10000" />
<property name="logSlowSql" value="true" />
bean>
使用 slowSqlMillis 定义慢SQL的时长
<dependency>
<groupId>com.alibabagroupId>
<artifactId>druid-spring-boot-starterartifactId>
<version>1.1.14version>
dependency>
private static final String FILTER_STAT_PREFIX = "spring.datasource.druid.filter.stat";
private static final String FILTER_CONFIG_PREFIX = "spring.datasource.druid.filter.config";
private static final String FILTER_ENCODING_PREFIX = "spring.datasource.druid.filter.encoding";
private static final String FILTER_SLF4J_PREFIX = "spring.datasource.druid.filter.slf4j";
private static final String FILTER_LOG4J_PREFIX = "spring.datasource.druid.filter.log4j";
private static final String FILTER_LOG4J2_PREFIX = "spring.datasource.druid.filter.log4j2";
private static final String FILTER_COMMONS_LOG_PREFIX = "spring.datasource.druid.filter.commons-log";
private static final String FILTER_WALL_PREFIX = "spring.datasource.druid.filter.wall";
spring:
datasource:
druid:
aop-patterns: fan.* #监控SpringBean,包
filters: stat,wall # 底层开启功能,stat(sql监控),wall(防火墙)
stat-view-servlet: # 配置监控页功能
enabled: true // 开启
login-username: admin
login-password: xxx
resetEnable: false
web-stat-filter: # 监控web
enabled: true
urlPattern: /*
exclusions: '*.js,*.gif,*.jpg,*.png,*.css,*.ico,/druid/*'
filter:
stat: # 对上面filters里面的stat的详细配置
slow-sql-millis: 1000
logSlowSql: true
enabled: true
wall: # 对上面filters里面的wall的详细配置
enabled: true
config:
drop-table-allow: false
最佳实战:
@MapperScan("fan.mapper")
简化,其他的接口就可以不用标注 @Mapper 注解https://github.com/mybatis
引入 Mybatis 的时候其实已经引了 JDBC 核心包,之前的 JDBC 核心包就不用引了
<dependency>
<groupId>org.mybatis.spring.bootgroupId>
<artifactId>mybatis-spring-boot-starterartifactId>
<version>2.1.4version>
dependency>
DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="fan.UserMapper">
<select id="getUser" resultType="fan.User">
select * from user where id=#{id}
select>
mapper>
mybatis:
# config-location: classpath:mybatis/mybatis-config.xml #全局配置文件位置
mapper-locations: classpath:mybatis/mapper/*.xml #sql映射文件位置
configuration:
map-underscore-to-camel-case: true
public interface TestMapper extends BaseMapper {
@Select("select * from test where id = #{id}")
public Test getTest(int id);
@Insert("insert into test values(null,#{name},#{age},#{gender})")
@Options(useGeneratedKeys = true,keyProperty = "id")
public void insert(User user);
}
引入 Mybatis-Plus 的同时已经引入了 Mybatis 和JDBC 的核心包,因此之前的两个核心包可以不用引入
<dependency>
<groupId>com.baomidougroupId>
<artifactId>mybatis-plus-boot-starterartifactId>
<version>3.4.1version>
dependency>
@MapperScan("fan.mapper")
批量扫描就行优点:
只需要我们的 Mapper 继承 BaseMapper 就可以拥有 CRUD 能力,无需编写 Mapper.xml 文件
创建Bean
一般来说 Bean 的名字与数据库表名对应,假如数据库表名修改了,可以使用 @TableName 标注数据库表名
// @TableName("xxx") // 对应数据库表名
public class User {
private int id;
private String name;
private int age;
private String gender;
}
创建 Mapper 接口
继承 BaseMapper,标注 Bean
public interface UserMapper extends BaseMapper<User> {
}
创建 Service
继承 ServiceImpl ,标注 Mapper 接口 和 Bean
public interface UserService extends IService<User> {
}
@Service
public class UserServiceImpl extends ServiceImpl<UserMapper,User> implements UserService {
}
创建 Controller
可直接使用 UserService 接口,调用 Mybatis-Plus 自带的 SQL 方法,进行 CRUD 操作
list() 方法,返回 List 集合,查询到的所有数据
page() 方法,返回分页数据
@Autowired
UserService userService;
@GetMapping("/editable_table")
public String editable_table(@RequestParam(value = "currentPage",defaultValue = "1") int currentPage, Model model){
List<User> list = UserService.list();
// model.addAttribute("users",list);
Page<User> userPage = new Page<>(currentPage,5); // 创建一个 BeanPage 对象,传入当前页码和每页显示数据数
Page<User> page = userService.page(userPage, null); // 调用 page 方法,传入 BeanPage 对象和条件
if (currentPage < 1){
userPage.setCurrent(1);
}else if (currentPage > page.getPages()){
userPage.setCurrent(page.getPages());
}
page = userService.page(userPage,null);
model.addAttribute("page",page);
return "table/editable_table";
}
HTML 页面
<tr class="" th:each="user,stats : ${page.records}">
<td th:text="${stats.count}">Jonathantd> // 可以 .属性名
<td th:text="${user.id}">Jonathantd>
<td th:text="${user.getName()}">Smithtd> // 也可以 get()方法
<td>[[${user.getAge}]]td>
<td>[[${user.gender}]]td>
<td>
<a th:href="@{/userDel/{id}(id = ${user.id},currentPage = ${page.current})}" class="btn btn-danger btn-sm">删除a>
td>
tr>
<ul>
<li class="prev"><a th:href="@{/editable_table(currentPage = ${page.current} - 1)}">← Preva>li>
<li th:class="${num == page.current} ? 'active' : ''" th:each="num : ${#numbers.sequence(1,page.pages)}">
<a th:href="@{/editable_table(currentPage = ${num})}">[[${num}]]a>
li>
<li class="next"><a th:href="@{/editable_table(currentPage = ${page.current} + 1)}">Next → a>li>
ul>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-data-redisartifactId>
dependency>
spring:
redis:
host: 124.222.118.90 # Redis服务器地址
port: 6379 # Redis服务器连接端口
client-type: lettuce # 可以选择客户端类型,默认为 lettuce
lettuce:
pool:
max-active: 10 # 连接池最大连接数(使用负值表示没有限制)
max-wait: -1 # 最大阻塞等待时间(负数表示没限制)
max-idle: 5 # 连接池中的最大空闲连接
min-idle: 0 # 连接池中的最小空闲连接
jedis:
pool:
max-active: 10
max-wait: 10
password: xxx # 密码
database: 0 # Redis数据库索引(默认为0)
connect-timeout: 1800000 # 连接超时时间(毫秒)
默认使用的是 Lettuce
<dependency>
<groupId>redis.clientsgroupId>
<artifactId>jedisartifactId>
dependency>
spring:
redis:
port: 6379 // 端口号
host: localhost // 主机
client-type: jedis // 客户端类型,Lettuce和Jedis
首先注入 RedisTemplate,然后使用 redisTemplate 的 opsForValue() 方法得到一个对象。使用该对象来对Redis 进行操纵
使用该对象的 increment(xxx) 方法,表示 xxx 的值自动加 1
@Controller
public class IndexController {
@Autowired
StringRedisTemplate stringRedisTemplate;
@GetMapping("/index.html")
public String index(Model model){
ValueOperations<String, String> operations = stringRedisTemplate.opsForValue();
operations.set("name","张三");
String name = operations.get("name");
System.out.println(name);
return "index";
}
}
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-testartifactId>
<scope>testscope>
dependency>
SpringBoot 2.4 以上版本移除了默认对 Vintage 的依赖。如果需要兼容 Junit4 需要自行引入(不能使用 Junit4 的功能 @Test)。 JUnit 5’s Vintage Engine Removed from spring-boot-starter-test
,如果需要继续兼容 Junit4 需要自行引入vintage
<dependency>
<groupId>org.junit.vintagegroupId>
<artifactId>junit-vintage-engineartifactId>
<scope>testscope>
<exclusions>
<exclusion>
<groupId>org.hamcrestgroupId>
<artifactId>hamcrest-coreartifactId>
exclusion>
exclusions>
dependency>
https://junit.org/junit5/docs/current/user-guide/#writing-tests-annotations
@Test
:表示方法是测试方法。但是与 JUnit4 的@Test不同,他的职责非常单一不能声明任何属性,拓展的测试将会由 Jupiter 提供额外测试@ParameterizedTest
:表示方法是参数化测试@RepeatedTest
:表示方法可重复执行@DisplayName
:为测试类或者测试方法设置展示名称@BeforeEach
:表示在每个单元测试之前执行@AfterEach
:表示在每个单元测试之后执行@BeforeAll
:表示在所有单元测试之前执行@AfterAll
:表示在所有单元测试之后执行@Tag
:表示单元测试类别,类似于 JUnit4 中的 @Categories@Disabled
:表示测试类或测试方法不执行,类似于 JUnit4 中的 @Ignore@Timeout
:表示测试方法运行如果超过了指定时间将会返回错误@ExtendWith
:为测试类或测试方法提供扩展类引用断言(assertions)是测试方法中的核心部分,用来对测试需要满足的条件进行验证。 这些断言方法都是 org.junit.jupiter.api.Assertions
的静态方法。JUnit 5 内置的断言可以分成如下几个类别:
方法 | 说明 |
---|---|
assertEquals | 判断两个对象或两个原始类型是否相等 |
assertNotEquals | 判断两个对象或两个原始类型是否不相等 |
assertSame | 判断两个对象引用是否指向同一个对象 |
assertNotSame | 判断两个对象引用是否指向不同的对象 |
assertTrue | 判断给定的布尔值是否为 True |
assertFalse | 判断给定的布尔值是否为 False |
assertNull | 判断给定的对象引用是否为 NULL |
assertNotNull | 判断给定的对象引用是否不为 NULL |
@Test
@DisplayName("simple assertion")
public void simple() {
assertEquals(3, 1 + 2, "simple math");
assertNotEquals(3, 1 + 1);
assertNotSame(new Object(), new Object());
Object obj = new Object();
assertSame(obj, obj);
assertFalse(1 > 2);
assertTrue(1 < 2);
assertNull(null);
assertNotNull(new Object());
}
@Test
@DisplayName("array assertion")
public void array() {
assertArrayEquals(new int[]{1, 2}, new int[] {1, 2});
}
@Test
@DisplayName("assert all")
public void all() {
assertAll("Math",
() -> assertEquals(2, 1 + 1,"number"),
() -> assertTrue(1 > 0)
);
}
@Test
@DisplayName("异常测试")
public void exceptionTest() {
ArithmeticException exception = Assertions.assertThrows(
//扔出断言异常
ArithmeticException.class, () -> System.out.println(1 % 0));
}
@Timeout(value = 500,unit = TimeUnit.MICROSECONDS)
@Test
@DisplayName("超时测试")
public void timeoutTest() {
//如果测试方法时间超过1s将会异常
//Assertions.assertTimeout(Duration.ofMillis(1000), () -> Thread.sleep(500));
Thread.sleep(600);
}
通过 Fail 方法直接使得测试失败
@Test
@DisplayName("fail")
public void shouldFail() {
if(1 == 1){
fail("This should fail");
}
}
JUnit 5 中的前置条件(assumptions【假设】)类似于断言,不同之处在于不满足的断言会使得测试方法失败,而不满足的前置条件只会使得测试方法的执行终止。 前置条件可以看成是测试方法执行的前提,当该前提不满足时,就没有继续执行的必要。
@DisplayName("前置条件")
public class AssumptionsTest {
private final String environment = "DEV";
@Test
@DisplayName("simple")
public void simpleAssume() {
assumeTrue(Objects.equals(this.environment, "DEV"));
assumeFalse(() -> Objects.equals(this.environment, "PROD"));
}
@Test
@DisplayName("assume then do")
public void assumeThenDo() {
assumingThat(
Objects.equals(this.environment, "DEV"),
() -> System.out.println("In DEV")
);
}
}
assumeTrue 和 assumFalse 确保给定的条件为 true 或 false,不满足条件会使得测试执行终止
assumingThat 的参数是表示条件的布尔值和对应的 Executable 接口的实现对象。只有条件满足时,Executable 对象才会被执行;当条件不满足时,测试执行并不会终止
JUnit 5 可以通过 Java 中的内部类和 @Nested 注解实现嵌套测试,从而可以更好的把相关的测试方法组织在一起。在内部类中可以使用 @BeforeEach 和@AfterEach 注解,而且嵌套的层次没有限制
@DisplayName("A stack")
class TestingAStackDemo {
Stack<Object> stack;
@Test
@DisplayName("is instantiated with new Stack()")
void isInstantiatedWithNew() {
new Stack<>();
}
@Nested
@DisplayName("when new")
class WhenNew {
@BeforeEach
void createNewStack() {
stack = new Stack<>();
}
@Test
@DisplayName("is empty")
void isEmpty() {
assertTrue(stack.isEmpty());
}
@Test
@DisplayName("throws EmptyStackException when popped")
void throwsExceptionWhenPopped() {
assertThrows(EmptyStackException.class, stack::pop);
}
@Test
@DisplayName("throws EmptyStackException when peeked")
void throwsExceptionWhenPeeked() {
assertThrows(EmptyStackException.class, stack::peek);
}
@Nested
@DisplayName("after pushing an element")
class AfterPushing {
String anElement = "an element";
@BeforeEach
void pushAnElement() {
stack.push(anElement);
}
@Test
@DisplayName("it is no longer empty")
void isNotEmpty() {
assertFalse(stack.isEmpty());
}
@Test
@DisplayName("returns the element when popped and is empty")
void returnElementWhenPopped() {
assertEquals(anElement, stack.pop());
assertTrue(stack.isEmpty());
}
@Test
@DisplayName("returns the element when peeked but remains not empty")
void returnElementWhenPeeked() {
assertEquals(anElement, stack.peek());
assertFalse(stack.isEmpty());
}
}
}
}
参数化测试是 JUnit5 很重要的一个新特性,它使得用不同的参数多次运行测试成为了可能,也为我们的单元测试带来许多便利。标注 @ParameterizedTest 注解指定这是一参数化测试类,利用 @ValueSource 等注解,指定入参,我们将可以使用不同的参数进行多次单元测试,而不需要每新增一个参数就新增一个单元测试,省去了很多冗余代码。
@ValueSource
: 为参数化测试指定入参来源,支持八大基础类以及String类型,Class类型@NullSource
: 表示为参数化测试提供一个 NULL 的入参@EnumSource
: 表示为参数化测试提供一个枚举入参@CsvFileSource
:表示读取指定 CSV 文件内容作为参数化测试入参@MethodSource
:表示读取指定方法的返回值作为参数化测试入参(注意方法返回需要是一个流)@ParameterizedTest
@ValueSource(strings = {"one", "two", "three"})
@DisplayName("参数化测试1")
public void parameterizedTest1(String string) {
System.out.println(string);
Assertions.assertTrue(StringUtils.isNotBlank(string));
}
@ParameterizedTest
@MethodSource("method") //指定方法名
@DisplayName("方法来源参数")
public void testWithExplicitLocalMethodSource(String name) {
System.out.println(name);
Assertions.assertNotNull(name);
}
static Stream<String> method() {
return Stream.of("apple", "banana");
}
未来每一个微服务在云上部署以后,我们都需要对其进行监控、追踪、审计、控制等。SpringBoot就抽取了Actuator场景,使得我们每个微服务快速引用即可获得生产级别的应用监控、审计等功能
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-actuatorartifactId>
dependency>
支持的暴露方式:
默认所有的 Endpoint 除了 shutdown 都是开启的。需要开启或者禁用某个 Endpoint。配置模式为 management.endpoint.
management:
endpoints:
enabled-by-default: true #暴露所有端点信息
web:
exposure:
include: '*' #以web方式暴露
或者禁用所有的 Endpoint 然后手动开启指定的 Endpoint ,设置相关端点属性
management:
endpoints:
enabled-by-default: false
web:
exposure:
include: '*'
endpoint:
health:
show-details: always #总是显示详细信息。可显示每个模块的状态信息
enabled: true
info:
enabled: true
beans:
enabled: true
metrics:
enabled: true
http://localhost:8080/actuator/**
常用 Endpoint:
如果应用程序是Web应用程序(Spring MVC,Spring WebFlux 或 Jersey),则可以使用以下附加端点:
健康检查端点,一般用于在云平台,平台会定时的检查应用的健康状况,就需要 Health Endpoint,可以为平台返回当前应用的一系列组件健康状况的集合
定制 Health
@Component
public class MyComHealthIndicator extends AbstractHealthIndicator {
@Override
protected void doHealthCheck(Health.Builder builder) throws Exception {
Map<String,Object> map = new HashMap<>();
if (1 == 1){
builder.up();
map.put("count",1);
map.put("ms",1000);
}else {
builder.status(Status.OUT_OF_SERVICE);
map.put("err","连接超时");
}
builder.withDetail("code",100)
.withDetails(map);
}
}
@Component
public class MyHealthIndicator implements HealthIndicator {
@Override
public Health health() {
int errorCode = check(); // perform some specific health check
if (errorCode != 0) {
return Health.down().withDetail("Error Code", errorCode).build();
}
return Health.up().build();
}
}
// 构建Health
Health build = Health.down()
.withDetail("msg", "error service")
.withDetail("code", "500")
.withException(new RuntimeException())
.build();
提供详细的、层级的、空间指标信息,这些信息可以被 pull(主动推送)或者push(被动获取)方式得到
定制Metrics:
class MyService{
Counter counter;
public MyService(MeterRegistry meterRegistry){
counter = meterRegistry.counter("myservice.method.running.counter");
}
public void hello() {
counter.increment();
}
}
//也可以使用下面的方式
@Bean
MeterBinder queueSize(Queue queue) {
return (registry) -> Gauge.builder("queueSize", queue::size).register(registry);
}
info:
appName: boot-admin
version: 2.0.1
mavenProjectName: @project.artifactId@ #使用@@可以获取maven的pom文件值
mavenProjectVersion: @project.version@
@Component
public class ExampleInfoContributor implements InfoContributor {
@Override
public void contribute(Info.Builder builder) {
builder.withDetail("ms","你好")
.withDetails(Collections.singletonMap("world","world"));
}
}
@Component
@Endpoint(id = "myService")
public class MyServiceEndpoint {
@ReadOperation
public Map getDockerInfo(){
return Collections.singletonMap("dockerInto","dockerInfo start");
}
@WriteOperation
public void stopDockerInfo(){
System.out.println("docker stopped");
}
}
https://github.com/codecentric/spring-boot-admin
创建一个 springboot server 项目,用作服务器指标监控 springboot client 客户端
<dependency>
<groupId>de.codecentricgroupId>
<artifactId>spring-boot-admin-starter-serverartifactId>
<version>2.3.1version>
dependency>
@EnableAdminServer
@SpringBootApplication
public class AdminserverApplication {
public static void main(String[] args) {
SpringApplication.run(AdminserverApplication.class, args);
}
}
server:
port: 8888
spring:
boot:
admin:
client:
url: http://localhost:8888 // 使用主机名注册
instance:
prefer-ip: true # 使用ip注册进来
application:
name: adminmanager
除了默认配置文件,Spring Boot 还可以加载一些位于项目外部的配置文件。可以通过如下 2 个参数,指定外部配置文件的路径:
spring.config.location
java -jar {JAR} --spring.config.location={外部配置文件全路径}
spring.config.additional-location
java -jar {JAR} --spring.config.additional-location={外部配置文件全路径}
--spring.config.location
不同,--spring.config.additional-location
不会使项目默认的配置文件失效,使用该命令行参数添加的外部配置文件会与项目默认的配置文件共同生效,形成互补配置,且其优先级是最高的,比所有默认配置文件的优先级都高Maven 对项目进行打包时,位于项目根目录下的配置文件是无法被打包进项目的 JAR 包的,因此位于根目录下的默认配置文件无法在 JAR 中生效, 即该项目将只加载指定的外部配置文件和项目类路径(classpath)下的默认配置文件,它们的加载优先级顺序为:
将 Spring Boot 项目打包后,然后在命令行启动命令中添加 spring.config.additional-location
参数指定外部配置文件,会导致项目根目录下的配置文件无法被加载,可以通过以下 3 种方式解决这个问题:
-Dspring.config.additional-location=D:\myConfig\my-application.yml
,指定外部配置文件--spring.config.additional-location=D:\myConfig\my-application.yml
,指定外部配置文件;spring.config.additional-location
,指定外部配置文件常用:Java属性文件、YAML文件、环境变量、命令行参数
@Controller
public class HelloController {
@Value("${MAVEN_HOME}")
private String msg;
@Value("os.name")
private String osName;
}
以下是常用的 Spring Boot 配置形式及其加载顺序(优先级由高到低):
java -jar {Jar文件名} --{参数1}={参数值1} --{参数2}={参数值2}
java -jar springbootdemo-0.0.1-SNAPSHOT.jar --server.port=8081 --server.servlet.context-path=/bcb
--server.port
:指定服务器端口号--server.servlet.context-path
:指定上下文路径(项目的访问路径)以上所有形式的配置都会被加载,当存在相同配置内容时,高优先级的配置会覆盖低优先级的配置;存在不同的配置内容时,高优先级和低优先级的配置内容取并集,共同生效,形成互补配置
根目录 config 目录下的子文件夹 > 根目录 config 目录下 > 根目录下 > classpath 目录下的 config 目录下 > classpath 目录下
指定环境优先,外部优先,后面的可以覆盖前面的同名配置项,同一位置下,Properties 文件优先级高于 YAML 文件
这些配置文件的优先级顺序,遵循以下规则:
功能:传入姓名 ,自动配置前缀和后缀
创建一个空项目,加入一个 Maven 模块,作为 Starter ,再加入一个 Springboot 模块,作为 Starter-autoconfigure
<properties>
<java.version>1.8java.version>
properties>
<dependencies>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starterartifactId>
dependency>
dependencies>
@ConfigurationProperties("fan")
public class HelloProperties {
private String prefix;
private String suffix;
}
public class HelloService {
@Autowired
HelloProperties helloProperties;
public String sayHello(String name){
return helloProperties.getPrefix() + ": " + name + "》" + helloProperties.getSuffix();
}
}
@Configuration
@EnableConfigurationProperties(HelloProperties.class)
public class HelloAutoConfiguration {
@ConditionalOnMissingBean(HelloService.class)
@Bean
public HelloService helloService(){
HelloService helloService = new HelloService();
return helloService;
}
}
# Auto Configure
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
fan.auto.HelloAutoConfiguraion // 自动配置类
<dependency>
<groupId>fangroupId>
<artifactId>fan-spring-boot-starter-autoconfigureartifactId>
<version>0.0.1-SNAPSHOTversion>
dependency>
<properties>
<project.build.sourceEncoding>UTF-8project.build.sourceEncoding>
properties>
先将 Starter-autoconfigure 执行 clean ,然后 install ,然后再将 Starter 执行 clean ,install,加载进本地仓库
<dependency>
<groupId>fangroupId>
<artifactId>fan-spring-boot-starterartifactId>
<version>1.0-SNAPSHOTversion>
dependency>
将自定义 Starter 中的 Service 注入进来,调用 Starter 的方法
@Controller
public class TestStarterController {
@Autowired
HelloService helloService;
@ResponseBody
@GetMapping("/teststart")
public String sayHello(){
String sayHello = helloService.sayHello("张三");
return sayHello;
}
}
使用 Starter-Autoconfiguration 自动配置的属性
fan:
prefix: fan
suffix: auto
将会使用我们自定义的 Config 来进行装配
@Configuration
public class MyHelloConfig {
@Bean
public HelloService helloService(){
HelloService helloService = new HelloService();
return helloService;
}
}
在 src/main/resources 下添加 4 个配置文件:
application.yml
:主配置文件application-dev.yml
:开发环境配置文件application-test.yml
:测试环境配置文件application-prod.yml
:生产环境配置文件默认配置文件 application.yml;任何时候都会加载,指定环境配置文件 application-prod.yml / application-test.yml / application-dev.yml
激活指定环境:
spring:
profiles:
active: prod
# active: test
java -jar xxx.jar --spring.profiles.active=prod --person.name=haha
java -Dspring.profiles.active=prod -jar xxx.jar
默认配置与环境配置同时生效,同名配置项,profile 配置优先
指定仅在某个 profile 下执行
@Configuration(proxyBeanMethods = false)
@Profile("prod")
public class ProductionConfiguration {
// ...
}
@Configuration(proxyBeanMethods = false)
@Profile("test")
public class ProductionConfiguration {
// ...
}
指定 profile 分组,同时加载多个 profile ,可以分别指定值,如:一个指定 name,一个指定 age ,然后同时加载进来
spring:
profiles:
active: production
group:
production[0]: prodname
production[1]: prodage
#默认配置
server:
port: 8080
#切换配置
spring:
profiles:
active: test
---
#开发环境
server:
port: 8081
spring:
config:
activate:
on-profile: dev
---
#测试环境
server:
port: 8082
spring:
config:
activate:
on-profile: test
---
#生产环境
server:
port: 8083
spring:
config:
activate:
on-profile: prod