SpringBoot学习笔记

SpringBoot学习笔记

一.简介

1.1优点

image-20200427162910483

1.2微服务

微服务:架构风格

一个应用应该是一个小型服务,可以通过http的方式进行互通。

每一个功能元素最终都是一个可独立替换和独立升级的软件单元。

image-20200515144822982

1.3HelloWorld

1
2
3
4
5
6
@SpringBootApplication			!!!
public class HelloWorld {
public static void main(String[] args) {
SpringApplication.run(HelloWorld.class,args); !!!!
}
}
1
2
3
4
5
6
7
8
9
@Controller
public class HelloController {

@ResponseBody
@RequestMapping("/hello")
public String Hello(){
return "hello";
}
}

点击运行main方法即可。非常便捷

1.4HelloWorld分析

1.pom.xml

父项目

image-20200518111743445

SpringBoot的版本仲裁中心,以后导入依赖默认不需要写版本。(Springboot没有的版本号需要导入)

2.导入的依赖

1
2
3
4
5
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<version>2.2.7.RELEASE</version>
</dependency>

spring-boot-starter-web:场景启动器, 导入了web模块正常运行所依赖的组件

SpringBoot将所有功能场景都抽取出来,做成一个个的starters(启动器) ,只要在项目里引用这些starter场景

所有相关依赖都会被导入进来。

1.5@SpringBootApplication

@SpringBootApplication说明这个类是SpringBoot的zhu配置类,运行此类的main方法启动Springboot

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan(
excludeFilters = {@Filter(
type = FilterType.CUSTOM,
classes = {TypeExcludeFilter.class}
), @Filter(
type = FilterType.CUSTOM,
classes = {AutoConfigurationExcludeFilter.class}
)}
)
  • @SpringBootConfiguration:SPringboot的配置类

​ Spring中@Configuration配置类上标注注解

  • @EnableAutoConfiguration:开启自动配置功能。

    • ```JAVA @AutoConfigurationPackage @Import({AutoConfigurationImportSelector.class}) public @interface EnableAutoConfiguration {
      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

      - **@AutoConfigurationPackage**:自动配置包

      - **@Import(Rigister.class})**:Spring底层注解@Import,给容器中导入一个组件:Rigister.class
      - AutoConfigurationPackages.Register.class:将**主配置类的所在包及下面**所有的组件扫描到Spring容器

      - **@Import({AutoConfigurationImportSelector.class})**:导入哪些组件的选择器

      - 将所有需要导入的组件以全类名的方式返回;这些组件就会被添加到容器中
      - 会给容器导入非常多的配置类(xxxAutoConfiguration),并自动配置

      ![image-20200518145300286](http://cdn.retainblog.top/blog/image-20200518145300286.png)

      > Spring boot在启动时从**类路径下的META-INF/spring.factories**中获取EnableAutoConfiguration的值,将这些值作为自动配置类导入到容器中,自动配置类就生效,帮我们进行自动配置的功能。

      自动配置类的信息:在autocinfig里面:

      ![image-20200518150217498](http://cdn.retainblog.top/blog/image-20200518150217498.png)

      ### 1.6使用spring initializer快速创建Spring Boot项目

      ![image-20200518150944764](http://cdn.retainblog.top/blog/image-20200518150944764.png)

      ![image-20200518151017933](http://cdn.retainblog.top/blog/image-20200518151017933.png)

      ![image-20200518151031557](http://cdn.retainblog.top/blog/image-20200518151031557.png)

      然后需要联网进行自动加载。

      - 特点:
      - 主程序已经生成好,只需要编辑业务逻辑
      - resources目录结构
      - static:保存的静态资源 js css images
      - templates:保存所有的模板页面(SpringBoot默认jar包使用嵌入式tomcat,默认不支持jsp);可以使用模板引擎(freemaker、thymeleaf)
      - application.properties:Springboot应用的配置文件(设置端口号等等)

      ```java
      @RestController
      public class HelloController {

      @RequestMapping("/hello")
      public String Hello(){
      return "hello";
      }
      }

@RestController=@Controller+@ResponseBody,后面的所有方法都不需要写@ResponseBody

二、SpringBoot配置

2.1配置文件

Springboot使用全局配置文件,配置文件名是固定的

  • application.properties
  • application.yml

配置文件作用:修改Springboot自动配置的默认值。

image-20200518153028296

2.2yaml语法

2.2.1基本语法

  • k:(空格)v:表示一对键值对(空格必须有)以空格缩进控制层级关系

  • 左对齐的都是同一层级

  • 属性和值也是大小写敏感

2.2.2值的写法

  • 字面量:普通的值(数字,字符串,布尔)

    • k: v:直接写
    • 字符串不用加单引号或双引号
      • “”:不会转义字符串里的特殊字符 如”“会输出换行
      • ‘’:会转义特殊字符,如”“会输出
  • 对象、Map(属性和值)

    • k: v

      1
      2
      3
      Person:
      lastName:zhangsan
      age:20

      注意缩进

  • 数组(List、Set)

    • - 值表示数组的一个元素

      1
      2
      3
      4
      pets:
      - cat
      - dog
      - pig
1
2
3
4
5
6
7
<!-- 配置文件自动映射 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
<version>2.1.6.RELEASE</version>
<optional>true</optional>
</dependency>
1
2
3
4
person:		!!小写
Name: 张三
age: 18
Job: 程序员
1
2
@ConfigurationProperties(prefix = "person")
public class Person {
出错:
image-20200518160306850

需要将其加入容器中才可以识别,添加@Component

2.2.3@Value和@ConfigurationProperties区别

@ConfigurationProperties @Value
功能 批量注入配置文件的属性 一个一个注入
松散绑定(松散语法) 支持(配置文件中last-name能映射到lastName) 不支持
SpEL 不支持 支持
JSR303数据校验 支持(@Validated校验属性的格式如@Email 不支持
复杂类型封装 支持(map,list) 不支持

如果说,我们只是在某个业务逻辑中需要获取一下配置文件中的某项值,使用@Value ; 如果说,我们专门编写了一个javaBean来和配置文件进行映射,我们就直接使用@ConfigurationProperties ; |

2.2.4@PropertySource,@ImportResource,@Bean

  • @PropertySource 加载指定的配置文件

    • @PropertySource(value={classpath:person.properties})
  • @ImportResource 导入Spring 的配置文件让配置文件里的内容生效

    • image-20200702162938174
    • 标注在主配置类上

    SpringBoot中没有Spring 的配置文件, 如果导入容器配置文件内容不会自动识别,使用@ImportResource使配置文件生效

  • @Bean 添加组件 SpringBoot推荐使用全注解的方式

1
2
3
4
5
6
7
8
9
@Configuration   //配置类
public class MyAppConfig {

@Bean
public HelloService helloService(){
System.out.println("添加组件");
return new HelloService();
}
}

2.2.5配置文件占位符

  • 随机数
1
2
${random.value},${random.int},${random.long}
${random.int(10)},${random.int[1024,65536]}
  • 获取之前的值,或者指定默认值
1
2
person.last-name=张三${random.uuid}
person.dog.name=${person.last-name:李四}_dog

2.2.6Profile多环境支持

  • 1.多profile文件

编写配置文件时可以是application-{profile}.properties/yml

  • 2.yml支持多文档块方式
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
server:
port: 8090
spring:
profiles:
active: dev
---
server:
port: 8091
spring:
profiles: dev
---
server:
port: 8092
spring:
profiles: prod
  • 3.激活指定profile

    1.使用多文件时在主配置文件指定spring-profiles-active=dev

    2.命令行

    ​ program arguments:--spring-profiles-active=dev

    3.虚拟机参数

    ​ VM options:-Dspring-profiles-active=dev

2.2.7配置文件加载位置

SpringBoot会扫面以下位置的application.properties或者application.yml作为默认配置文件

  1. -file:.config
  2. -file:./
  3. -classpath:/config/
  4. -classpath:/

优先级由高到低,高优先级的配置会覆盖低优先级的配置

互补配置:高优先级有的用高优先级,高优先级没有的用其他优先级的。

通过--Spring.config.location=G:/application.properties形成默认配置文件位置

项目打包好以后,我们可以使用命令行参数的形式,启动项目的时候来指定配置文件的新位置;指定配置文件和默 认加载的这些配置文件共同起作用形成互补配置。从而不需要重新打包

2.2.8外部配置加载顺序

Springboot也可以从以下位置加载配置,优先级从高到低

image-20200702174031306

6,7,8,9 由jar包外向jar包内寻找,优先加载带profile的,再加载不带profile的

2.2.9自动配置原理*

  1. Springboot启动时加载主配置类,开启自动配置功能@EnableAutoConfiguration

  2. @EnableAutoConfiguration作用:

    1. )利用AutoConfigurationImportSelector给容器中导入组件
    2. 可以查看selectImports()内容
    3. List configurations = getCandidateConfigurations(annotationMetadata,atributes);获取候选 的配置
    1
    2
    3
    4
    SpringFactoriesLoader . loadF actoryNames()
    扫描所有jar包类路径下META- INF/spring. factories
    把扫描到的这些文件的内容包装成properties对象
    从properties中获取到EnableAutoConfiguration.class类(类名)对应的值,然后把他们添加在容器中

    总结:将类路径下META-INF/spring.factories 里面配置的所有EnableAutoConfiguration的值加入到了容器中;

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
# Initializers
org.springframework.context.ApplicationContextInitializer=\
org.springframework.boot.autoconfigure.SharedMetadataReaderFactoryContextInitializer,\
org.springframework.boot.autoconfigure.logging.ConditionEvaluationReportLoggingListener

# Application Listeners
org.springframework.context.ApplicationListener=\
org.springframework.boot.autoconfigure.BackgroundPreinitializer

# Auto Configuration Import Listeners
org.springframework.boot.autoconfigure.AutoConfigurationImportListener=\
org.springframework.boot.autoconfigure.condition.ConditionEvaluationReportAutoConfigurationImportListener

# Auto Configuration Import Filters
org.springframework.boot.autoconfigure.AutoConfigurationImportFilter=\
org.springframework.boot.autoconfigure.condition.OnBeanCondition,\
org.springframework.boot.autoconfigure.condition.OnClassCondition,\
org.springframework.boot.autoconfigure.condition.OnWebApplicationCondition

# 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

# Failure analyzers
org.springframework.boot.diagnostics.FailureAnalyzer=\
org.springframework.boot.autoconfigure.diagnostics.analyzer.NoSuchBeanDefinitionFailureAnalyzer,\
org.springframework.boot.autoconfigure.flyway.FlywayMigrationScriptMissingFailureAnalyzer,\
org.springframework.boot.autoconfigure.jdbc.DataSourceBeanCreationFailureAnalyzer,\
org.springframework.boot.autoconfigure.jdbc.HikariDriverConfigurationFailureAnalyzer,\
org.springframework.boot.autoconfigure.r2dbc.ConnectionFactoryBeanCreationFailureAnalyzer,\
org.springframework.boot.autoconfigure.session.NonUniqueSessionRepositoryFailureAnalyzer

# Template availability providers
org.springframework.boot.autoconfigure.template.TemplateAvailabilityProvider=\
org.springframework.boot.autoconfigure.freemarker.FreeMarkerTemplateAvailabilityProvider,\
org.springframework.boot.autoconfigure.mustache.MustacheTemplateAvailabilityProvider,\
org.springframework.boot.autoconfigure.groovy.template.GroovyTemplateAvailabilityProvider,\
org.springframework.boot.autoconfigure.thymeleaf.ThymeleafTemplateAvailabilityProvider,\
org.springframework.boot.autoconfigure.web.servlet.JspTemplateAvailabilityProvider

每一个这样的xxxAutoConfiguration类都是容器中的一个组件,都加入到容器中;用他们来做自动配置;

