目录
文章目录
目录SpringBoot(v2.3.2.RELEASE)场景启动器@SpringBootApplication注解@SpringBootConfiguration@EnableAutoConfiguration
SpringBoot全局配置文件yml文件读取yml文件@ConfigurationProperties和@Value的区别
PropertySouce和ImportResouce注解的区别PropertySourceImportResource(标注在一个配置类上)
配置文件占位符多ProFile文件properties文件yml文件使用命令行参数使用虚拟机参数
配置文件的加载顺序外部配置加载顺序SpringBoot访问静态资源Thymeleaf抽取公共代码片段引入公共代码片段三种引入公共代码片段的方式
添加视图映射和拦截器错误处理机制ErrorMvcAutoConfiguration
配置外部Servlet容器全局配置文件
注册Servlet三大组件ServletFilterListenermyFiltermyListener
配置其他web容器
数据访问SpringBoot JDBC自动配置原理SpringBoot整合Spring Data JPApom.xmlapplication.yml实体类dao
SpringBoot整合Mybatispom.xmlapplication.yml启动类
Druid(主从复制)pom.xmlapplication.ymlDruidConfig.javaMyDruidConfig.javaDynamicDataSourceConfig.javaDynamicDataSource.javaDynamicDataSourceHolder.javaDynamicDataSourceInterceptor.java
SpringBoot启动配置原理SpringBoot启动类SpringApplication构造函数WebApplicationType.deduceFromClasspath()getSpringFactoriesInstances()loadFactoryNames()loadSpringFactories()ApplicationContextInitializer类ApplicationListener类deduceMainApplicationClass()SpringApplicationRunListener类listeners.starting()ConfigurableEnvironment prepareEnvironment()listeners.environmentPrepared()
printBanner()createApplicationContext()prepareContext()listeners.contextPrepared()listeners.contextLoaded()
listeners.started()callRunnerslisteners.running()
SpringBoot缓存@Cacheable注解自动配置类CacheAutoConfiguration运行流程@CachePut注解@CacheEvict注解Caching注解@CacheConfig注解Redispom.xml配置redis主机自定义redis序列化对象的规则实体类实现Serializable接口RedisCacheConfiguration
SpringBoot(v2.3.2.RELEASE)
场景启动器
spring-boot-start
<parent>
<groupId>org.springframework.boot
</groupId>
<artifactId>spring-boot-starter-parent
</artifactId>
<version>2.3.0.RELEASE
</version>
</parent>
<dependency>
<groupId>org.springframework.boot
</groupId>
<artifactId>spring-boot-starter-web
</artifactId>
</dependency>
@SpringBootApplication注解
@SpringBootConfiguration
@Configuration
@EnableAutoConfiguration
@AutoConfigurationPackage
@Import(AutoConfigurationPackages.Registrar.class)(Spring底层注解 给容器导入一个组件)
将主配置类(@SpringBootApplication标注的类)所在的包及其下所有的子包里面所有的组件扫描到容器中,或者使用@SpringBootApplication(scanBasePackages = {“com.example”})指定扫描的包,也可以使用注解@ComponentScan(value = “com.example”)指定扫描的包
@Import({AutoConfigurationImportSelector.class})
AutoConfigurationImportSelector 将需要导入的组件以全类名的方式返回并添加到容器中,即给容器中导入很多的自动配置类
protected List
<String> getCandidateConfigurations(AnnotationMetadata metadata
, AnnotationAttributes attributes
) {
List
<String> configurations
= SpringFactoriesLoader
.loadFactoryNames(this.getSpringFactoriesLoaderFactoryClass(), this.getBeanClassLoader());
Assert
.notEmpty(configurations
, "No auto configuration classes found in META-INF/spring.factories. If you are using a custom packaging, make sure that file is correct.");
return configurations
;
}
SpringBoot在启动时从类路径下spring-boot-autoconfigure-2.3.0.RELEASE.jar/META-INF/spring.factories找到EnabledAutoConfiguration指定的值并将这些值作为配置类导入到容器中
SpringBoot全局配置文件
Application.propertiesApplication.yml
yml文件
将空格玩到极致的语法 所有的冒号后面都有一个空格
person:
name: jack
age: 21
list:
- hello
- world
maps:
k1: v1
k2: 12
#等价于properties文件
person.name=jack
person.age=21
person.list=hello,world
person.maps.k1=v1
person.maps.k2=12
读取yml文件
package com
.example
.entry
;
import org
.springframework
.boot
.context
.properties
.ConfigurationProperties
;
import org
.springframework
.stereotype
.Component
;
import java
.util
.Arrays
;
import java
.util
.Map
;
@Component
@ConfigurationProperties(prefix
= "person")
public class Person {
private String name
;
private Integer age
;
private String
[] list
;
private Map
<String,Object> maps
;
public Map
<String, Object> getMaps() {
return maps
;
}
public void setMaps(Map
<String, Object> maps
) {
this.maps
= maps
;
}
public String
getName() {
return name
;
}
public void setName(String name
) {
this.name
= name
;
}
public Integer
getAge() {
return age
;
}
public void setAge(Integer age
) {
this.age
= age
;
}
public String
[] getList() {
return list
;
}
public void setList(String
[] list
) {
this.list
= list
;
}
@Override
public String
toString() {
return "Person{" +
"name='" + name
+ '\'' +
", age=" + age
+
", list=" + Arrays
.toString(list
) +
", maps=" + maps
+
'}';
}
}
@ConfigurationProperties和@Value的区别
@ConfigurationProperties默读取全局配置文件
相同点都是可以从配置文件中读取值并注入到字段中
@ConfigurationProperties批量注入配置文件中的数据,而@Value则是一次一次注入
前者支持松散绑定语法(下划线转驼峰),而后者不支持
前者不支持SPEL,后者支持
前者支持数据校验,后者不支持
前者支持复杂数据类型的封装,后者不支持
PropertySouce和ImportResouce注解的区别
PropertySource
@PropertySouce(value
= {"classpath:xxx.properties"})
ImportResource(标注在一个配置类上)
@ImportResource(locations
= {"classpath:bean.xml"})
SpringBoot不能自动识别自定义的配置文件,需要手动导入
SpringBoot不推荐用此方式向Spring容器中添加组件,可以自定义一个配置类代替配置文件,使用@Bean注解方式向容器中加入组件
配置文件占位符
person.name=jack${person.hello:hello} #不存在的属性直接看成字符串 可以赋予默认值
person.age=${random.int} #随机数
person.list=hello,world
person.maps.k1=v1
person.maps.k2=12_${person.name} #配置文件存在的属性则替换
多ProFile文件
properties文件
在主配置文件中激活环境
spring.Profiles.active = dev
application-dev.properties
server.port = 8082
yml文件
使用文档块的形式
server:
port: 8080
spring:
profiles:
active: dev
---
server:
port: 8081
spring:
profiles: dev
---
server:
port: 8082
spring:
profiles: prod
使用命令行参数
--spring
.profiles
.active
=dev
使用虚拟机参数
-Dspring
.profiles
.active
=dev
配置文件的加载顺序
优先级为:项目根路径下的config中的配置文件 > 项目根路径下配置文件 > 类路径下config中的配置文件 > 类路径下配置文件
/config/xxx.properties
/xxx.properties
classpath:/config/xxx.properties
classpaht:/xxx.properties
SpringBoot会全部加载这四个位置的配置文件,高优先级的配置文件会覆盖优先级低的相同配置文件
#等号之间不要加空格
#在项目打包时可以加载指定路径的配置文件
--spring.config.location=xxx.properties
#等号之间不要加空格
java -jar hello-0.0.1-SNAPSHOT.jar --spring.config.location=E:/application.properties
server
.servlet
.context
-path
=/test
外部配置加载顺序
命令行
#运行jar文件时指定端口号 运行路径,等号之间不要加空格 多个配置空额隔开
java -jar hello-0.0.1-SNAPSHOT.jar --server.port=8848 --server.servlet.context-path=/test
优先加载带profile的文件,再加载不带profile的文件从jar包外向内加载
SpringBoot访问静态资源
classpath:/META-INF/resources/
classpath:/resources/
classpath:/public/
classpath:/static/
/:
#配置文件 自定义静态资源的路径
spring.resources.static-locations=classpath:/hello,classpath:/test
Thymeleaf
<html lang="en" xmlns:th="http://www.thymeleaf.org">
抽取公共代码片段
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<body>
<div th:fragment="copy">
© 2011 The Good Thymes Virtual Grocery
</div>
</body>
</html>
引入公共代码片段
<body>
...
<div th:insert="footer :: copy"></div>
</body>
~{templatename::fragmentname}
~{模板名::代码片段名}
~{templatename::selector}
~{模板名::选择器}
三种引入公共代码片段的方式
th:insertth:replaceth:include
<footer th:fragment="copy">
© 2011 The Good Thymes Virtual Grocery
</footer>
<body>
...
<div th:insert="footer :: copy"></div>
<div th:replace="footer :: copy"></div>
<div th:include="footer :: copy"></div>
</body>
<body>
...
<div>
<footer>
© 2011 The Good Thymes Virtual Grocery
</footer>
.
</div>
<footer>
© 2011 The Good Thymes Virtual Grocery
</footer>
.
<div>
© 2011 The Good Thymes Virtual Grocery
</div>
</body
添加视图映射和拦截器
package com
.example
.config
;
import com
.example
.component
.LoginHandleInterceptor
;
import org
.springframework
.context
.annotation
.Configuration
;
import org
.springframework
.web
.servlet
.config
.annotation
.InterceptorRegistry
;
import org
.springframework
.web
.servlet
.config
.annotation
.ViewControllerRegistry
;
import org
.springframework
.web
.servlet
.config
.annotation
.WebMvcConfigurer
;
@Configuration
public class MyMVCConfiguration implements WebMvcConfigurer {
@Override
public void addViewControllers(ViewControllerRegistry registry
) {
registry
.addViewController("/").setViewName("login");
@Override
public void addInterceptors(InterceptorRegistry registry
) {
registry
.addInterceptor(new LoginHandleInterceptor()).addPathPatterns("/**").excludePathPatterns("/", "/login");
}
}
package com
.example
.component
;
import org
.springframework
.web
.servlet
.HandlerInterceptor
;
import javax
.servlet
.http
.HttpServletRequest
;
import javax
.servlet
.http
.HttpServletResponse
;
public class LoginHandleInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request
, HttpServletResponse response
, Object handler
) throws Exception
{
Object LoginSession
= request
.getSession().getAttribute("LoginSession");
if(LoginSession
== null
){
response
.sendRedirect("/");
return false;
}
else return true;
}
}
错误处理机制
ErrorMvcAutoConfiguration
ErrorPageCustomizer
class ErrorProperties
@Value("${error.path:/error}")
private String path
= "/error";
BasicErrorController
@Controller
@RequestMapping({"${server.error.path:${error.path:/error}}"})
class BasicErrorController{
@RequestMapping(produces
= MediaType
.TEXT_HTML_VALUE
)
public ModelAndView
errorHtml(HttpServletRequest request
, HttpServletResponse response
) {
HttpStatus status
= getStatus(request
);
Map
<String, Object> model
= Collections
.unmodifiableMap(getErrorAttributes(request
, getErrorAttributeOptions(request
, MediaType
.TEXT_HTML
)));
response
.setStatus(status
.value());
ModelAndView modelAndView
= resolveErrorView(request
, response
, status
, model
);
return (modelAndView
!= null
) ? modelAndView
: new ModelAndView("error", model
);
}
@RequestMapping
public ResponseEntity
<Map
<String, Object>> error(HttpServletRequest request
) {
HttpStatus status
= getStatus(request
);
if (status
== HttpStatus
.NO_CONTENT
) {
return new ResponseEntity<>(status
);
}
Map
<String, Object> body
= getErrorAttributes(request
, getErrorAttributeOptions(request
, MediaType
.ALL
));
return new ResponseEntity<>(body
, status
);
}
}
protected ModelAndView
resolveErrorView(HttpServletRequest request
, HttpServletResponse response
, HttpStatus status
,Map
<String, Object> model
) {
for (ErrorViewResolver resolver
: this.errorViewResolvers
) {
ModelAndView modelAndView
= resolver
.resolveErrorView(request
, status
, model
);
if (modelAndView
!= null
) {
return modelAndView
;
}
}
return null
;
}
@FunctionalInterface
public interface ErrorViewResolver {
ModelAndView
resolveErrorView(HttpServletRequest request
, HttpStatus status
, Map
<String, Object> model
);
}
DefaultErrorViewResolver
public class DefaultErrorViewResolver implements ErrorViewResolver, Ordered
{
public ModelAndView
resolveErrorView(HttpServletRequest request
, HttpStatus status
, Map
<String, Object> model
) {
ModelAndView modelAndView
= this.resolve(String
.valueOf(status
.value()), model
);
if (modelAndView
== null
&& SERIES_VIEWS
.containsKey(status
.series())) {
modelAndView
= this.resolve((String
)SERIES_VIEWS
.get(status
.series()), model
);
}
return modelAndView
;
}
private ModelAndView
resolve(String viewName
, Map
<String, Object> model
) {
String errorViewName
= "error/" + viewName
;
TemplateAvailabilityProvider provider
= this.templateAvailabilityProviders
.getProvider(errorViewName
, this.applicationContext
);
return provider
!= null
? new ModelAndView(errorViewName
, model
) : this.resolveResource(errorViewName
, model
);
}
private ModelAndView
resolveResource(String viewName
, Map
<String, Object> model
) {
String
[] var3
= this.resourceProperties
.getStaticLocations();
int var4
= var3
.length
;
for(int var5
= 0; var5
< var4
; ++var5
) {
String location
= var3
[var5
];
try {
Resource resource
= this.applicationContext
.getResource(location
);
resource
= resource
.createRelative(viewName
+ ".html");
if (resource
.exists()) {
return new ModelAndView(new DefaultErrorViewResolver.HtmlResourceView(resource
), model
);
}
} catch (Exception var8
) {
}
}
return null
;
}
}
DefaultErrorAttributes
public class DefaultErrorAttributes implements ErrorAttributes {
@Deprecated
public Map
<String, Object> getErrorAttributes(ServerRequest request
, boolean includeStackTrace
) {
Map
<String, Object> errorAttributes
= new LinkedHashMap();
errorAttributes
.put("timestamp", new Date());
errorAttributes
.put("path", request
.path());
Throwable error
= this.getError(request
);
MergedAnnotation
<ResponseStatus> responseStatusAnnotation
= MergedAnnotations
.from(error
.getClass(), SearchStrategy
.TYPE_HIERARCHY
).get(ResponseStatus
.class);
HttpStatus errorStatus
= this.determineHttpStatus(error
, responseStatusAnnotation
);
errorAttributes
.put("status", errorStatus
.value());
errorAttributes
.put("error", errorStatus
.getReasonPhrase());
errorAttributes
.put("message", this.determineMessage(error
, responseStatusAnnotation
));
errorAttributes
.put("requestId", request
.exchange().getRequest().getId());
this.handleException(errorAttributes
, this.determineException(error
), includeStackTrace
);
return errorAttributes
;
}
}
有模板引擎的情况下会找error文件夹下的状态码.html
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>Title
</title>
</head>
<body>
<h1>[[${status}]]
</h1>
<h1>[[${timestamp}]]
</h1>
<h1>[[${path}]]
</h1>
<h1>[[${error}]]
</h1>
<h1>[[${message}]]
</h1>
<h1>[[${requestId}]]
</h1>
</body>
</html>
没有模板引擎的情况下会从静态文件夹下找error/状态码.html
配置外部Servlet容器
全局配置文件
server.port=8888
@ConfigurationProperties(prefix
= "server",ignoreUnknownFields
= true)
class ServerProperties
注册Servlet三大组件
Servlet
ServletRegistrationBean
Filter
FilterRegistrationBean
Listener
ServletListenerRegistrationBean
package com
.example
.config
;
import com
.example
.component
.MyFilter
;
import com
.example
.component
.MyListener
;
import com
.example
.component
.MyServlet
;
import org
.springframework
.boot
.web
.server
.ConfigurableWebServerFactory
;
import org
.springframework
.boot
.web
.server
.WebServerFactoryCustomizer
;
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 java
.util
.Collections
;
@Configuration
public class MyServletConfig {
@Bean
public WebServerFactoryCustomizer
<ConfigurableWebServerFactory> webServerFactoryCustomizer(){
return new WebServerFactoryCustomizer<ConfigurableWebServerFactory>() {
@Override
public void customize(ConfigurableWebServerFactory factory
) {
factory
.setPort(8888);
}
};
}
@Bean
public ServletRegistrationBean
<MyServlet> servletRegistrationBean() {
return new ServletRegistrationBean<>(new MyServlet(), "/myServlet/*");
}
@Bean
public FilterRegistrationBean
<MyFilter> filterFilterRegistrationBean() {
FilterRegistrationBean
<MyFilter> filterRegistrationBean
= new FilterRegistrationBean<>();
filterRegistrationBean
.setFilter(new MyFilter());
filterRegistrationBean
.setUrlPatterns(Collections
.singletonList("/myServlet"));
return filterRegistrationBean
;
}
@Bean
public ServletListenerRegistrationBean
<MyListener> servletListenerRegistrationBean() {
return new ServletListenerRegistrationBean<>(new MyListener());
}
}
myFilter
package com
.example
.component
;
import javax
.servlet
.FilterChain
;
import javax
.servlet
.ServletException
;
import javax
.servlet
.http
.HttpFilter
;
import javax
.servlet
.http
.HttpServletRequest
;
import javax
.servlet
.http
.HttpServletResponse
;
import java
.io
.IOException
;
public class MyFilter extends HttpFilter {
@Override
protected void doFilter(HttpServletRequest request
, HttpServletResponse response
, FilterChain chain
) throws IOException
, ServletException
{
System
.out
.println("Filter had run!");
chain
.doFilter(request
, response
);
}
}
myListener
package com
.example
.component
;
import javax
.servlet
.ServletContextEvent
;
import javax
.servlet
.ServletContextListener
;
public class MyListener implements ServletContextListener {
@Override
public void contextInitialized(ServletContextEvent sce
) {
System
.out
.println("the project is running");
}
@Override
public void contextDestroyed(ServletContextEvent sce
) {
System
.out
.println("the project is destroy");
}
}
配置其他web容器
<dependency>
<groupId>org.springframework.boot
</groupId>
<artifactId>spring-boot-starter-web
</artifactId>
<exclusions>
<exclusion>
<groupId>org.springframework.boot
</groupId>
<artifactId>spring-boot-starter-tomcat
</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.springframework.boot
</groupId>
<artifactId>spring-boot-starter-jetty
</artifactId>
</dependency>
数据访问
SpringBoot JDBC自动配置原理
org.springframework.boot.autoconfigure.jdbc
DataSourceConfiguration
配置各种数据源,也可以自定义数据源
在配置文件中指定spring.datasouce.type=xxx 指定自定义数据源
DataSourceAutoConfiguration
@Import({DataSourcePoolMetadataProvidersConfiguration
.class, DataSourceInitializationConfiguration
.class})
public class DataSourceAutoConfiguration {}
@Import({DataSourceInitializerInvoker
.class, DataSourceInitializationConfiguration
.Registrar
.class})
class DataSourceInitializationConfiguration {DataSourceInitializationConfiguration() {}
class DataSourceInitializerInvoker implements ApplicationListener<DataSourceSchemaCreatedEvent>, InitializingBean
{
private DataSourceInitializer
getDataSourceInitializer()
}
class DataSourceInitializer {
private List
<Resource> getScripts(String propertyName
, List
<String> resources
, String fallback
) {
if (resources
!= null
) {
return this.getResources(propertyName
, resources
, true);
} else {
String platform
= this.properties
.getPlatform();
List
<String> fallbackResources
= new ArrayList();
fallbackResources
.add("classpath*:" + fallback
+ "-" + platform
+ ".sql");
fallbackResources
.add("classpath*:" + fallback
+ ".sql");
return this.getResources(propertyName
, fallbackResources
, false);
}
}
}
自定义脚本文件
spring:
datasource:
url: jdbc
:mysql
://localhost
:3306/test
?useUnicode=true
&serverTimezone=UTC
username: root
password: 123456
driver-class-name: com.mysql.cj.jdbc.Driver
initialization-mode: always
schema:
- classpath
:student_info.sql
SpringBoot整合Spring Data JPA
JPA (java persistence api)
pom.xml
<parent>
<groupId>org.springframework.boot
</groupId>
<artifactId>spring-boot-starter-parent
</artifactId>
<version>2.3.0.RELEASE
</version>
</parent>
<dependencies>
<dependency>
<groupId>org.springframework.boot
</groupId>
<artifactId>spring-boot-starter-web
</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok
</groupId>
<artifactId>lombok
</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot
</groupId>
<artifactId>spring-boot-starter-data-jpa
</artifactId>
</dependency>
<dependency>
<groupId>mysql
</groupId>
<artifactId>mysql-connector-java
</artifactId>
<version>8.0.19
</version>
</dependency>
</dependencies>
application.yml
spring:
datasource:
url: jdbc
:mysql
://localhost
:3306/test
?useSSL=false
&serverTimezone=UTC
username: root
password: 123456
driver-class-name: com.mysql.cj.jdbc.Driver
jpa:
show-sql: true
properties:
hibernate: true
实体类
package cn
.qingweico
.entity
;
import lombok
.Data
;
import javax
.persistence
.Entity
;
import javax
.persistence
.GeneratedValue
;
import javax
.persistence
.GenerationType
;
import javax
.persistence
.Id
;
@Data
@Entity
@Table(name
= "student")
public class Student {
@Id
@GeneratedValue(strategy
= GenerationType
.IDENTITY
)
@Column(name
= "id")
private Integer id
;
private String name
;
private String number
;
}
dao
package cn
.qingweico
.dao
;
import cn
.qingweico
.entity
.Student
;
import org
.springframework
.data
.jpa
.repository
.JpaRepository
;
public interface StudentDao extends JpaRepository<Student,Integer> {
}
SpringBoot整合Mybatis
pom.xml
<parent>
<groupId>org.springframework.boot
</groupId>
<artifactId>spring-boot-starter-parent
</artifactId>
<version>2.3.0.RELEASE
</version>
</parent>
<dependencies>
<dependency>
<groupId>org.springframework.boot
</groupId>
<artifactId>spring-boot-starter-web
</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok
</groupId>
<artifactId>lombok
</artifactId>
</dependency>
<dependency>
<groupId>org.mybatis.spring.boot
</groupId>
<artifactId>mybatis-spring-boot-starter
</artifactId>
<version>1.3.2
</version>
</dependency>
<dependency>
<groupId>mysql
</groupId>
<artifactId>mysql-connector-java
</artifactId>
<version>8.0.19
</version>
</dependency>
</dependencies>
application.yml
spring:
datasource:
url: jdbc
:mysql
://localhost
:3306/test
?useUnicode=true
&serverTimezone=UTC
username: root
password: 123456
driver-class-name: com.mysql.cj.jdbc.Driver
mybatis:
mapper-locations: classpath
:/mappers/*.xml
config-locations: classpath: /mybatis
-config.xml
type-aliases-package: cn.qingweico.entity
server:
port: 8848
启动类
@MapperScan("cn.qingweico.dao")
Druid(主从复制)
pom.xml
<dependency>
<groupId>com.alibaba
</groupId>
<artifactId>druid-spring-boot-starter
</artifactId>
<version>1.1.10
</version>
</dependency>
<dependency>
<groupId>com.alibaba
</groupId>
<artifactId>druid
</artifactId>
<version>1.1.10
</version>
</dependency>
application.yml
spring:
datasource:
masterUrl: jdbc
:mysql
://127.0.0.1
:3306/tmall
?useUnicode=true
&serverTimezone=UTC
slaveUrl: jdbc
:mysql
://xxx
:3306/tmall
?useUnicode=true
&serverTimezone=UTC
username: root
password: 123456
driver-class-name: com.mysql.cj.jdbc.Driver
initialization-mode: always
initialSize: 5
minIdle: 5
maxActive: 20
maxWait: 60000
timeBetweenEvictionRunsMillis: 60000
minEvictableIdleTimeMillis: 300000
validationQuery: SELECT 1 FROM DUAL
testWhileIdle: true
testOnBorrow: false
testOnReturn: false
poolPreparedStatements: true
filters: stat
,wall
,log4j2
maxPoolPreparedStatementPerConnectionSize: 20
useGlobalDataSourceStat: true
connectionProperties: druid.stat.mergeSql=true;druid.stat.slowSqlMillis=500
type: com.alibaba.druid.pool.DruidDataSource
DruidConfig.java
package cn
.qingweico
.config
;
import lombok
.Data
;
import lombok
.NoArgsConstructor
;
import org
.springframework
.boot
.context
.properties
.ConfigurationProperties
;
import org
.springframework
.stereotype
.Component
;
@Component
@ConfigurationProperties(prefix
= "spring.datasource")
@NoArgsConstructor
@Data
public class DruidConfig {
private String masterUrl
;
private String slaveUrl
;
private String driverClassName
;
private String username
;
private String password
;
private int initialSize
;
private int minIdle
;
private int maxActive
;
private int maxWait
;
private int timeBetweenEvictionRunsMillis
;
private int minEvictableIdleTimeMillis
;
private String validationQuery
;
private boolean testWhileIdle
;
private boolean testOnBorrow
;
private boolean testOnReturn
;
private boolean poolPreparedStatements
;
private String filters
;
private int maxPoolPreparedStatementPerConnectionSize
;
private boolean useGlobalDataSourceStat
;
private String connectionProperties
;
}
MyDruidConfig.java
package cn
.qingweico
.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
.web
.servlet
.FilterRegistrationBean
;
import org
.springframework
.boot
.web
.servlet
.ServletRegistrationBean
;
import org
.springframework
.context
.annotation
.Bean
;
import org
.springframework
.context
.annotation
.Configuration
;
import org
.springframework
.context
.annotation
.Import
;
import org
.springframework
.context
.annotation
.Primary
;
import org
.springframework
.transaction
.annotation
.EnableTransactionManagement
;
import javax
.sql
.DataSource
;
import java
.sql
.SQLException
;
import java
.util
.Collections
;
import java
.util
.HashMap
;
import java
.util
.Map
;
@Configuration
@EnableTransactionManagement
public class MyDruidConfig {
DruidConfig druidConfig
;
@Autowired
public void setDruidConfig(DruidConfig druidConfig
) {
this.druidConfig
= druidConfig
;
}
@Bean(name
= "masterDataSource")
@Primary
public DataSource
masterDataSource() throws SQLException
{
DruidDataSource druidDataSource
= new DruidDataSource();
druidDataSource
.setUrl(druidConfig
.getMasterUrl());
return getDataSource(druidDataSource
);
}
@Bean(name
= "slaveDataSource")
public DataSource
slaveDataSource() throws SQLException
{
DruidDataSource druidDataSource
= new DruidDataSource();
druidDataSource
.setUrl(druidConfig
.getSlaveUrl());
return getDataSource(druidDataSource
);
}
private DataSource
getDataSource(DruidDataSource druidDataSource
) throws SQLException
{
druidDataSource
.setDriverClassName(druidConfig
.getDriverClassName());
druidDataSource
.setUsername(druidConfig
.getUsername());
druidDataSource
.setPassword(druidConfig
.getPassword());
druidDataSource
.setInitialSize(druidConfig
.getInitialSize());
druidDataSource
.setMinIdle(druidConfig
.getMinIdle());
druidDataSource
.setMaxActive(druidConfig
.getMaxActive());
druidDataSource
.setMaxWait(druidConfig
.getMaxWait());
druidDataSource
.setTimeBetweenEvictionRunsMillis(druidConfig
.getTimeBetweenEvictionRunsMillis());
druidDataSource
.setMinEvictableIdleTimeMillis(druidConfig
.getMinEvictableIdleTimeMillis());
druidDataSource
.setValidationQuery(druidConfig
.getValidationQuery());
druidDataSource
.setTestWhileIdle(druidConfig
.isTestWhileIdle());
druidDataSource
.setTestOnBorrow(druidConfig
.isTestOnBorrow());
druidDataSource
.setTestOnReturn(druidConfig
.isTestOnReturn());
druidDataSource
.setPoolPreparedStatements(druidConfig
.isPoolPreparedStatements());
druidDataSource
.setFilters(druidConfig
.getFilters()); druidDataSource
.setMaxPoolPreparedStatementPerConnectionSize(druidConfig
.getMaxPoolPreparedStatementPerConnectionSize());
druidDataSource
.setUseGlobalDataSourceStat(druidConfig
.isUseGlobalDataSourceStat());
druidDataSource
.setConnectionProperties(druidConfig
.getConnectionProperties());
return druidDataSource
;
}
@Bean
public ServletRegistrationBean
<StatViewServlet> statViewServlet() {
ServletRegistrationBean
<StatViewServlet> bean
= new ServletRegistrationBean<>(new StatViewServlet(), "/druid/*");
Map
<String, String> maps
= new HashMap<>(5);
maps
.put("loginUsername", "admin");
maps
.put("loginPassword", "123456");
maps
.put("allow", "");
maps
.put("deny", "");
bean
.setInitParameters(maps
);
return bean
;
}
@Bean
public FilterRegistrationBean
<WebStatFilter> webStatFilter() {
FilterRegistrationBean
<WebStatFilter> bean
= new FilterRegistrationBean<>(new WebStatFilter());
bean
.setUrlPatterns(Collections
.singletonList("/*"));
Map
<String, String> maps
= new HashMap<>(2);
maps
.put("exclusion", "*.js/,*.css,/druid/*");
bean
.setInitParameters(maps
);
return bean
;
}
}
DynamicDataSourceConfig.java
package cn
.qingweico
.config
;
import cn
.qingweico
.dao
.spilt
.DynamicDataSource
;
import org
.springframework
.context
.annotation
.Bean
;
import org
.springframework
.context
.annotation
.Configuration
;
import javax
.sql
.DataSource
;
import java
.util
.HashMap
;
import java
.util
.Map
;
@Configuration
public class DynamicDataSourceConfig {
@Bean
public DynamicDataSource
dynamicDataSource(DataSource masterDataSource
, DataSource slaveDataSource
){
DynamicDataSource dynamicDataSource
= new DynamicDataSource();
Map
<Object, Object> map
= new HashMap<>(5);
map
.put("master",masterDataSource
);
map
.put("slave",slaveDataSource
);
dynamicDataSource
.setTargetDataSources(map
);
return dynamicDataSource
;
}
}
DynamicDataSource.java
package cn
.qingweico
.dao
.spilt
;
import org
.springframework
.jdbc
.datasource
.lookup
.AbstractRoutingDataSource
;
public class DynamicDataSource extends AbstractRoutingDataSource {
@Override
protected Object
determineCurrentLookupKey() {
return DynamicDataSourceHolder
.getDbType();
}
}
DynamicDataSourceHolder.java
package cn
.qingweico
.dao
.spilt
;
import org
.slf4j
.Logger
;
import org
.slf4j
.LoggerFactory
;
public class DynamicDataSourceHolder {
private static final Logger logger
= LoggerFactory
.getLogger(DynamicDataSourceHolder
.class);
private static final ThreadLocal
<String> CONTEXT_HOLDER
= new ThreadLocal<>();
public static final String DB_MASTER
= "master";
public static final String DB_SLAVE
= "slave";
public static String
getDbType() {
String db
= CONTEXT_HOLDER
.get();
if (db
== null
) {
db
= DB_MASTER
;
}
return db
;
}
public static void setDbType(String dbType
) {
logger
.debug("所使用的数据源是:" + dbType
);
CONTEXT_HOLDER
.set(dbType
);
}
public static void clearDbType() {
CONTEXT_HOLDER
.remove();
}
}
DynamicDataSourceInterceptor.java
package cn
.qingweico
.dao
.spilt
;
import org
.apache
.ibatis
.executor
.Executor
;
import org
.apache
.ibatis
.executor
.keygen
.SelectKeyGenerator
;
import org
.apache
.ibatis
.mapping
.BoundSql
;
import org
.apache
.ibatis
.mapping
.MappedStatement
;
import org
.apache
.ibatis
.mapping
.SqlCommandType
;
import org
.apache
.ibatis
.plugin
.*
;
import org
.apache
.ibatis
.session
.ResultHandler
;
import org
.apache
.ibatis
.session
.RowBounds
;
import org
.slf4j
.Logger
;
import org
.slf4j
.LoggerFactory
;
import org
.springframework
.transaction
.support
.TransactionSynchronizationManager
;
import java
.util
.Locale
;
import java
.util
.Properties
;
@Intercepts({@Signature(type
= Executor
.class, method
= "update", args
= {MappedStatement
.class, Object
.class}),
@Signature(type
= Executor
.class, method
= "query", args
= {MappedStatement
.class, Object
.class, RowBounds
.class, ResultHandler
.class})})
public class DynamicDataSourceInterceptor implements Interceptor {
private static final Logger logger
= LoggerFactory
.getLogger(DynamicDataSourceInterceptor
.class);
private static final String REGEX
= ".*insert\\u0020.*|.*update\\0020.*|.*delete\\0020.*";
@Override
public Object
intercept(Invocation invocation
) throws Throwable
{
boolean synchronizationActive
= TransactionSynchronizationManager
.isSynchronizationActive();
Object
[] objects
= invocation
.getArgs();
MappedStatement mappedStatement
= (MappedStatement
) invocation
.getArgs()[0];
String lookupKey
= DynamicDataSourceHolder
.DB_MASTER
;
if (!synchronizationActive
) {
if (mappedStatement
.getSqlCommandType().equals(SqlCommandType
.SELECT
)) {
if (mappedStatement
.getId().contains(SelectKeyGenerator
.SELECT_KEY_SUFFIX
)) {
lookupKey
= DynamicDataSourceHolder
.DB_MASTER
;
} else {
BoundSql boundSql
= mappedStatement
.getSqlSource().getBoundSql(objects
[1]);
String sql
= boundSql
.getSql().toLowerCase(Locale
.CHINA
).replaceAll("\\t\\n\\r", " ");
if (sql
.matches(REGEX
)) {
lookupKey
= DynamicDataSourceHolder
.DB_MASTER
;
} else {
lookupKey
= DynamicDataSourceHolder
.DB_SLAVE
;
}
}
}
} else {
lookupKey
= DynamicDataSourceHolder
.DB_MASTER
;
}
logger
.debug("设置[{}] use[{}] strategy,SqlCommandType[{}]......", mappedStatement
.getId(), lookupKey
, mappedStatement
.getSqlCommandType().name());
DynamicDataSourceHolder
.setDbType(lookupKey
);
return invocation
.proceed();
}
@Override
public Object
plugin(Object target
) {
if (target
instanceof Executor) {
return Plugin
.wrap(target
, this);
} else {
return target
;
}
}
@Override
public void setProperties(Properties properties
) {}
}
SpringBoot启动配置原理
SpringBoot启动类
package com
.example
;
import org
.springframework
.boot
.SpringApplication
;
import org
.springframework
.boot
.autoconfigure
.SpringBootApplication
;
import org
.springframework
.context
.ConfigurableApplicationContext
;
@SpringBootApplication
public class SpringBootDataApplication {
public static void main(String
[] args
) {
ConfigurableApplicationContext run
= SpringApplication
.run(SpringBootDataApplication
.class, args
);
String
[] beans
= run
.getBeanDefinitionNames();
for(String bean
: beans
){
System
.out
.println(bean
);
}
}
}
public static ConfigurableApplicationContext
run(Class
<?> primarySource
, String
... args
) {
return run(new Class[]{primarySource
}, args
);
}
public static ConfigurableApplicationContext
run(Class
<?>[] primarySources
, String
[] args
) {
return (new SpringApplication(primarySources
)).run(args
);
}
先创建SpringApplication对象再运行run方法
创建SpringApplication对象
SpringApplication构造函数
public SpringApplication(Class
<?>... primarySources
) {
this(null
, primarySources
);
}
public SpringApplication(ResourceLoader resourceLoader
, Class
<?>... primarySources
) {
this.resourceLoader
= resourceLoader
;
Assert
.notNull(primarySources
, "PrimarySources must not be null");
this.primarySources
= new LinkedHashSet<>(Arrays
.asList(primarySources
));
this.webApplicationType
= WebApplicationType
.deduceFromClasspath();
setInitializers((Collection
) getSpringFactoriesInstances(ApplicationContextInitializer
.class));
setListeners((Collection
) getSpringFactoriesInstances(ApplicationListener
.class));
this.mainApplicationClass
= deduceMainApplicationClass();
}
WebApplicationType.deduceFromClasspath()
static WebApplicationType
deduceFromClasspath() {
if (ClassUtils
.isPresent(WEBFLUX_INDICATOR_CLASS
, null
) && !ClassUtils
.isPresent(WEBMVC_INDICATOR_CLASS
, null
)
&& !ClassUtils
.isPresent(JERSEY_INDICATOR_CLASS
, null
)) {
return WebApplicationType
.REACTIVE
;
}
for (String className
: SERVLET_INDICATOR_CLASSES
) {
if (!ClassUtils
.isPresent(className
, null
)) {
return WebApplicationType
.NONE
;
}
}
return WebApplicationType
.SERVLET
;
}
private static final String
[] SERVLET_INDICATOR_CLASSES
= { "javax.servlet.Servlet",
"org.springframework.web.context.ConfigurableWebApplicationContext" };
private static final String WEBMVC_INDICATOR_CLASS
= "org.springframework.web.servlet.DispatcherServlet";
private static final String WEBFLUX_INDICATOR_CLASS
= "org.springframework.web.reactive.DispatcherHandler";
private static final String JERSEY_INDICATOR_CLASS
= "org.glassfish.jersey.servlet.ServletContainer";
getSpringFactoriesInstances()
private <T> Collection
<T> getSpringFactoriesInstances(Class
<T> type
) {
return getSpringFactoriesInstances(type
, new Class<?>[] {});
}
private <T> Collection
<T> getSpringFactoriesInstances(Class
<T> type
, Class
<?>[] parameterTypes
, Object
... args
) {
ClassLoader classLoader
= getClassLoader();
Set
<String> names
= new LinkedHashSet<>(SpringFactoriesLoader
.loadFactoryNames(type
, classLoader
));
List
<T> instances
= createSpringFactoriesInstances(type
, parameterTypes
, classLoader
, args
, names
);
AnnotationAwareOrderComparator
.sort(instances
);
return instances
;
}
loadFactoryNames()
public static List
<String> loadFactoryNames(Class
<?> factoryType
, @Nullable ClassLoader classLoader
) {
String factoryTypeName
= factoryType
.getName();
return loadSpringFactories(classLoader
).getOrDefault(factoryTypeName
, Collections
.emptyList());
}
loadSpringFactories()
private static Map
<String
, List
<String>> loadSpringFactories(@Nullable ClassLoader classLoader
) {
MultiValueMap
<String, String> result
= cache
.get(classLoader
);
if (result
!= null
) {
return result
;
}
try {
Enumeration
<URL> urls
= (classLoader
!= null
?
classLoader
.getResources(FACTORIES_RESOURCE_LOCATION
) :
ClassLoader
.getSystemResources(FACTORIES_RESOURCE_LOCATION
));
result
= new LinkedMultiValueMap<>();
while (urls
.hasMoreElements()) {
URL url
= urls
.nextElement();
UrlResource resource
= new UrlResource(url
);
Properties properties
= PropertiesLoaderUtils
.loadProperties(resource
);
for (Map
.Entry
<?, ?> entry
: properties
.entrySet()) {
String factoryTypeName
= ((String
) entry
.getKey()).trim();
for (String factoryImplementationName
: StringUtils
.commaDelimitedListToStringArray((String
) entry
.getValue())) {
result
.add(factoryTypeName
, factoryImplementationName
.trim());
}
}
}
cache
.put(classLoader
, result
);
return result
;
}
catch (IOException ex
) {
throw new IllegalArgumentException("Unable to load factories from location [" +
FACTORIES_RESOURCE_LOCATION
+ "]", ex
);
}
}
public static final String FACTORIES_RESOURCE_LOCATION
= "META-INF/spring.factories";
ApplicationContextInitializer类
org\springframework\boot\spring-boot\2.3.2.RELEASE\
spring-boot-2.3.2.RELEASE.jar!\
META-INF\
spring.factories
org.springframework.context.ApplicationContextInitializer=\
org.springframework.boot.context.ConfigurationWarningsApplicationContextInitializer,\
org.springframework.boot.context.ContextIdApplicationContextInitializer,\
org.springframework.boot.context.config.DelegatingApplicationContextInitializer,\
org.springframework.boot.rsocket.context.RSocketPortInfoApplicationContextInitializer,\
org.springframework.boot.web.context.ServerPortInfoApplicationContextInitializer
org\springframework\boot\spring-boot-autoconfigure\2.3.2.RELEASE\
spring-boot-autoconfigure-2.3.2.RELEASE.jar!\
META-INF\
spring.factories
# Initializers
org.springframework.context.ApplicationContextInitializer=\
org.springframework.boot.autoconfigure.SharedMetadataReaderFactoryContextInitializer,\
org.springframework.boot.autoconfigure.logging.ConditionEvaluationReportLoggingListener
ApplicationListener类
org\springframework\boot\spring-boot\2.3.2.RELEASE\
spring-boot-2.3.2.RELEASE.jar!\
META-INF\
spring.factories
# Application Listeners
org.springframework.context.ApplicationListener=\
org.springframework.boot.ClearCachesApplicationListener,\
org.springframework.boot.builder.ParentContextCloserApplicationListener,\
org.springframework.boot.cloud.CloudFoundryVcapEnvironmentPostProcessor,\
org.springframework.boot.context.FileEncodingApplicationListener,\
org.springframework.boot.context.config.AnsiOutputApplicationListener,\
org.springframework.boot.context.config.ConfigFileApplicationListener,\
org.springframework.boot.context.config.DelegatingApplicationListener,\
org.springframework.boot.context.logging.ClasspathLoggingApplicationListener,\
org.springframework.boot.context.logging.LoggingApplicationListener,\
org.springframework.boot.liquibase.LiquibaseServiceLocatorApplicationListener
org\springframework\boot\spring-boot-autoconfigure\2.3.2.RELEASE\
spring-boot-autoconfigure-2.3.2.RELEASE.jar!\
META-INF\
spring.factories
# Application Listeners
org.springframework.context.ApplicationListener=\
org.springframework.boot.autoconfigure.BackgroundPreinitializer
deduceMainApplicationClass()
private Class
<?> deduceMainApplicationClass() {
try {
StackTraceElement
[] stackTrace
= new RuntimeException().getStackTrace();
for (StackTraceElement stackTraceElement
: stackTrace
) {
if ("main".equals(stackTraceElement
.getMethodName())) {
return Class
.forName(stackTraceElement
.getClassName());
}
}
}
catch (ClassNotFoundException ex
) {
}
return null
;
}
运行run方法
public ConfigurableApplicationContext
run(String
... args
) {
StopWatch stopWatch
= new StopWatch();
stopWatch
.start();
ConfigurableApplicationContext context
= null
;
Collection
<SpringBootExceptionReporter> exceptionReporters
= new ArrayList<>();
configureHeadlessProperty();
SpringApplicationRunListeners listeners
= getRunListeners(args
);
listeners
.starting();
try {
ApplicationArguments applicationArguments
= new DefaultApplicationArguments(args
);
ConfigurableEnvironment environment
= prepareEnvironment(listeners
, applicationArguments
);
configureIgnoreBeanInfo(environment
);
Banner printedBanner
= printBanner(environment
);
context
= createApplicationContext();
exceptionReporters
= getSpringFactoriesInstances(SpringBootExceptionReporter
.class,
new Class[] { ConfigurableApplicationContext
.class }, context
);
prepareContext(context
, environment
, listeners
, applicationArguments
, printedBanner
);
refreshContext(context
);
afterRefresh(context
, applicationArguments
);
stopWatch
.stop();
if (this.logStartupInfo
) {
new StartupInfoLogger(this.mainApplicationClass
).logStarted(getApplicationLog(), stopWatch
);
}
listeners
.started(context
);
callRunners(context
, applicationArguments
);
}
catch (Throwable ex
) {
handleRunFailure(context
, ex
, exceptionReporters
, listeners
);
throw new IllegalStateException(ex
);
}
try {
listeners
.running(context
);
}
catch (Throwable ex
) {
handleRunFailure(context
, ex
, exceptionReporters
, null
);
throw new IllegalStateException(ex
);
}
return context
;
}
SpringApplicationRunListener类
org\springframework\boot\spring-boot\2.3.2.RELEASE\
spring-boot-2.3.2.RELEASE.jar!\
META-INF\
spring.factories
# Run Listeners
org.springframework.boot.SpringApplicationRunListener=\
org.springframework.boot.context.event.EventPublishingRunListener
listeners.starting()
void starting() {
for (SpringApplicationRunListener listener
: this.listeners
) {
listener
.starting();
}
}
ConfigurableEnvironment prepareEnvironment()
private ConfigurableEnvironment
prepareEnvironment(SpringApplicationRunListeners listeners
,
ApplicationArguments applicationArguments
) {
ConfigurableEnvironment environment
= getOrCreateEnvironment();
configureEnvironment(environment
, applicationArguments
.getSourceArgs());
ConfigurationPropertySources
.attach(environment
);
listeners
.environmentPrepared(environment
);
bindToSpringApplication(environment
);
if (!this.isCustomEnvironment
) {
environment
= new EnvironmentConverter(getClassLoader()).convertEnvironmentIfNecessary(environment
,
deduceEnvironmentClass());
}
ConfigurationPropertySources
.attach(environment
);
return environment
;
}
listeners.environmentPrepared()
void environmentPrepared(ConfigurableEnvironment environment
) {
for (SpringApplicationRunListener listener
: this.listeners
) {
listener
.environmentPrepared(environment
);
}
}
printBanner()
. ____ _ __ _ _
/\\ / ___'_ __ _ _(_)_ __ __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
\\/ ___)| |_)| | | | | || (_| | ) ) ) )
' |____| .__|_| |_|_| |_\__, | / / / /
=========|_|==============|___/=/_/_/_/
:: Spring Boot :: (v2.3.2.RELEASE)
createApplicationContext()
protected ConfigurableApplicationContext
createApplicationContext() {
Class
<?> contextClass
= this.applicationContextClass
;
if (contextClass
== null
) {
try {
switch (this.webApplicationType
) {
case SERVLET
:
contextClass
= Class
.forName(DEFAULT_SERVLET_WEB_CONTEXT_CLASS
);
break;
case REACTIVE
:
contextClass
= Class
.forName(DEFAULT_REACTIVE_WEB_CONTEXT_CLASS
);
break;
default:
contextClass
= Class
.forName(DEFAULT_CONTEXT_CLASS
);
}
}
catch (ClassNotFoundException ex
) {
throw new IllegalStateException(
"Unable create a default ApplicationContext, please specify an ApplicationContextClass", ex
);
}
}
return (ConfigurableApplicationContext
) BeanUtils
.instantiateClass(contextClass
);
}
public static final String DEFAULT_SERVLET_WEB_CONTEXT_CLASS
= "org.springframework.boot."
+ "web.servlet.context.AnnotationConfigServletWebServerApplicationContext";
public static final String DEFAULT_REACTIVE_WEB_CONTEXT_CLASS
= "org.springframework."
+ "boot.web.reactive.context.AnnotationConfigReactiveWebServerApplicationContext";
public static final String DEFAULT_CONTEXT_CLASS
= "org.springframework.context."
+ "annotation.AnnotationConfigApplicationContext";
prepareContext()
private void prepareContext(ConfigurableApplicationContext context
, ConfigurableEnvironment environment
,
SpringApplicationRunListeners listeners
, ApplicationArguments applicationArguments
, Banner printedBanner
) {
context
.setEnvironment(environment
);
postProcessApplicationContext(context
);
applyInitializers(context
);
listeners
.contextPrepared(context
);
if (this.logStartupInfo
) {
logStartupInfo(context
.getParent() == null
);
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
= getAllSources();
Assert
.notEmpty(sources
, "Sources must not be empty");
load(context
, sources
.toArray(new Object[0]));
listeners
.contextLoaded(context
);
}
listeners.contextPrepared()
void contextPrepared(ConfigurableApplicationContext context
) {
for (SpringApplicationRunListener listener
: this.listeners
) {
listener
.contextPrepared(context
);
}
}
listeners.contextLoaded()
void contextLoaded(ConfigurableApplicationContext context
) {
for (SpringApplicationRunListener listener
: this.listeners
) {
listener
.contextLoaded(context
);
}
}
2020-08-07 09:26:00.064 INFO
19028 --- [ main
] com
.example
.SpringBootDataApplication
: Starting SpringBootDataApplication on LAPTOP
-LIEB0H0I with PID
19028 (C
:\Users\周庆伟\java\springbootdata\target\classes started by 周庆伟 in C
:\Users\周庆伟\java\springbootdata
)
2020-08-07 09:26:00.563 INFO
19028 --- [ main
] com
.example
.SpringBootDataApplication
: No active profile set
, falling back to
default profiles
: default
listeners.started()
void started(ConfigurableApplicationContext context
) {
for (SpringApplicationRunListener listener
: this.listeners
) {
listener
.started(context
);
}
}
callRunners
private void callRunners(ApplicationContext context
, ApplicationArguments args
) {
List
<Object> runners
= new ArrayList<>();
runners
.addAll(context
.getBeansOfType(ApplicationRunner
.class).values());
runners
.addAll(context
.getBeansOfType(CommandLineRunner
.class).values());
AnnotationAwareOrderComparator
.sort(runners
);
for (Object runner
: new LinkedHashSet<>(runners
)) {
if (runner
instanceof ApplicationRunner) {
callRunner((ApplicationRunner
) runner
, args
);
}
if (runner
instanceof CommandLineRunner) {
callRunner((CommandLineRunner
) runner
, args
);
}
}
}
listeners.running()
void running(ConfigurableApplicationContext context
) {
for (SpringApplicationRunListener listener
: this.listeners
) {
listener
.running(context
);
}
}
SpringBoot缓存
CachingManager管理多个Cache组件,而每个组件都有自己的唯一一个名称
@Cacheable(cacheNames
= {"studentInfo"} ,key
="#id")
public Student
findById(int id
){
System
.out
.println("连接数据库啦!!");
return studentDao
.findById(id
);
}
@EnableCaching
@Cacheable注解
public @
interface Cacheable {
@AliasFor("cacheNames")
String
[] value() default {};
@AliasFor("value")
String
[] cacheNames() default {};
String
key() default "";
String
keyGenerator() default "";
String
cacheManager() default "";
String
cacheResolver() default "";
String
condition() default "";
String
unless() default "";
boolean sync() default false;
}
String
[] cacheNames() default {};
String
key() default "";
#指定key时可以使用spEL表达式
key ="#root.methodName" //当前被调用的方法名
key ="#root.method.name" //当前被调用的方法
key ="#root.target" //当前被调用的目标对象
key ="#root.targetClass" //当前被调用的目标对象类
key ="#root.args[0]" //当前被调用方法的参数列表
key ="#root.caches[0].name" //当前方法调用的缓存列表
key ="#result" //当前方法执行返回的值
key ="#参数" key ="#a0" //当前参数的值 0代表参数的索引·
String
keyGenerator() default "";
String
cacheManager() default "";
String
cacheResolver() default "";
String
condition() default "";
String
unless() default "";
自动配置类CacheAutoConfiguration
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
;
}
}
org
.springframework
.boot
.autoconfigure
.cache
.GenericCacheConfiguration
org
.springframework
.boot
.autoconfigure
.cache
.JCacheCacheConfiguration
org
.springframework
.boot
.autoconfigure
.cache
.EhCacheCacheConfiguration
org
.springframework
.boot
.autoconfigure
.cache
.HazelcastCacheConfiguration
org
.springframework
.boot
.autoconfigure
.cache
.InfinispanCacheConfiguration
org
.springframework
.boot
.autoconfigure
.cache
.CouchbaseCacheConfiguration
org
.springframework
.boot
.autoconfigure
.cache
.RedisCacheConfiguration
org
.springframework
.boot
.autoconfigure
.cache
.CaffeineCacheConfiguration
org
.springframework
.boot
.autoconfigure
.cache
.SimpleCacheConfiguration
org
.springframework
.boot
.autoconfigure
.cache
.NoOpCacheConfiguration
@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
);
}
}
public class ConcurrentMapCache extends AbstractValueAdaptingCache {
private final String name
;
private final ConcurrentMap
<Object, Object> store
;
}
运行流程
@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
;
}
目标方法运行之前会先去查询缓存组件,根据cacheNames查询,第一次查询cache为空则创建cache并放在ConcurrentMap中
protected Cache
createConcurrentMapCache(String name
) {
SerializationDelegate actualSerialization
= (isStoreByValue() ? this.serialization
: null
);
return new ConcurrentMapCache(name
, new ConcurrentHashMap<>(256), isAllowNullValues(), actualSerialization
);
}
根据key(默认方法参数的值 ,若参数有多个则传入多个参数包装的对象)来查询value,若value为空则调用目标方法
@Override
@Nullable
protected Object
lookup(Object key
) {
return this.store
.get(key
);
}
将目标的返回值放入缓存中
@Override
public void put(Object key
, @Nullable Object value
) {
this.store
.put(key
, toStoreValue(value
));
}
@CachePut注解
用于更新数据 先调用目标方法,再将方法的返回值更新到缓存中(不仅可以更新数据库中的数据还可以更新缓存中的数据)
@CachePut(cacheNames
= {"studentInfo"}, key
= "#student.id")
public Student
update(Student student
){
System
.out
.println("更新数据啦!");
studentDao
.update(student
);
return student
;
}
@Cacheable是先根据key查询缓存,若该key对应的值为null再调用目标方法,最后才将数据put进缓存中
@CachePut则是直接调用目标方法,再将方法的值put进缓存中,会覆盖key对应的值
@CacheEvict注解
用于清除缓存
@CacheEvict(cacheNames
= {"studentInfo"} ,key
= "#id" ,allEntries
= true ,beforeInvocation
= true)
public void deleteCacheById(Integer id
){
}
allEntries属性默认为false即根据只key来删除相对应的缓存,而true即代表删除该缓存组件中所有的缓存
beforeInvocation属性默认为false,代表方法执行完后清除缓存 true代表方法执行前删除缓存,无论方法非正常执行完都要清除缓存
Caching注解
可以指定多个属性
@Target({ElementType
.TYPE
, ElementType
.METHOD
})
@Retention(RetentionPolicy
.RUNTIME
)
@Inherited
@Documented
public @
interface Caching {
Cacheable
[] cacheable() default {};
CachePut
[] put() default {};
CacheEvict
[] evict() default {};
}
@Caching(
cacheable
= {@Cacheable(value
= "studentInfo" ,key
= "#student.id")},
put
= {@CachePut(value
= "studentInfo" ,key
= "#result.name")}
)
public Student
findNameById(Student student
){
studentDao
.findNameById(student
);
return student
;
}
@CacheConfig注解
标注在类上,指定该类中方法共有的缓存属性
@Target(ElementType
.TYPE
)
@Retention(RetentionPolicy
.RUNTIME
)
@Documented
public @
interface CacheConfig {
String
[] cacheNames() default {};
String
keyGenerator() default "";
String
cacheManager() default "";
String
cacheResolver() default "";
}
Redis
pom.xml
<dependency>
<groupId>org.springframework.boot
</groupId>
<artifactId>spring-boot-starter-data-redis
</artifactId>
</dependency>
配置redis主机
spring:
redis:
host: 192.168.0.107
@Autowired
StringRedisTemplate stringRedisTemplate
;
@Autowired
RedisTemplate redisTemplate
;
自定义redis序列化对象的规则
保存对象默认使用jdk序列化机制
package com
.example
.config
;
import com
.example
.entity
.Student
;
import org
.springframework
.context
.annotation
.Bean
;
import org
.springframework
.context
.annotation
.Configuration
;
import org
.springframework
.data
.redis
.connection
.RedisConnectionFactory
;
import org
.springframework
.data
.redis
.core
.RedisTemplate
;
import org
.springframework
.data
.redis
.serializer
.Jackson2JsonRedisSerializer
;
@Configuration
public class MyRedisConfig {
@Bean
public RedisTemplate
<Object ,Student> myRedisTemplate(RedisConnectionFactory redisConnectionFactory
){
RedisTemplate
<Object ,Student> redisTemplate
= new RedisTemplate<>();
redisTemplate
.setConnectionFactory(redisConnectionFactory
);
Jackson2JsonRedisSerializer
<Student> jackson2JsonRedisSerializer
= new Jackson2JsonRedisSerializer<>(Student
.class);
redisTemplate
.setDefaultSerializer(jackson2JsonRedisSerializer
);
return redisTemplate
;
}
@Bean
public RedisCacheManager
cacheManager(RedisConnectionFactory connectionFactory
) {
RedisCacheConfiguration cacheConfiguration
= RedisCacheConfiguration
.defaultCacheConfig()
.entryTtl(Duration
.ofDays(1))
.disableCachingNullValues()
.serializeValuesWith(RedisSerializationContext
.SerializationPair
.fromSerializer(new GenericJackson2JsonRedisSerializer()));
return RedisCacheManager
.builder(connectionFactory
).cacheDefaults(cacheConfiguration
).build();
}
}
@Autowired
StudentDao studentDao
;
@Autowired
RedisTemplate
<Object , Student> myRedisTemplate
;
@Test
void contextLoads() {
Student student
= studentDao
.findById(2);
myRedisTemplate
.opsForValue().set("student",student
);
}
实体类实现Serializable接口
RedisCacheConfiguration
默认会将CacheName作为key的前缀
@Deprecated
public RedisCacheConfiguration
prefixKeysWith(String prefix
) {
Assert
.notNull(prefix
, "Prefix must not be null!");
return computePrefixWith((cacheName
) -> prefix
);
}
当容器中存在多个cacheManager时,需要在目标方法的类上使用@CacheConfig注解指明cacheManager
直接在组件方法上使用@Primary注解表明这是默认的cacheManager
@Autowired
RedisCacheManager cacheManager
;
public Student
findById(int id
){
System
.out
.println("连接数据库啦!!");
Student student
= studentDao
.findById(id
);
Cache cache
= cacheManager
.getCache("studentInfo");
cache
.put("key",student
);
return student
;
}