  • 每一个自动配置类进行自动配置
  • HttpEncodingAutoConfiguration为例解释自动配置原理
1
2
3
4
5
6
7
8
9
10
@Configuration(proxyBeanMethods = false)	//表示一个配置类,可以给容器中添加组件
@EnableConfigurationProperties(ServerProperties.class) //启用ServerProperties类的ConfigurationProperties功能;将配置文件中对应的值和HttpEncodingProperties绑定起来
@ConditionalOnWebApplication(type = ConditionalOnWebApplication.Type.SERVLET)
//Spring底层的@Conditional注解 ,根据不同的条件,如果满足指定的条件,整个配置类里面的配置就会生效;判断当前应用是否是web应用,如果是,当前配置类就生效
@ConditionalOnClass(CharacterEncodingFilter.class)
//判断当前项目有没有这个类。CharacterEncodingFilter:SpringMVC中进行乱码解决的过滤器
@ConditionalOnProperty(prefix = "server.servlet.encoding", value = "enabled", matchIfMissing = true)
//判断配置文件中是否存在某个配置 server.servlet.encoding;如果不存在,判断也是成立的。即使配置文件中不配置server.servlet.encoding,也是默认生效的。
public class HttpEncodingAutoConfiguration {

  • 所有配置文件中能配置的属性都是在xxxProperties类中封装着,配置文件能配置什么就可以参照某个功能对应的这个属性类
1
2
@ConfigurationProperties(prefix = "server", ignoreUnknownFields = true) //从配置文件中获取指定的值和属性进行绑定
public class ServerProperties {
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public class HttpEncodingAutoConfiguration {
private final Encoding properties; //和Springboot的配置文件映射了

//只有一个有参构造器的情况下,参数的值就会利用@EnableConfigurationProperties(ServerProperties.class)从容器中拿;
public HttpEncodingAutoConfiguration(ServerProperties properties) {
this.properties = properties.getServlet().getEncoding();
}

@Bean
@ConditionalOnMissingBean
public CharacterEncodingFilter characterEncodingFilter() {
CharacterEncodingFilter filter = new OrderedCharacterEncodingFilter();
filter.setEncoding(this.properties.getCharset().name());
filter.setForceRequestEncoding(this.properties.shouldForce(Encoding.Type.REQUEST));
filter.setForceResponseEncoding(this.properties.shouldForce(Encoding.Type.RESPONSE));
return filter;
}
//this.properties.getCharset().name()从properties中获取编码格式
  • 精髓:
    • SpringBoot启动会加载大量的配置类
    • 看我们需要的功能有没有SpringBoot默认写好的自动配置类
    • 再看这个自动配置类中到底配置了哪些组件(如果有我们需要用的,我们就不需要再配置了)
    • 给容器中自动配置类添加组件的时候会从Properties文件中获取某些属性,我们可以在配置文件中指定这些属性的值。

xxxAutoConfiguration:自动配置类

xxxProperties:封装配置文件中的

马士兵Springboot自动配置原理

image-20200710202024814
怎么看源码

1.8分靠猜测,2分去验证

2.不要一条路走到黑(不要一直点方法)

3.通过方法类的名字猜测具体的含义

4.多看方法类上的注释

5.记录对应的流程

6.遇到疑惑标记下来

Springboot启动流程

!!!load()

!!!@import解析工作,加载了哪些类,最重要的!

image-20200710203415702
image-20200710203520013
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
public ConfigurableApplicationContext run(String... args) {
StopWatch stopWatch = new StopWatch();
stopWatch.start();
ConfigurableApplicationContext context = null;
Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList();
this.configureHeadlessProperty();
SpringApplicationRunListeners listeners = this.getRunListeners(args); //从配置文件获取监听器
listeners.starting();

Collection exceptionReporters;
try {
ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
ConfigurableEnvironment environment = this.prepareEnvironment(listeners, applicationArguments); //环境接口,
this.configureIgnoreBeanInfo(environment);
Banner printedBanner = this.printBanner(environment); //打印banner,springboot图标
context = this.createApplicationContext(); //创建上下文
exceptionReporters = this.getSpringFactoriesInstances(SpringBootExceptionReporter.class, new Class[]{ConfigurableApplicationContext.class}, context); //创建异常报告器


this.prepareContext(context, environment, listeners, applicationArguments, printedBanner);


this.refreshContext(context);
this.afterRefresh(context, applicationArguments);
stopWatch.stop();
if (this.logStartupInfo) {
(new StartupInfoLogger(this.mainApplicationClass)).logStarted(this.getApplicationLog(), stopWatch);
}

listeners.started(context);
this.callRunners(context, applicationArguments);
} catch (Throwable var10) {
this.handleRunFailure(context, var10, exceptionReporters, listeners);
throw new IllegalStateException(var10);
}

try {
listeners.running(context);
return context;
} catch (Throwable var9) {
this.handleRunFailure(context, var9, exceptionReporters, (SpringApplicationRunListeners)null);
throw new IllegalStateException(var9);
}
}
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
private void prepareContext(ConfigurableApplicationContext context, ConfigurableEnvironment environment, SpringApplicationRunListeners listeners, ApplicationArguments applicationArguments, Banner printedBanner) {
context.setEnvironment(environment);
this.postProcessApplicationContext(context);
this.applyInitializers(context);
listeners.contextPrepared(context);
if (this.logStartupInfo) {
this.logStartupInfo(context.getParent() == null);
this.logStartupProfileInfo(context);
}

ConfigurableListableBeanFactory beanFactory = context.getBeanFactory();
beanFactory.registerSingleton("springApplicationArguments", applicationArguments);
if (printedBanner != null) {
beanFactory.registerSingleton("springBootBanner", printedBanner);
}

if (beanFactory instanceof DefaultListableBeanFactory) {
((DefaultListableBeanFactory)beanFactory).setAllowBeanDefinitionOverriding(this.allowBeanDefinitionOverriding);
}

if (this.lazyInitialization) {
context.addBeanFactoryPostProcessor(new LazyInitializationBeanFactoryPostProcessor());
}

Set<Object> sources = this.getAllSources(); //source:主类SpringDataApplication.class
Assert.notEmpty(sources, "Sources must not be empty");
//加载资源,
this.load(context, sources.toArray(new Object[0]));

listeners.contextLoaded(context);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
//BeanDefinition中的load方法
private int load(Class<?> source) {
if (this.isGroovyPresent() && BeanDefinitionLoader.GroovyBeanDefinitionSource.class.isAssignableFrom(source)) {
BeanDefinitionLoader.GroovyBeanDefinitionSource loader = (BeanDefinitionLoader.GroovyBeanDefinitionSource)BeanUtils.instantiateClass(source, BeanDefinitionLoader.GroovyBeanDefinitionSource.class);
this.load(loader);
}

if (this.isComponent(source)) {
this.annotatedReader.register(new Class[]{source}); //开启注解扫描,整合spring
return 1;
} else {
return 0;
}
}
1
2
3
4
5
6
7
8
9
//AbstractApplicationContext 
protected void invokeBeanFactoryPostProcessors(ConfigurableListableBeanFactory beanFactory) {
PostProcessorRegistrationDelegate.invokeBeanFactoryPostProcessors(beanFactory, this.getBeanFactoryPostProcessors());
if (beanFactory.getTempClassLoader() == null && beanFactory.containsBean("loadTimeWeaver")) {
beanFactory.addBeanPostProcessor(new LoadTimeWeaverAwareProcessor(beanFactory));
beanFactory.setTempClassLoader(new ContextTypeMatchClassLoader(beanFactory.getBeanClassLoader()));
}

}
1
2
3
4
public abstract class AnnotationConfigUtils {
public static final String CONFIGURATION_ANNOTATION_PROCESSOR_BEAN_NAME = "org.springframework.context.annotation.internalConfigurationAnnotationProcessor";

创建的是ConfigurationClassPostProcessor.class,引入了ConfigurationClassParser 加载解析配置类的相关组件,处理注解等
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
//引入了ConfigurationClassParser实现@Import注解

private void processImports(ConfigurationClass configClass, ConfigurationClassParser.SourceClass currentSourceClass, Collection<ConfigurationClassParser.SourceClass> importCandidates, Predicate<String> exclusionFilter, boolean checkForCircularImports) {
if (!importCandidates.isEmpty()) {
if (checkForCircularImports && this.isChainedImportOnStack(configClass)) {
this.problemReporter.error(new ConfigurationClassParser.CircularImportProblem(configClass, this.importStack));
} else {
this.importStack.push(configClass);

try {
Iterator var6 = importCandidates.iterator();

while(var6.hasNext()) {
ConfigurationClassParser.SourceClass candidate = (ConfigurationClassParser.SourceClass)var6.next();
Class candidateClass;
if (candidate.isAssignable(ImportSelector.class)) {
candidateClass = candidate.loadClass();
ImportSelector selector = (ImportSelector)ParserStrategyUtils.instantiateClass(candidateClass, ImportSelector.class, this.environment, this.resourceLoader, this.registry);
Predicate<String> selectorFilter = selector.getExclusionFilter();
if (selectorFilter != null) {
exclusionFilter = exclusionFilter.or(selectorFilter);
}

if (selector instanceof DeferredImportSelector) {
this.deferredImportSelectorHandler.handle(configClass, (DeferredImportSelector)selector);
} else {
String[] importClassNames = selector.selectImports(currentSourceClass.getMetadata());
Collection<ConfigurationClassParser.SourceClass> importSourceClasses = this.asSourceClasses(importClassNames, exclusionFilter);
this.processImports(configClass, currentSourceClass, importSourceClasses, exclusionFilter, false);
}
} else if (candidate.isAssignable(ImportBeanDefinitionRegistrar.class)) {
candidateClass = candidate.loadClass();
ImportBeanDefinitionRegistrar registrar = (ImportBeanDefinitionRegistrar)ParserStrategyUtils.instantiateClass(candidateClass, ImportBeanDefinitionRegistrar.class, this.environment, this.resourceLoader, this.registry);
configClass.addImportBeanDefinitionRegistrar(registrar, currentSourceClass.getMetadata());
} else {
this.importStack.registerImport(currentSourceClass.getMetadata(), candidate.getMetadata().getClassName());
this.processConfigurationClass(candidate.asConfigClass(configClass), exclusionFilter);
}
}
} catch (BeanDefinitionStoreException var17) {
throw var17;
} catch (Throwable var18) {
throw new BeanDefinitionStoreException("Failed to process import candidates for configuration class [" + configClass.getMetadata().getClassName() + "]", var18);
} finally {
this.importStack.pop();
}
}

}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
//AutoConfigurationImportSelector中getAutoConfigurationEntry,配置@EnableAutoConfiguration注解需要的类

protected AutoConfigurationEntry getAutoConfigurationEntry(AnnotationMetadata annotationMetadata) {
if (!isEnabled(annotationMetadata)) {
return EMPTY_ENTRY;
}
AnnotationAttributes attributes = getAttributes(annotationMetadata);
List<String> configurations = getCandidateConfigurations(annotationMetadata, attributes);
configurations = removeDuplicates(configurations); //碰到@EnableAutoConfiguration,从spring.facotries中获取需要加载的类,一共124个
Set<String> exclusions = getExclusions(annotationMetadata, attributes);
checkExcludedClasses(configurations, exclusions);
configurations.removeAll(exclusions);
configurations = getConfigurationClassFilter().filter(configurations); //过滤器过滤掉不需要的类
fireAutoConfigurationImportEvents(configurations, exclusions);
return new AutoConfigurationEntry(configurations, exclusions);
}
//最后剩下29个
tomcat启动
1
2
3
4
5
6
7
8
9
10
ServletWebConfigurationContext类中
protected void onRefresh() {
super.onRefresh();

try {
this.createWebServer(); //创建服务器
} catch (Throwable var2) {
throw new ApplicationContextException("Unable to start web server", var2);
}
}

2.3.0 @Conditional与自动配置报告

  • @Conditional派生注解
    • 作用:比如是@Conditional指定的条件成立,才会给容器中添加组件,配置类里面的内容才会生效。
image-20200703114148402
  • 可以通过配置debug=true属性,来让控制台打印自动配置报告,可以知道配置了哪些类
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
============================
CONDITIONS EVALUATION REPORT
============================


Positive matches: //启用的配置类
-----------------

AopAutoConfiguration matched:
- @ConditionalOnProperty (spring.aop.auto=true) matched (OnPropertyCondition)

AopAutoConfiguration.ClassProxyingConfiguration matched:
- @ConditionalOnMissingClass did not find unwanted class 'org.aspectj.weaver.Advice' (OnClassCondition)
- @ConditionalOnProperty (spring.aop.proxy-target-class=true) matched (OnPropertyCondition)

Negative matches: //未启用的配置类
-----------------

ActiveMQAutoConfiguration:
Did not match:
- @ConditionalOnClass did not find required class 'javax.jms.ConnectionFactory' (OnClassCondition)

三、日志

3.1日志框架

image-20200703161229074

左边选一个门面,右边选一个实现

日志门面:SLF4j;

日志实现:logback;(是log4j的升级,log4j2是apache推出的,比较复杂)

SpringBoot底层是Spring框架,Spring默认使用JCL;

SpringBoot选用SLF4j和logback;

3.2 SLF4j的使用

开发的时候,日志方法的调用,不应该直接调用日志的实现类,而应该调用日志抽象层的方法;

1
2
3
4
5
6
7
8
9
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class HelloWorld {
public static void main(String[] args) {
Logger logger = LoggerFactory.getLogger(HelloWorld.class);
logger.info("Hello World");
}
}
image-20200703162612914

slf4j与logback和log4j配合使用

每一个日志的实现框架都有自己的配置文件。使用slf4j以后,配置文件还是做成日志实现框架的配置文件;

3.2.1遗留问题

不同框架使用的日志记录不同,需要统一日志框架。

image-20200703163643619

如何让系统中所有日志统一到slf4j?

  1. 将系统中其他日志框架排除出去
  2. 用中间包替换原有的日志框架
  3. 导入slf4j其他的实现

3.3Springboot日志关系

SpringBoot使用Spring-boot-starter-logging用作日志功能

image-20200703201856394
image-20200703202300674

如果引用其他框架,一定要把框架的默认日志依赖移除掉.

​ Spring框架使用的是commons-logging,在依赖界面使用<exclusions></exclusions>将其移除掉

1
2
3
4
5
6
7
8
9
10
11
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
<exclusions>
<exclusion>
<groupId>org.junit.vintage</groupId>
<artifactId>junit-vintage-engine</artifactId>
</exclusion>
</exclusions>
</dependency>
1
2
3
4
5
6
7
8
Logger logger= LoggerFactory.getLogger(getClass());
@Autowired
Person person;
@Test
void contextLoads() {
logger.debug("degug信息");
logger.warn("warn信息");
logger.trace("trace信息");

日志的级别:trace<debug<info<warn<error

默认使用info级别(root级别),可以调整日志级别,日志就只会输出比该级别高的日志。

1
2
3
4
5
6
7
8
logging.level.com.fyw=trace	#将com.fyw包下的日志调为trace级别
logging.file.path=springboot.log #设置输出日志文件名,默认在类路径下

//控制台输出格式
logging.pattern.console=%d{yyyy-MM-dd} [%thread] %-5level %logger{50} - %msg%n

//文件中输出格式
logging.pattern.file=%d{yyyy-MM-dd}=== [%thread] ===%-5level %logger{50} ===- %msg%n
  • 指定配置

给类路径下放上每个日志框架自己的配置文件即可;SpringBoot就不使用他默认配置的了

image-20200703211607704

logback.xml:直接被日志框架识别了

logback-spring.xml:日志框架不直接加载日志的配置项,由SpringBoot解析日志配置,可以使用SpringBoot的高级profile功能

1
2
3
<springProfile name="staging">
可以指定某段配置只在某个环境下生效
</springProfile>

否则会出现 no applicable action for [springProfile]

3.4切换日志框架

四、web开发

流程:

  • 创建SpringBoot应用,选中需要的模块
  • SpringBoot已经将这些配置配置好了,只需要修改少量配置即可运行起来
  • 编写业务逻辑代码

明白自动配置原理,可以弄明白SpringBoot帮我们配置了什么?能不能修改?哪些配置能修改?能不能扩展等。

4.1Springboot对静态资源的映射

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
WebMvcAutoConfiguration下的addResourceHandlers方法。
@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();
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));
}
}


//欢迎页映射
@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;
}
  • 所有/webjars/**,都去classpath:/META-INF/resources/webjars/下找资源。

    • webjars:以jar包的形式引入静态资源

    webjar

    http://localhost:8091/webjars/jquery/3.5.0/dist/jquery.js 会访问image-20200704095317903

1
2
3
4
5
6
<!--        引入jQuery-webjar-->
<dependency>
<groupId>org.webjars.npm</groupId>
<artifactId>jquery</artifactId>
<version>3.5.0</version>
</dependency>

2./**访问当前项目任何资源(静态资源文件夹)

1
2
3
4
5
"/" :当前项目根路径
classpath:/META-INF/resources/",
"classpath:/resources/",
"classpath:/static/",
"classpath:/public/

3.欢迎页映射:静态资源下的index.html被/**映射

​ localhost:8091 找index页面

4.2引入thymeleaf

1
2
3
4
5
6
修改版本thymeleaf3需要thymeleaf-layout-dialect.version2.0以上
<properties>
<java.version>1.8</java.version>
<thymeleaf.version>3.0.11.RELEASE</thymeleaf.version>
<thymeleaf-layout-dialect.version>2.1.1</thymeleaf-layout-dialect.version>
</properties>

添加依赖

1
2
3
4
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>

只要我们把HTML 页面放在classpath:/templates/ , thymeleaf就能自动渲染;

4.2.1使用thymeleaf

  1. 导入名称空间
  2. ```html

    成功!

    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

    自动映射hello,获取map的“你好”并显示



    #### 4.2.2thymeleaf语法

    ![image-20200704173410101](http://cdn.retainblog.top/blog/image-20200704173410101.png)

    ```properties
    Simple expressions:
    Variable Expressions: ${...} OGNL表达式,
    1)、获取对象属性,调用方法
    2)、使用内置的基本对象
    3)、内置的一些工具对象
    Selection Variable Expressions: *{...} 和${}在功能上是一样的 配合th:Object="${session.user}"
    Message Expressions: #{...} :获取国际化内容
    Link URL Expressions: @{...} :定义url连接 @{/order/process(execId=${execId},execType='FAST')} 不需要"?变量名"进行拼接
    Fragment Expressions: ~{...} :片段引用表达式
    Literals :字面量
    Text literals: 'one text' , 'Another one!' ,…
    Number literals: 0 , 34 , 3.0 , 12.3 ,…
    Boolean literals: true , false
    Null literal: null
    Literal tokens: one , sometext , main ,…
    Text operations: :文本操作
    String concatenation: +
    Literal substitutions: |The name is ${name}|
    Arithmetic operations: :数学运算
    Binary operators: + , - , * , / , %
    Minus sign (unary operator): -
    Boolean operations:
    Binary operators: and , or
    Boolean negation (unary operator): ! , not
    Comparisons and equality:
    Comparators: > , < , >= , <= ( gt , lt , ge , le )
    Equality operators: == , != ( eq , ne )
    Conditional operators:
    If-then: (if) ? (then)
    If-then-else: (if) ? (then) : (else)
    Default: (value) ?: (defaultvalue)
    Special tokens:
    Page 17 of 106
    No-Operation: _

1
2
3
4
5
6
7
<body>
<h1>成功!</h1>
<div th:text="${hello}"></div>
<hr/>
<div th:text="${user}" th:each="user:${users}"></div>

</body>

4.3 SpringMVC自动配置原理*

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:

  • Inclusion of ContentNegotiatingViewResolver and BeanNameViewResolver beans.

    • 自动配置了视图解析器ViewResolver,根据方法的返回值得到视图对象View,View觉得如何渲染(转发?重定向?)
    • ContentNegotiatingViewResolver组合所有视图解析器。
    • 如何定制:可以自己给容器中添加一个视图解析器;自动将其组合进来。
    image-20200705114055844

    DispatcherServlet中

  • Support for serving static resources, including support for WebJars (see below).

  • Automatic registration of Converter, GenericConverter, Formatter beans.

    • 自动注册了转换器:public String hello(User user); 传入文本数据转User对象
    • Formatter(格式化器):2020/07/05=====Date
    1
    2
    3
    protected void addFormatters(FormatterRegistry registry) {
    this.configurers.addFormatters(registry);
    }
    • 自己添加的格式化转换器,只需要添加到容器中就可以
  • Support for HttpMessageConverters (see below).

    • HttpMessageConverters :SpringMVC用来转换Http请求和响应的lUser--json
    • 是从容器中确定;获取所有的HttpMessageConverters
    • 自己添加的HttpMessageConverters ,只需要添加到容器中就可以
  • Automatic registration of MessageCodesResolver (see below). //定义错误代码生成规则

  • Static index.html support. 静态首页访问

  • Custom Favicon support.

  • Automatic use of a ConfigurableWebBindingInitializer bean (see below).

If you want to keep Spring Boot MVC features, and you just want to add additional MVC configuration (interceptors, formatters, view controllers etc.) you can add your own @Configuration class of type WebMvcConfigurerAdapter, but without @EnableWebMvc. If you wish to provide custom instances of RequestMappingHandlerMapping, RequestMappingHandlerAdapter or ExceptionHandlerExceptionResolver you can declare a WebMvcRegistrationsAdapter instance providing such components.

If you want to take complete control of Spring MVC, you can add your own @Configuration annotated with @EnableWebMvc.

4.4全面接管SpringMVC

4.4.1扩展SpringMVC

添加拦截器,格式化器,视图控制器等

  • you can add your own @Configuration class of type WebMvcConfigurerAdapter, but without @EnableWebMvc
  • 编写一个配置类,2.0以上实现implements WebMvcConfigurer,不标注@EnableWebMvc。丢弃extendsWebMvcConfigurerAdapter``
1
2
3
4
5
6
7
8
//既保留了所有的自动配置,又可以用我们自己的配置
@Configuration
public class MyMVCConfig implements WebMvcConfigurer {
@Override
public void addViewControllers(ViewControllerRegistry registry) {
registry.addViewController("/demo").setViewName("/success");
}
}

原理

  1. ​ WebMvcConfiguration是SpringMVC的自动配置类
  2. 在做其他自动配置时会导入:@ImportEnableWebMvcConfiguration.class)

4.4.2 全面接管SpringMvc

使用@EnableWebMvc会使Springboot对SpringMvc的所有自动配置失效,而自己重新配置SpringMvc

优点:如果项目比较小时用不到这么多功能可以节省空间。

但不推荐全面接管Mvc

原理:

1
2
@Import({DelegatingWebMvcConfiguration.class})
public @interface EnableWebMvc {
1
2
public class DelegatingWebMvcConfiguration extends WebMvcConfigurationSupport {
private final WebMvcConfigurerComposite configurers = new WebMvcConfigurerComposite();
1
2
3
4
5
6
7
8
@Configuration(proxyBeanMethods = false)
@ConditionalOnWebApplication(type = Type.SERVLET)
@ConditionalOnClass({ Servlet.class, DispatcherServlet.class, WebMvcConfigurer.class })
@ConditionalOnMissingBean(WebMvcConfigurationSupport.class) //没有WebMvcConfigurationSupport类时自动配置才生效
@AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE + 10)
@AutoConfigureAfter({ DispatcherServletAutoConfiguration.class, TaskExecutionAutoConfiguration.class,
ValidationAutoConfiguration.class })
public class WebMvcAutoConfiguration {

@EnableWebMvc导入的WebMvcConfigurationSupport只是Mvc最基本的功能

4.4.3如何修改SpringBoot的默认配置

模式:

1 )、SpringBoot在自动配置很多组件的时候,先看容器中有没有用户自己配置的( @Bean@Component )如果有就用用户配置的,如果没有,才自动配置;如果有些组件可以有多个( ViewResolver )将 用户配置的和自己默认的组合起来;

2)、在SpringBoot中会有非常 多的XxxConfigurer帮助我们进行扩展配置

4.5案例 RestfulCRUD

  • 要求:
    • 默认访问首页
1
2
3
4
@Override		//方法一
public void addViewControllers(ViewControllerRegistry registry) {
registry.addViewController("/").setViewName("index");
}
1
2
3
4
@RequestMapping({"","/index.html"})	//方法二
public String index(){
return "index";
}
  • 首页问题解决:未显示css样式
1
2
3
4
@Override		//因为被拦截,重写方法添加静态资源
public void addResourceHandlers(ResourceHandlerRegistry registry) {
registry.addResourceHandler("/**").addResourceLocations("classpath:/static/");
}

4.5.1国际化

  1. 编写国家化配置文件,抽取需要显示的国际化消息
image-20200705181426319
  1. springboot自动配置了国际化的组件

@ConfigurationProperties(prefix = "spring.messages") public class MessageSourceAutoConfiguration {

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
/**
* Comma-separated list of basenames (essentially a fully-qualified classpath
* location), each following the ResourceBundle convention with relaxed support for
* slash based locations. If it doesn't contain a package qualifier (such as
* "org.mypackage"), it will be resolved from the classpath root.
*/
private String basename = "messages";
//我们的配置文件可以直接放在类路径下叫messages.properties;

@Bean
public MessageSource messageSource() {
ResourceBundleMessageSource messageSource = new ResourceBundleMessageSource();
if (StringUtils.hasText(this.basename)) {
//设置国际化资源文件的基础名(去掉语言国家代码的)
messageSource.setBasenames(StringUtils.commaDelimitedListToStringArray(
StringUtils.trimAllWhitespace(this.basename)));
}
if (this.encoding != null) {
messageSource.setDefaultEncoding(this.encoding.name());
}
messageSource.setFallbackToSystemLocale(this.fallbackToSystemLocale);
messageSource.setCacheSeconds(this.cacheSeconds);
messageSource.setAlwaysUseMessageFormat(this.alwaysUseMessageFormat);
return messageSource;
}

3、获取国际化值

原理:

​ 国际化Locale(区域信息对象);LocaleResolver(获取区域信息对象);

1
2
3
4
5
6
7
8
9
10
11
12
@Bean
@ConditionalOnMissingBean
@ConditionalOnProperty(prefix = "spring.mvc", name = "locale")
public LocaleResolver localeResolver() {
if (this.mvcProperties
.getLocaleResolver() == WebMvcProperties.LocaleResolver.FIXED) {
return new FixedLocaleResolver(this.mvcProperties.getLocale());
}
AcceptHeaderLocaleResolver localeResolver = new AcceptHeaderLocaleResolver();
localeResolver.setDefaultLocale(this.mvcProperties.getLocale());
return localeResolver;
}

默认的就是根据请求头带来的区域信息获取Locale进行国际化

4.点击链接切换国际化

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
public class MyLocaleResolver implements LocaleResolver {
@Override
public Locale resolveLocale(HttpServletRequest httpServletRequest) {
String l = httpServletRequest.getParameter("l"); //非getHeader
Locale locale =Locale.getDefault();
if (!StringUtils.isEmpty(l)){
String[] split =l.split("_");
locale=new Locale(split[0],split[1]);
}
return locale;
}

@Override
public void setLocale(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Locale locale) {

}

mvcconfig:
@Bean
public LocaleResolver localeResolver(){ //方法名与bean名相同!
return new MyLocaleResolver();
}


1
2
<a class="btn btn-sm" th:href="@{/(l='zh_CN')}">中文</a>
<a class="btn btn-sm" th:href="@{/(l='en_US')}">English</a>

4.5.2登录与拦截器

开发期间模板引擎页面修改以后,要实时生效

1)、禁用模板引擎的缓存

1
2
# 禁用缓存
spring.thymeleaf.cache=false

2)、页面修改完成以后ctrl+f9:重新编译;

登陆错误消息的显示

1
<p style="color: red" th:text="${msg}" th:if="${not #strings.isEmpty(msg)}"></p>

跳转主页面

  • 添加视图映射
1
registry.addViewController("/main").setViewName("dashboard");
  • 重定向
1
2
3
4
5
6
7
8
9
10
11
@PostMapping("/user/login")
public String login(@RequestParam("username") String username,
@RequestParam("password") String password,
Map<String,Object> map){
if (!StringUtils.isEmpty(username) && "123456".equals(password)){
return "redirect:/main"; //重定向到主页面
}else {
map.put("msg","用户名密码错误");
return "login";
}
}
拦截器实现登录检查
1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class LoginHandlerInterceptor implements HandlerInterceptor {

//登陆前检查
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
Object user = request.getSession().getAttribute("loginUser");
if (user==null){
request.setAttribute("msg","请先登录!");
request.getRequestDispatcher("/").forward(request,response);
return false;
}else {
return true;
}
}

mvcconfig注册拦截器

1
2
3
4
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new LoginHandlerInterceptor()).addPathPatterns("/**").excludePathPatterns("/","/index.html","/user/login");
}

4.5.3CRUD-员工列表

实验要求:

1)、RestfulCRUD:CRUD满足Rest风格;

URI: /资源名称/资源标识 HTTP请求方式区分对资源CRUD操作

普通CRUD(uri来区分操作) RestfulCRUD
查询 getEmp emp---GET
添加 addEmp?xxx emp---POST
修改 updateEmp?id=xxx&xxx=xx emp/{id}---PUT
删除 deleteEmp?id=1 emp/{id}---DELETE

2)、实验的请求架构;

实验功能 请求URI 请求方式
查询所有员工 emps GET
查询某个员工(来到修改页面) emp/1 GET
来到添加页面 emp GET
添加员工 emp POST
来到修改页面(查出员工进行信息回显) emp/1 GET
修改员工 emp PUT
删除员工 emp/1 DELETE
1
2
3
4
5
6
7
8
9
10
11
12
13
@Controller
public class EmployeeController {

@Autowired
EmployeeDao employeeDao;

@GetMapping("/emps")
public String list(Model model){
Collection<Employee> all = employeeDao.getAll();
model.addAttribute("emps",all);
//classpath:/templates/
return "../emp/list"; //这里教程是emp/list,但会出现500在templates中找不到,所以返回上级目录
}
1
2
3
4
5
<link href="/static/asserts/css/bootstrap.min.css"  rel="stylesheet">
//使用thymeleaf和之前的路径会出现样式丢失,在前面加 /static样式就重新回来了

<!-- Custom styles for this template -->
<link href="/static/asserts/css/dashboard.css" rel="stylesheet">
  • 公共页抽取
1
2
3
4
5
6
7
8
9
10
11
12
13
14
1、抽取公共片段
<div th:fragment="copy">
&copy; 2011 The Good Thymes Virtual Grocery
</div>

2、引入公共片段
<div th:insert="~{footer :: copy}"></div>
~{templatename::selector}:模板名::选择器
~{templatename::fragmentname}:模板名::片段名

3、默认效果:
insert的公共片段在div标签中
如果使用th:insert等属性进行引入,可以不用写~{}:
行内写法可以加上:[[~{}]];[(~{})];
1
<nav class="navbar navbar-dark sticky-top bg-dark flex-md-nowrap p-0" th:fragment="topbar">

三种引入公共片段的th属性:

th:insert:将公共片段整个插入到声明引入的元素中

th:replace:将声明引入的元素替换为公共片段

th:include:将被引入的片段的内容包含进这个标签中

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
<footer th:fragment="copy">
&copy; 2011 The Good Thymes Virtual Grocery
</footer>

引入方式
<div th:insert="footer :: copy"></div>
<div th:replace="footer :: copy"></div>
<div th:include="footer :: copy"></div>

效果
<div>
<footer>
&copy; 2011 The Good Thymes Virtual Grocery
</footer>
</div>

<footer>
&copy; 2011 The Good Thymes Virtual Grocery
</footer>

<div>
&copy; 2011 The Good Thymes Virtual Grocery
</div>

4.6错误处理页面

其他客户端,响应json数据

1
2
3
4
5
6
7
{
"timestamp": 1594366266983,
"status": 404,
"error": "Not Found",
"message": "No message available",
"path": "/m"
}

原理:

参照ErrorMvcAutoConfiguration自动配置类

给容器添加了以下组件:

  • DefaultErrorAttributes
1
2
3
4
5
6
7
8
9
10
11
//帮我们在页面共享信息;
@Override
public Map<String, Object> getErrorAttributes(RequestAttributes requestAttributes,
boolean includeStackTrace) {
Map<String, Object> errorAttributes = new LinkedHashMap<String, Object>();
errorAttributes.put("timestamp", new Date());
addStatus(errorAttributes, requestAttributes);
addErrorDetails(errorAttributes, requestAttributes, includeStackTrace);
addPath(errorAttributes, requestAttributes);
return errorAttributes;
}
  • BasicErrorController 处理默认/error请求

@Controller @RequestMapping("\({server.error.path:\){error.path:/error}}") public class BasicErrorController extends AbstractErrorController {

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
@RequestMapping(produces = "text/html")//产生html类型的数据;浏览器发送的请求来到这个方法处理
public ModelAndView errorHtml(HttpServletRequest request,
HttpServletResponse response) {
HttpStatus status = getStatus(request);
Map<String, Object> model = Collections.unmodifiableMap(getErrorAttributes(
request, isIncludeStackTrace(request, MediaType.TEXT_HTML)));
response.setStatus(status.value());

//去哪个页面作为错误页面;包含页面地址和页面内容
ModelAndView modelAndView = resolveErrorView(request, response, status, model);
return (modelAndView == null ? new ModelAndView("error", model) : modelAndView);
}

@RequestMapping
@ResponseBody //产生json数据,其他客户端来到这个方法处理;
public ResponseEntity<Map<String, Object>> error(HttpServletRequest request) {
Map<String, Object> body = getErrorAttributes(request,
isIncludeStackTrace(request, MediaType.ALL));
HttpStatus status = getStatus(request);
return new ResponseEntity<Map<String, Object>>(body, status);
}
  • ErrorPageCustomizer

    1
    2
    @Value("${error.path:/error}")
    private String path = "/error"; //系统出现错误以后来到error请求进行处理;(web.xml注册的错误页面规则)
  • DefaultErrorViewResolver

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
@Override
public ModelAndView resolveErrorView(HttpServletRequest request, HttpStatus status,
Map<String, Object> model) {
ModelAndView modelAndView = resolve(String.valueOf(status), model);
if (modelAndView == null && SERIES_VIEWS.containsKey(status.series())) {
modelAndView = resolve(SERIES_VIEWS.get(status.series()), model);
}
return modelAndView;
}
private ModelAndView resolve(String viewName, Map<String, Object> model) {
//默认SpringBoot可以去找到一个页面? error/404
String errorViewName = "error/" + viewName;

//模板引擎可以解析这个页面地址就用模板引擎解析
TemplateAvailabilityProvider provider = this.templateAvailabilityProviders
.getProvider(errorViewName, this.applicationContext);
if (provider != null) {
//模板引擎可用的情况下返回到errorViewName指定的视图地址
return new ModelAndView(errorViewName, model);
}
//模板引擎不可用,就在静态资源文件夹下找errorViewName对应的页面 error/404.html
return resolveResource(errorViewName, model);
}

步骤:

​ 一但系统出现4xx或者5xx之类的错误;ErrorPageCustomizer就会生效(定制错误的响应规则);就会来到/error请求;就会被BasicErrorController处理;

​ 1)响应页面;去哪个页面是由DefaultErrorViewResolver解析得到的;

1
2
3
4
5
6
7
8
9
10
11
protected ModelAndView resolveErrorView(HttpServletRequest request,
HttpServletResponse response, HttpStatus status, Map<String, Object> model) {
//所有的ErrorViewResolver得到ModelAndView
for (ErrorViewResolver resolver : this.errorViewResolvers) {
ModelAndView modelAndView = resolver.resolveErrorView(request, status, model);
if (modelAndView != null) {
return modelAndView;
}
}
return null;
}

1)、如何定制错误的页面;

1)、有模板引擎的情况下;error/状态码; 【将错误页面命名为 错误状态码.html 放在模板引擎文件夹里面的 error文件夹下】,发生此状态码的错误就会来到 对应的页面;

​ 我们可以使用4xx和5xx作为错误页面的文件名来匹配这种类型的所有错误,精确优先(优先寻找精确的状态码.html);

​ 页面能获取的信息;

​ timestamp:时间戳

​ status:状态码

​ error:错误提示

​ exception:异常对象

​ message:异常消息

​ errors:JSR303数据校验的错误都在这里

​ 2)、没有模板引擎(模板引擎找不到这个错误页面),静态资源文件夹下找;

​ 3)、以上都没有错误页面,就是默认来到SpringBoot默认的错误提示页面;

2)、如何定制错误的json数据;

​ 1)、自定义异常处理&返回定制json数据;

1
2
3
4
5
6
7
8
9
10
11
12
13
@ControllerAdvice
public class MyExceptionHandler {

@ResponseBody
@ExceptionHandler(UserNotExistException.class)
public Map<String,Object> handleException(Exception e){
Map<String,Object> map = new HashMap<>();
map.put("code","user.notexist");
map.put("message",e.getMessage());
return map;
}
}
//没有自适应效果...

​ 2)、转发到/error进行自适应响应效果处理

1
2
3
4
5
6
7
8
9
10
11
12
13
14
@ExceptionHandler(UserNotExistException.class)
public String handleException(Exception e, HttpServletRequest request){
Map<String,Object> map = new HashMap<>();
//传入我们自己的错误状态码 4xx 5xx,否则就不会进入定制错误页面的解析流程
/**
* Integer statusCode = (Integer) request
.getAttribute("javax.servlet.error.status_code");
*/
request.setAttribute("javax.servlet.error.status_code",500);
map.put("code","user.notexist");
map.put("message",e.getMessage());
//转发到/error
return "forward:/error";
}

3)、将我们的定制数据携带出去;

出现错误以后,会来到/error请求,会被BasicErrorController处理,响应出去可以获取的数据是由getErrorAttributes得到的(是AbstractErrorController(ErrorController)规定的方法);

​ 1、完全来编写一个ErrorController的实现类【或者是编写AbstractErrorController的子类】,放在容器中;

​ 2、页面上能用的数据,或者是json返回能用的数据都是通过errorAttributes.getErrorAttributes得到;

​ 容器中DefaultErrorAttributes.getErrorAttributes();默认进行数据处理的;

自定义ErrorAttributes

1
2
3
4
5
6
7
8
9
10
11
//给容器中加入我们自己定义的ErrorAttributes
@Component
public class MyErrorAttributes extends DefaultErrorAttributes {

@Override
public Map<String, Object> getErrorAttributes(RequestAttributes requestAttributes, boolean includeStackTrace) {
Map<String, Object> map = super.getErrorAttributes(requestAttributes, includeStackTrace);
map.put("company","atguigu");
return map;
}
}

最终的效果:响应是自适应的,可以通过定制ErrorAttributes改变需要返回的内容,

4.7嵌入式Servlet

Springboot默认使用tomcat作为嵌入式Servlet容器

  • 修改server有关的配置ServerProperties【也是EmbeddedServletContainerCustomizer】);

    1
    2
    3
    4
    5
    6
    7
    8
    9
    server.port=8081
    server.context-path=/crud

    server.tomcat.uri-encoding=UTF-8

    //通用的Servlet容器设置
    server.xxx
    //Tomcat的设置
    server.tomcat.xxx

2、编写一个EmbeddedServletContainerCustomizer:嵌入式的Servlet容器的定制器;来修改Servlet容器的配置

1
2
3
4
5
6
7
8
9
10
11
12
13
//2.x版本使用的是WebServerFactoryCustomizer,但接口方法没找到

@Bean //一定要将这个定制器加入到容器中
public EmbeddedServletContainerCustomizer embeddedServletContainerCustomizer(){
return new EmbeddedServletContainerCustomizer() {

//定制嵌入式的Servlet容器相关的规则
@Override
public void customize(ConfigurableEmbeddedServletContainer container) {
container.setPort(8083);
}
};
}

4.8注册Servlet三大组件(Servlet,Filter,Listener)

  • ServletRegistrationBean
1
2
3
4
5
6
7
8
9
10
11
public class MyServlet extends HttpServlet {
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
resp.getWriter().write("Hello MyServlet");
}

@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
doPost(req, resp);
}
}
1
2
3
4
5
6
7
8
9
@Configuration
public class MyServerConfig {
//注册三大组件
@Bean
public ServletRegistrationBean myServlet() {
ServletRegistrationBean servletRegistrationBean = new ServletRegistrationBean(new MyServlet(),"/myServlet");
return servletRegistrationBean;
}
}
  • FilterRegistrationBean
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public class MyFilter implements Filter {

@Override
public void init(FilterConfig filterConfig) throws ServletException {

}

@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
System.out.println("MyFilter process");
filterChain.doFilter(servletRequest,servletResponse);
}

@Override
public void destroy() {

}
}
1
2
3
4
5
6
7
@Bean
public FilterRegistrationBean myFilter(){
FilterRegistrationBean<Filter> registrationBean = new FilterRegistrationBean<>();
registrationBean.setFilter(new MyFilter());
registrationBean.setUrlPatterns(Arrays.asList("/hello","/myServlet"));
return registrationBean;
}
  • ServletListenerRegistrationBean
1
2
3
4
5
6
7
8
9
10
11
12
public class MyListener implements ServletContextListener{
@Override
public void contextInitialized(ServletContextEvent sce) {
System.out.println("ServletContextEvent 启动");
}

@Override
public void contextDestroyed(ServletContextEvent sce) {
System.out.println("ServletContextEvent 关闭");

}
}
1
2
3
4
5
@Bean
public ServletListenerRegistrationBean myListener(){
ServletListenerRegistrationBean<MyListener> registrationBean = new ServletListenerRegistrationBean<MyListener>(new MyListener());
return registrationBean;
}

SpringBoot帮我们自动SpringMVC的时候,自动的注册SpringMVC的前端控制器;DIspatcherServlet;

DispatcherServletAutoConfiguration中:

1
2
3
4
5
6
7
8
9
10
11
12
13
@Bean(name = DEFAULT_DISPATCHER_SERVLET_REGISTRATION_BEAN_NAME)
@ConditionalOnBean(value = DispatcherServlet.class, name = DEFAULT_DISPATCHER_SERVLET_BEAN_NAME)
public DispatcherServletRegistrationBean dispatcherServletRegistration(DispatcherServlet dispatcherServlet,
WebMvcProperties webMvcProperties, ObjectProvider<MultipartConfigElement> multipartConfig) {
DispatcherServletRegistrationBean registration = new DispatcherServletRegistrationBean(dispatcherServlet,
webMvcProperties.getServlet().getPath());
//默认拦截“/” 所有请求,包括静态资源,但是不拦截jsp请求。 /*会拦截jsp请求
//可以通过spring.mvc.servlet.path来修改SpringMVC前端控制器默认拦截的请求路径
registration.setName(DEFAULT_DISPATCHER_SERVLET_BEAN_NAME);
registration.setLoadOnStartup(webMvcProperties.getServlet().getLoadOnStartup());
multipartConfig.ifAvailable(registration::setMultipartConfig);
return registration;
}

4.9替换其他嵌入式servlet容器

Jetty(长连接)

Undertow(不支持jsp)

  • tomcat
1
2
3
4
5
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
引入web模块默认就是使用嵌入式的Tomcat作为Servlet容器;
</dependency>
  • Jetty
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<!-- 引入web模块 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<exclusions>
<exclusion>
<artifactId>spring-boot-starter-tomcat</artifactId>
<groupId>org.springframework.boot</groupId>
</exclusion>
</exclusions>
</dependency>

<!--引入其他的Servlet容器-->
<dependency>
<artifactId>spring-boot-starter-jetty</artifactId>
<groupId>org.springframework.boot</groupId>
</dependency>
  • Undertow
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<!-- 引入web模块 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<exclusions>
<exclusion>
<artifactId>spring-boot-starter-tomcat</artifactId>
<groupId>org.springframework.boot</groupId>
</exclusion>
</exclusions>
</dependency>

<!--引入其他的Servlet容器-->
<dependency>
<artifactId>spring-boot-starter-undertow</artifactId>
<groupId>org.springframework.boot</groupId>
</dependency>

4)、嵌入式Servlet容器自动配置原理;

EmbeddedServletContainerAutoConfiguration:嵌入式的Servlet容器自动配置?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
@AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE)
@Configuration
@ConditionalOnWebApplication
@Import(BeanPostProcessorsRegistrar.class)
//导入BeanPostProcessorsRegistrar:Spring注解版;给容器中导入一些组件
//导入了EmbeddedServletContainerCustomizerBeanPostProcessor:
//后置处理器:bean初始化前后(创建完对象,还没赋值赋值)执行初始化工作
public class EmbeddedServletContainerAutoConfiguration {

@Configuration
@ConditionalOnClass({ Servlet.class, Tomcat.class })//判断当前是否引入了Tomcat依赖;
@ConditionalOnMissingBean(value = EmbeddedServletContainerFactory.class, search = SearchStrategy.CURRENT)//判断当前容器没有用户自己定义EmbeddedServletContainerFactory:嵌入式的Servlet容器工厂;作用:创建嵌入式的Servlet容器
public static class EmbeddedTomcat {

@Bean
public TomcatEmbeddedServletContainerFactory tomcatEmbeddedServletContainerFactory() {
return new TomcatEmbeddedServletContainerFactory();
}

}

/**
* Nested configuration if Jetty is being used.
*/
@Configuration
@ConditionalOnClass({ Servlet.class, Server.class, Loader.class,
WebAppContext.class })
@ConditionalOnMissingBean(value = EmbeddedServletContainerFactory.class, search = SearchStrategy.CURRENT)
public static class EmbeddedJetty {

@Bean
public JettyEmbeddedServletContainerFactory jettyEmbeddedServletContainerFactory() {
return new JettyEmbeddedServletContainerFactory();
}

}

/**
* Nested configuration if Undertow is being used.
*/
@Configuration
@ConditionalOnClass({ Servlet.class, Undertow.class, SslClientAuthMode.class })
@ConditionalOnMissingBean(value = EmbeddedServletContainerFactory.class, search = SearchStrategy.CURRENT)
public static class EmbeddedUndertow {

@Bean
public UndertowEmbeddedServletContainerFactory undertowEmbeddedServletContainerFactory() {
return new UndertowEmbeddedServletContainerFactory();
}

}

1)、EmbeddedServletContainerFactory(嵌入式Servlet容器工厂)

1
2
3
4
5
6
7
public interface EmbeddedServletContainerFactory {

//获取嵌入式的Servlet容器
EmbeddedServletContainer getEmbeddedServletContainer(
ServletContextInitializer... initializers);

}

2)、EmbeddedServletContainer:(嵌入式的Servlet容器)

3)、以TomcatServletWebServerFactory为例

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
    public WebServer getWebServer(ServletContextInitializer... initializers) {
if (this.disableMBeanRegistry) {
Registry.disableRegistry();
}

Tomcat tomcat = new Tomcat(); //创建一个tomcat
File baseDir = this.baseDirectory != null ? this.baseDirectory : this.createTempDir("tomcat");
tomcat.setBaseDir(baseDir.getAbsolutePath());
Connector connector = new Connector(this.protocol);
connector.setThrowOnFailure(true);
tomcat.getService().addConnector(connector);
this.customizeConnector(connector);
tomcat.setConnector(connector);
tomcat.getHost().setAutoDeploy(false);
this.configureEngine(tomcat.getEngine());
Iterator var5 = this.additionalTomcatConnectors.iterator();

while(var5.hasNext()) {
Connector additionalConnector = (Connector)var5.next();
tomcat.getService().addConnector(additionalConnector);
}

this.prepareContext(tomcat.getHost(), initializers);
return this.getTomcatWebServer(tomcat); //将配置好的tomcat传过去,并且启动tomcat
}
}

4)、我们对嵌入式容器的配置修改是怎么生效?

1
ServerProperties、EmbeddedServletContainerCustomizer

EmbeddedServletContainerCustomizer:定制器帮我们修改了Servlet容器的配置?

怎么修改的原理?

5)、容器中导入了EmbeddedServletContainerCustomizerBeanPostProcessor

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
//初始化之前
@Override
public Object postProcessBeforeInitialization(Object bean, String beanName)
throws BeansException {
//如果当前初始化的是一个ConfigurableEmbeddedServletContainer类型的组件
if (bean instanceof ConfigurableEmbeddedServletContainer) {
//
postProcessBeforeInitialization((ConfigurableEmbeddedServletContainer) bean);
}
return bean;
}

private void postProcessBeforeInitialization(
ConfigurableEmbeddedServletContainer bean) {
//获取所有的定制器,调用每一个定制器的customize方法来给Servlet容器进行属性赋值;
for (EmbeddedServletContainerCustomizer customizer : getCustomizers()) {
customizer.customize(bean);
}
}

private Collection<EmbeddedServletContainerCustomizer> getCustomizers() {
if (this.customizers == null) {
// Look up does not include the parent context
this.customizers = new ArrayList<EmbeddedServletContainerCustomizer>(
this.beanFactory
//从容器中获取所有这葛类型的组件:EmbeddedServletContainerCustomizer
//定制Servlet容器,给容器中可以添加一个EmbeddedServletContainerCustomizer类型的组件
.getBeansOfType(EmbeddedServletContainerCustomizer.class,
false, false)
.values());
Collections.sort(this.customizers, AnnotationAwareOrderComparator.INSTANCE);
this.customizers = Collections.unmodifiableList(this.customizers);
}
return this.customizers;
}

ServerProperties也是定制器

步骤:

1)、SpringBoot根据导入的依赖情况,给容器中添加相应的EmbeddedServletContainerFactory【TomcatEmbeddedServletContainerFactory】

2)、容器中某个组件要创建对象就会惊动后置处理器;EmbeddedServletContainerCustomizerBeanPostProcessor;

只要是嵌入式的Servlet容器工厂,后置处理器就工作;

3)、后置处理器,从容器中获取所有的EmbeddedServletContainerCustomizer,调用定制器的定制方法

###5)、嵌入式Servlet容器启动原理;

什么时候创建嵌入式的Servlet容器工厂?什么时候获取嵌入式的Servlet容器并启动Tomcat;

获取嵌入式的Servlet容器工厂:

1)、SpringBoot应用启动运行run方法

2)、refreshContext(context);SpringBoot刷新IOC容器【创建IOC容器对象,并初始化容器,创建容器中的每一个组件】;如果是web应用创建AnnotationConfigEmbeddedWebApplicationContext,否则:AnnotationConfigApplicationContext

3)、refresh(context);刷新刚才创建好的ioc容器;

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
public void refresh() throws BeansException, IllegalStateException {
synchronized (this.startupShutdownMonitor) {
// Prepare this context for refreshing.
prepareRefresh();

// Tell the subclass to refresh the internal bean factory.
ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();

// Prepare the bean factory for use in this context.
prepareBeanFactory(beanFactory);

try {
// Allows post-processing of the bean factory in context subclasses.
postProcessBeanFactory(beanFactory);

// Invoke factory processors registered as beans in the context.
invokeBeanFactoryPostProcessors(beanFactory);

// Register bean processors that intercept bean creation.
registerBeanPostProcessors(beanFactory);

// Initialize message source for this context.
initMessageSource();

// Initialize event multicaster for this context.
initApplicationEventMulticaster();

// Initialize other special beans in specific context subclasses.
onRefresh();

// Check for listener beans and register them.
registerListeners();

// Instantiate all remaining (non-lazy-init) singletons.
finishBeanFactoryInitialization(beanFactory);

// Last step: publish corresponding event.
finishRefresh();
}

catch (BeansException ex) {
if (logger.isWarnEnabled()) {
logger.warn("Exception encountered during context initialization - " +
"cancelling refresh attempt: " + ex);
}

// Destroy already created singletons to avoid dangling resources.
destroyBeans();

// Reset 'active' flag.
cancelRefresh(ex);

// Propagate exception to caller.
throw ex;
}

finally {
// Reset common introspection caches in Spring's core, since we
// might not ever need metadata for singleton beans anymore...
resetCommonCaches();
}
}
}

4)、 onRefresh(); web的ioc容器重写了onRefresh方法

5)、webioc容器会创建嵌入式的Servlet容器;createEmbeddedServletContainer();

6)、获取嵌入式的Servlet容器工厂:

EmbeddedServletContainerFactory containerFactory = getEmbeddedServletContainerFactory();

​ 从ioc容器中获取EmbeddedServletContainerFactory 组件;TomcatEmbeddedServletContainerFactory创建对象,后置处理器一看是这个对象,就获取所有的定制器来先定制Servlet容器的相关配置;

7)、使用容器工厂获取嵌入式的Servlet容器:this.embeddedServletContainer = containerFactory .getEmbeddedServletContainer(getSelfInitializer());

8)、嵌入式的Servlet容器创建对象并启动Servlet容器;

先启动嵌入式的Servlet容器,再将ioc容器中剩下没有创建出的对象获取出来;

==IOC容器启动创建嵌入式的Servlet容器==

数据操作

JDBC连接

1
2
3
4
5
6
7
8
9
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
1
2
3
4
5
6
spring:
datasource:
username: root
password: qwer
url: jdbc:mysql://39.99.187.18:3306/Test
driver-class-name: com.mysql.cj.jdbc.Driver

使用的数据源:

1
2
class com.zaxxer.hikari.HikariDataSource
HikariProxyConnection@1454499111 wrapping com.mysql.cj.jdbc.ConnectionImpl@68c34db2

数据源的相关配置都在DataSourceProperties类中

数据源的自动配置原理:

2.x使用HikariCP-3.4.5.jar作为数据源

  • 自定义数据源
1
2
3
4
5
6
7
8
9
10
11
@Configuration(proxyBeanMethods = false)
@ConditionalOnMissingBean(DataSource.class)
@ConditionalOnProperty(name = "spring.datasource.type")
static class Generic {

@Bean
DataSource dataSource(DataSourceProperties properties) {
return properties.initializeDataSourceBuilder().build();
}

}

4、DataSourceInitializer:ApplicationListener

​ 作用:

​ 1)、runSchemaScripts();运行建表语句;

​ 2)、runDataScripts();运行插入数据的sql语句;

默认只需要将文件命名为:

1
2
3
4
5
6
schema-*.sql、data-*.sql
默认规则:schema.sql,schema-all.sql;
可以使用
schema:
- classpath:department.sql
指定位置

5、操作数据库:自动配置了JdbcTemplate操作数据库

1
2
3
4
5
6
7
8
spring:
datasource:
username: root
password: qwer
url: jdbc:mysql://39.99.187.18:3306/Test
driver-class-name: com.mysql.cj.jdbc.Driver
schema:
- classpath*:department.sql #设置自动运行建表语句

整合Druid数据源

1
2
3
4
5
6
7
<!-- https://mvnrepository.com/artifact/com.alibaba/druid -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.1.22</version>
</dependency>

1
2
3
4
5
6
7
spring:
datasource:
username: root
password: 'qwer'
url: jdbc:mysql://39.99.187.18:3306/Test
driver-class-name: com.mysql.cj.jdbc.Driver
type: com.alibaba.druid.pool.DruidDataSource #

配置类

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
package com.fyw.demo.config;

import com.alibaba.druid.pool.DruidDataSource;
import com.alibaba.druid.support.http.StatViewServlet;
import com.alibaba.druid.support.http.WebStatFilter;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.boot.web.servlet.ServletListenerRegistrationBean;
import org.springframework.boot.web.servlet.ServletRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import javax.sql.DataSource;
import java.util.Arrays;
import java.util.EventListener;
import java.util.HashMap;
import java.util.Map;

@Configuration
public class DruidConfig {

@ConfigurationProperties(prefix = "spring.datasource")
@Bean
public DataSource druid(){
return new DruidDataSource();
}

//配置Druid的监控
//1.配置一个管理后台的Servlet
@Bean
public ServletRegistrationBean statViewServlet(){
ServletRegistrationBean bean = new ServletRegistrationBean(new StatViewServlet(),"/druid/*");
Map<String,String> initParams =new HashMap<>();
bean.setInitParameters(initParams);
initParams.put("loginUsername","admin");
initParams.put("loginPassword","123456");
initParams.put("deny","127.0.0.1");
return bean;
}

//配置web监控的filter
@Bean
public FilterRegistrationBean webStatFilter(){
FilterRegistrationBean bean = new FilterRegistrationBean();
bean.setFilter(new WebStatFilter());

Map<String,String> initParams =new HashMap<>();
initParams.put("exclusions","*.js,*.css,/druid/*");
bean.setInitParameters(initParams);

bean.setUrlPatterns(Arrays.asList("/*"));
return bean;
}


}
image-20200718110718195

整合Mybatis

1
2
3
4
5
6
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>2.1.3</version>
</dependency>
//查看了版本号中没有mybatis版本号,需要自己导入。此starter是mybatis适配springboot开发的
image-20200719103912341
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
spring:
datasource:
username: root
password: 'qwer'
url: jdbc:mysql://39.99.187.18:3306/mybatis
driver-class-name: com.mysql.cj.jdbc.Driver
type: com.alibaba.druid.pool.DruidDataSource
#配置监控统计拦截的filters,解决监控界面sql无法统计,wall用于防火墙
filters: stat,wall,log4j
maxPoolpreparedStatementPerConnectionSize: 20
useGlobalDataSourceStat: true
connectionProperties: druid.stat.mergeSql=true;druid.stat.slowSqlMillis=500

schema:
- classpath:department.sql #注意 - 后面有空格,classpath:后面没有空格
initialization-mode: always #开启后才会自动初始化表!

导入lombok自动生成实体

1
2
3
4
5
6
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.12</version>
<scope>provided</scope>
</dependency>
1
2
3
4
5
6
7
8
9
10
11
@Getter
@Setter
@ToString
public class Employee {

private Integer id;
private String lastName;
private String email;
private Integer gender;
private Integer dId;
}

Mybatis注解版

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
@Mapper
@Component
public interface DepartmentMapper {

@Select("Select * from department where id=#{id}")
Department getDeptById(Integer id);

@Delete("Delete from department where id=#{id}")
int deleteDeptById(Integer id);

@Update("Update department set departmentName=#{departmentName} where id=#{id}")
int updateDept(Department department);

@Options(useGeneratedKeys = true,keyProperty = "id") //Options可以显示自增的值
@Insert("Insert into department(departmentName) values(#{departmentName})")
int insertIntoDept(Department department);
}
  • 但是遇到Bean名与数据库字段不对应时。

开启自定义配置,开启驼峰命名

1
2
3
4
5
6
7
8
9
10
11
12
13
14
@org.springframework.context.annotation.Configuration
public class MyBatisConfig {

@Bean
public ConfigurationCustomizer configurationCustomizer(){
return new ConfigurationCustomizer(){

@Override
public void customize(Configuration configuration) {
configuration.setMapUnderscoreToCamelCase(true); //开启驼峰命名
}
};
}
}
  • 当Mapper比较多的时候,在主类上添加@MapperScan("com.fyw.demo.mapper"),自动将包内文件扫描为Mapper而不需要使用@Mapper注解。

Myabtis配置文件版

1
2
3
4
public interface EmployeeMapper {
Department getEmpById(Integer id);
int insertIntoEmp(Employee employee);
}
  • EmployeeMapper
1
2
3
4
5
6
7
8
9
10
11
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.fyw.demo.mapper.EmployeeMapper">
<select id="getEmpById" resultType="com.fyw.demo.entities.Employee">
select * from employee where id=#{id}
</select>
<insert id="insertIntoEmp">
insert into employee(lastName,email,gender,d_id) values (#{lastName},#{email},#{gender},#{dId})
</insert>
</mapper>
1
2
3
4
5
6
7
8
9
10
@GetMapping("/emp/{id}")
public Employee getEmpById(@PathVariable("id") Integer id){
return employeeMapper.getEmpById(id);
}

@GetMapping("/emp")
public Employee insertIntoEmp(Employee employee){
employeeMapper.insertIntoEmp(employee);
return employee;
}
1
2
3
4
mybatis:
typeAliasesPackage: com.example.springboot.mybatisxml.entity
mapperLocations: classpath:mybatis/mapper/*.xml
config-location: classpath:mybatis/mybatis-config.xml

整合JPA

  • 编写一个实体类,和数据表进行映射

//使用JPA注解配置映射关系 @Entity //告诉JPA这是一个实体类(和数据表映射的类) @Table(name = "tbl_user") //@Table来指定和哪个数据表对应;如果省略默认表名就是user; public class User {

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
@Entity	//告诉jpa这是一个实体类
@Table(name = "tbl_user") //和tbl_user表对应,默认小写类名user
public class User{

//使用JPA注解配置映射关系
@Entity //告诉JPA这是一个实体类(和数据表映射的类)
@Table(name = "tbl_user") //@Table来指定和哪个数据表对应;如果省略默认表名就是user;
public class User {

@Id //这是一个主键
@GeneratedValue(strategy = GenerationType.IDENTITY)//自增主键
private Integer id;

@Column(name = "last_name",length = 50) //这是和数据表对应的一个列
private String lastName;
@Column //省略默认列名就是属性名
private String email;
}
  • 编写一个Dao接口
1
2
3
//继承JpaRepository来完成对数据库的操作
public interface UserRepository extends JpaRepository<User,Integer> {
}
  • 基本配置
1
2
3
4
5
6
7
spring:  
jpa:
hibernate:
# 更新或者创建数据表结构
ddl-auto: update
# 控制台显示SQL
show-sql: true

原理解析

启动流程

1.创建SpringApplication对象

自定义Starters*

1
2
3
4
5
6
7
8
9
10
11
12
13
@Configuration  //指定这个类是一个配置类
@ConditionalOnXXX //在指定条件成立的情况下自动配置类生效
@AutoConfigureAfter //指定自动配置类的顺序
@Bean //给容器中添加组件

@ConfigurationPropertie结合相关xxxProperties类来绑定相关的配置
@EnableConfigurationProperties //让xxxProperties生效加入到容器中

自动配置类要能加载
将需要启动就加载的自动配置类,配置在META-INF/spring.factories
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
org.springframework.boot.autoconfigure.admin.SpringApplicationAdminJmxAutoConfiguration,\
org.springframework.boot.autoconfigure.aop.AopAutoConfiguration,\

模式

  • 启动器只用来做==依赖导入==;

  • 专门来写一个==自动配置模块==;

  • 启动器依赖自动配置;别人只需要==引入启动器==(starter)

  • mybatis-spring-boot-starter;自定义启动器名-spring-boot-starter

1
2
3
4
5
6
7
8
9
10
11
12
13
14
  <!-- Starter的pom--> 

<groupId>org.mystarter</groupId>
<artifactId>my-spring-boot-starter</artifactId>
<version>1.0-SNAPSHOT</version>
<!-- 启动器-->
<dependencies>
<!-- 引入自动配置-->
<dependency>
<groupId>com.mystarter</groupId>
<artifactId>my-spring-boot-starter-autoconfigurer</artifactId>
<version>0.0.1-SNAPSHOT</version>
</dependency>
</dependencies>
image-20200722125703544
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
HelloProperties

@ConfigurationProperties(prefix = "mystarter.hello")
public class HelloProperties {

private String prefix;
private String suffix;

public String getPrefix() {
return prefix;
}

public void setPrefix(String prefix) {
this.prefix = prefix;
}

public String getSuffix() {
return suffix;
}

public void setSuffix(String suffix) {
this.suffix = suffix;
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
HelloService

public class HelloService {

public HelloProperties getHelloProperties() {
return helloProperties;
}

HelloProperties helloProperties;

public String sayHelloMyStarter(String name){
return helloProperties.getPrefix() +"-"+name+helloProperties.getSuffix();
}

public void setHelloProperties(HelloProperties helloProperties) {
this.helloProperties = helloProperties;
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
HelloServiceAutoConfiguration

@Configuration
@ConditionalOnWebApplication //web应用才生效
@EnableConfigurationProperties(HelloProperties.class)
public class HelloServiceAutoConfiguration {

@Autowired
HelloProperties helloProperties;

@Bean
public HelloService helloService(){
HelloService helloService = new HelloService();
helloService.setHelloProperties(helloProperties);
return helloService;
}
}

AutoConfigurer的pom

1
2
3
4
5
6
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
</dependencies>

Starter

1
2
3
4
5
6
7
8
9
<!--    启动器-->
<dependencies>
<!-- 引入自动配置-->
<dependency>
<groupId>com.mystarter</groupId>
<artifactId>my-spring-boot-starter-autoconfigurer</artifactId>
<version>0.0.1-SNAPSHOT</version>
</dependency>
</dependencies>

然后依次将AutoConfigurer、starter install进maven仓库

在其他项目导入starter依赖

1
2
3
4
5
6
<!--        引入自定义的starter-->
<dependency>
<groupId>org.mystarter</groupId>
<artifactId>my-spring-boot-starter</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>

HelloController

1
2
3
4
5
6
7
8
9
10
@RestController
public class HelloControllerStarter {
@Autowired
HelloService helloService; //starter包中的helloservice

@GetMapping("/hello2")
public String hello(){
return helloService.sayHelloMyStarter("张三");
}
}

编写配置

1
2
mystarter.hello.prefix=mystarter
mystarter.hello.suffix=hello world

缓存抽象

重要接口

image-20200723153814201
1
2
3
4
mybatis.configuration.map-underscore-to-camel-case=true
设置驼峰命名规则,dId对应数据库d_id

但是设置后启动报错,UnsatisfiedDependencyException: Error creating bean with name 'employeeController': Unsatisfied dependency expressed through field 'employeeService'; neste

开启缓存

  1. 开启基于注解的缓存@EnableCaching
  2. 标注缓存注解
1
2
@Cacheable()
public Employee getEmp(Integer id){
image-20200723164033929

SpEL表达式

image-20200723164225199image-20200723170503688

缓存工作原理

  • 自动配置类:org/springframework/boot/autoconfigure/cache/CacheAutoConfiguration.java
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
@Import({ CacheConfigurationImportSelector.class, 	CacheManagerEntityManagerFactoryDependsOnPostProcessor.class })  //导入了缓存选择器CacheConfigurationImportSelector
//进入CacheConfigurationImportSelector,打断点查看
static class CacheConfigurationImportSelector implements ImportSelector {

@Override
public String[] selectImports(AnnotationMetadata importingClassMetadata) {
CacheType[] types = CacheType.values();
String[] imports = new String[types.length];
for (int i = 0; i < types.length; i++) {
imports[i] = CacheConfigurations.getConfigurationClass(types[i]);
}
return imports; //断点
}

}

//缓存配置类
0 = "org.springframework.boot.autoconfigure.cache.GenericCacheConfiguration"
//JSR107JCache配置
1 = "org.springframework.boot.autoconfigure.cache.JCacheCacheConfiguration"
2 = "org.springframework.boot.autoconfigure.cache.EhCacheCacheConfiguration"
3 = "org.springframework.boot.autoconfigure.cache.HazelcastCacheConfiguration"
4 = "org.springframework.boot.autoconfigure.cache.InfinispanCacheConfiguration"
5 = "org.springframework.boot.autoconfigure.cache.CouchbaseCacheConfiguration"
6 = "org.springframework.boot.autoconfigure.cache.RedisCacheConfiguration"
7 = "org.springframework.boot.autoconfigure.cache.CaffeineCacheConfiguration"
8 = "org.springframework.boot.autoconfigure.cache.SimpleCacheConfiguration"
9 = "org.springframework.boot.autoconfigure.cache.NoOpCacheConfiguration"

哪个生效?
通过debug=true查看 发现只有SimpleCacheConfiguration生效
SimpleCacheConfiguration matched:
- Cache org.springframework.boot.autoconfigure.cache.SimpleCacheConfiguration automatic cache type (CacheCondition)
- @ConditionalOnMissingBean (types: org.springframework.cache.CacheManager; SearchStrategy: all) did not find any beans (OnBeanCondition)
  • SimpleCacheConfiguration
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
@Configuration(proxyBeanMethods = false)
@ConditionalOnMissingBean(CacheManager.class)
@Conditional(CacheCondition.class)
class SimpleCacheConfiguration {

@Bean
ConcurrentMapCacheManager cacheManager(CacheProperties cacheProperties,
CacheManagerCustomizers cacheManagerCustomizers) {
ConcurrentMapCacheManager cacheManager = new ConcurrentMapCacheManager();//缓存管理器,进入查看
List<String> cacheNames = cacheProperties.getCacheNames();
if (!cacheNames.isEmpty()) {
cacheManager.setCacheNames(cacheNames);
}
return cacheManagerCustomizers.customize(cacheManager);
}
}

//作用:可以获取和创建ConcurrentMapCache类型的缓存组件,将数据保存在ConcurrentMap中
public class ConcurrentMapCacheManager implements CacheManager, BeanClassLoaderAware {
@Override
@Nullable
public Cache getCache(String name) { //根据名字获得缓存
Cache cache = this.cacheMap.get(name);
if (cache == null && this.dynamic) {
synchronized (this.cacheMap) {
cache = this.cacheMap.get(name);
if (cache == null) {
cache = createConcurrentMapCache(name);
this.cacheMap.put(name, cache);
}
}
}
return cache;
}
}

运行流程

@Cacheable

  1. 方法运行之前,先调用getCache(String name)按照cacheNames指定的名字==查询缓存==,第一次获取如果==没有缓存组件会自动创建出来==(emp缓存)
  2. 去Cache中查找缓存的==内容==,使用一个key,默认就是方法的参数(key是按照某种策略生成出来的:默认使用SimplekeyGenerator生成key)
  3. 没有查到缓存==内容==就调用目标方法(get方法)
  4. 将目标方法返回结果调用==put方法==,key是生成的key,value是返回结果,==放入缓存中==。

key生成策略

没有参数:key=new SimpleKey();

一个参数:key = 参数的值;

多个参数:key=new SimpleKey(Params);

核心

  • 使用CacheManager(ConcurrentCacheManager)按照名字得到Cache(ConcurrentMapCache)组件
  • key是使用keyGenerator生成的,默认为SimpleKeyGenerator

@Cacheable其他属性

1
2
3
4
5
6
7
@Cacheable(cacheNames = {"emp"},key = "#root.methodName"+'['+"#id"+']')
public Employee getEmp(Integer id){
System.out.println("查询"+id+"号员工");
Employee empById = employeeMapper.getEmpById(id);
return empById;
}
//使用字符串拼接生成key

使用自定义的keyGenerator

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
@Configuration
public class MyCacheConfig {

@Bean("myKeyGenerator")
public KeyGenerator keyGenerator(){
return new KeyGenerator() {
@Override
public Object generate(Object target, Method method, Object... params) {
return method.getName()+"["+ Arrays.asList(params).toString() +"]";
}
};
}
}

//然后指定注解参数
@Cacheable(cacheNames = {"emp"},keyGenerator = "myKeyGenerator")
//第一个参数值大于1才缓存
@Cacheable(cacheNames = {"emp"},condition = "#a0>1")

@CachePut

既调用方法==修改数据库的值,同时又更新缓存==,从而更新数据后再查询也不用从数据库中查询

运行时机:先调用方法,再将目标方法的结果放入缓存

1
@CachePut(value=“emp”,key=“#result.id”)  //使用返回结果的id

@CacheEvict

缓存清除

1
2
3
@CacheEvict(value="emp",key="#id")	//删除id缓存
@CacheEvict(value="emp",allEntries=true) //删除所有emp缓存
@CacheEvict(value="emp",beforeInvocation=true,key="#id") //在方法执行之前清除缓存,避免方法内数据库更新数据后出现异常缓存清除失败。

@Caching 与@CacheConfig

@Caching:组合复杂的cache规则

只要又cacheput那么方法一定会执行

1
2
3
4
5
6
7
8
9
10
11
12
13
14
@Caching(
cacheable = {
@Cacheable(value = "emp",key = "#id") //按照id查询
},
put = {
@CachePut(value = "emp",key = "#result.id"), //按照id查询
@CachePut(value = "emp",key = "#result.email") //按照邮箱查询
}
)
public Employee getEmp(Integer id){
System.out.println("查询"+id+"号员工");
Employee empById = employeeMapper.getEmpById(id);
return empById;
}

@CacheConfig:抽取缓存的公共配置

1
2
3
@Service
@CacheConfig(cacheNames = "emp") //后面方法中@Cacheable等注解就不需要指定value="emp"了
public class EmployeeService {

本博客所有文章除特别声明外,均采用 CC BY-SA 4.0 协议 ,转载请注明出处!