目录 [TOC]
SpringBoot(v2.3.2.RELEASE) SpringBoot配置 场景启动器 spring-boot-start
1 2 3 4 5 6 7 8 9 <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
@EnableAutoConfiguration
1 2 3 4 5 6 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.properties
Application.yml
yml文件 将空格玩到极致的语法 所有的冒号后面都有一个空格
1 2 3 4 5 6 7 8 9 person: name: jack age: 21 list: - hello - world maps: k1: v1 k2: 12
1 2 3 4 5 6 person.name =jack person.age =21 person.list =hello,world person.maps.k1 =v1 person.maps.k2 =12
读取yml文件 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 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默读取全局配置文件
相同点都是可以从配置文件中读取值并注入到字段中(前提是字段所属的类一定要在ioc容器中)
@ConfigurationProperties批量注入配置文件中的数据,而@Value则是一次一次注入
前者支持松散绑定语法(下划线转驼峰),而后者不支持
前者不支持SPEL,后者支持
前者支持数据校验,后者不支持
前者支持复杂数据类型的封装,后者不支持
前者支持注入静态类型的变量,后者不支持(当使用set方法时也可以完成为静态变量注入,注意set方法不可以是静态的, 且@Value注解加在set方法上)
PropertySouce和ImportResouce注解的区别 PropertySource 1 @PropertySouce(value = {"classpath:xxx.properties"})
ImportResource(标注在一个配置类上) 1 @ImportResource(locations = {"classpath:bean.xml"})
SpringBoot不能自动识别自定义的配置文件,需要手动导入
SpringBoot不推荐用此方式向Spring容器中添加组件,可以自定义一个配置类代替配置文件,使用@Bean
注解方式向容器中加入组件
配置文件占位符 1 2 3 4 5 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文件 在主配置文件中激活环境
1 spring.Profiles.active = dev
application-dev.properties
yml文件 使用文档块的形式
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 server: port: 8080 spring: profiles: active: dev --- server: port: 8081 spring: profiles: dev --- server: port: 8082 spring: profiles: prod
使用命令行参数 1 --spring.profiles.active=dev
使用虚拟机参数 1 -Dspring.profiles.active=dev
配置文件的加载顺序 优先级为:项目根路径下的config中的配置文件 > 项目根路径下配置文件 > 类路径下config中的配置文件 > 类路径下配置文件
1 2 3 4 /config/xxx.properties /xxx.properties classpath :/config/xxx.properties classpaht :/xxx.properties
SpringBoot会全部加载这四个位置的配置文件,高优先级的配置文件会覆盖优先级低的相同配置文件
1 2 3 --spring.config.location =xxx.properties
1 2 java -jar hello-0.0.1-SNAPSHOT.jar --spring.config.location=E:/application.properties
1 server.servlet.context-path=/test
外部配置加载顺序
1 2 java -jar hello-0.0.1-SNAPSHOT.jar --server.port=8848 --server.servlet.context-path=/test
优先加载带profile的文件,再加载不带profile的文件
从jar包外向内加载
SpringBoot访问静态资源 1 2 3 4 5 classpath :/META-INF/resources/ classpath :/resources/ classpath :/public/ classpath :/static/ / :
1 2 3 4 5 6 7 spring: mvc: static-path-pattern: /static/** resources: static-locations: classpath:/hello,classpath:/test
关于Spring Boot中使用request.getServletContext().getRealPath路径获取问题 String path = req.getServletContext().getRealPath("");
默认情况下返回的是一个临时文件夹的地址
1 2 3 4 package org.springframework.boot.web.servlet.server; class DocumentRoot { private static final String[] COMMON_DOC_ROOTS = { "src/main/webapp" , "public" , "static" }; }
所以只要在项目根目录新建文件夹public或者static或者在类路径下新建文件夹webapp即可读到路径
优先级 webapp > public > static(同时存在)
Thymeleaf 1 2 <html lang ="en" xmlns:th ="http://www.thymeleaf.org" >
抽取公共代码片段 1 2 3 4 5 6 7 8 9 10 <!DOCTYPE html > <html xmlns:th ="http://www.thymeleaf.org" > <body > <div th:fragment ="copy" > © 2011 The Good Thymes Virtual Grocery </div > </body > </html >
引入公共代码片段 1 2 3 4 5 6 7 8 9 <body > ... <div th:insert ="footer :: copy" > </div > </body > ~{templatename::fragmentname} ~{模板名::代码片段名} ~{templatename::selector} ~{模板名::选择器}
三种引入公共代码片段的方式
th:insert
th:replace
th:include
1 2 3 <footer th:fragment ="copy" > © 2011 The Good Thymes Virtual Grocery </footer >
1 2 3 4 5 6 <body > ... <div th:insert ="footer :: copy" > </div > <div th:replace ="footer :: copy" > </div > <div th:include ="footer :: copy" > </div > </body >
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 <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
添加视图映射和拦截器 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 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" ); } @Override public void addResourceHandlers (ResourceHandlerRegistry registry) { registry.addResourceHandler("/**" ).addResourceLocations( "classpath:/templates/" , "classpath:/static/" , "classpath:/resources/" ); } }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 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
1 2 3 4 class ErrorProperties @Value("${error.path:/error}") private String path = "/error" ;
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 @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); } }
1 2 3 4 5 6 7 8 9 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 ; }
1 2 3 4 5 6 7 8 9 10 11 12 13 @FunctionalInterface public interface ErrorViewResolver { ModelAndView resolveErrorView (HttpServletRequest request, HttpStatus status, Map<String, Object> model) ; }
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 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 ; } }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 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
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 <!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容器 全局配置文件
1 2 3 @ConfigurationProperties(prefix = "server",ignoreUnknownFields = true) class ServerProperties
注册Servlet三大组件 Servlet
Filter
Listener 1 ServletListenerRegistrationBean
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 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 1 2 3 4 5 6 7 8 9 10 11 12 13 14 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 1 2 3 4 5 6 7 8 9 10 11 12 13 14 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容器 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 <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 >
配置跨域请求 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 package com.example.config;import org.springframework.context.annotation.Configuration;import org.springframework.web.servlet.config.annotation.CorsRegistry;import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;@Configuration public class CorsConfig implements WebMvcConfigurer { @Override public void addCorsMappings (CorsRegistry registry) { WebMvcConfigurer.super .addCorsMappings(registry); registry.addMapping("/**" ) .allowedHeaders("*" ) .allowedMethods("POST" , "GET" , "DELETE" , "PUT" ) .allowedOrigins("*" ); } }
数据访问 SpringBoot JDBC自动配置原理 org.springframework.boot.autoconfigure.jdbc
1 2 配置各种数据源,也可以自定义数据源 在配置文件中指定spring.datasouce.type =xxx 指定自定义数据源
SpringBoot整合Spring Data JPA JPA (java persistence api)
pom.xml 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 <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 1 2 3 4 5 6 7 8 9 10 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
实体类 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 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 1 2 3 4 5 6 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 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 <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 1 2 3 4 5 6 7 8 9 10 11 12 13 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
启动类 1 @MapperScan("cn.qingweico.dao")
Druid(主从复制) pom.xml 1 2 3 4 5 6 7 8 9 10 <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 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 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 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 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 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 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 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 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 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 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 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 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 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 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启动类 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 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); } } }
1 2 3 public static ConfigurableApplicationContext run (Class<?> primarySource, String... args) { return run(new Class []{primarySource}, args); }
1 2 3 public static ConfigurableApplicationContext run (Class<?>[] primarySources, String[] args) { return (new SpringApplication (primarySources)).run(args); }
先创建SpringApplication对象再运行run方法
SpringApplication构造函数 1 2 3 public SpringApplication (Class<?>... primarySources) { this (null , primarySources); }
1 2 3 4 5 6 7 8 9 10 11 12 13 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() 1 2 3 4 5 6 7 8 9 10 11 12 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; }
1 2 3 4 5 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() 1 2 3 private <T> Collection<T> getSpringFactoriesInstances (Class<T> type) { return getSpringFactoriesInstances(type, new Class <?>[] {}); }
1 2 3 4 5 6 7 8 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() 1 2 3 4 public static List<String> loadFactoryNames (Class<?> factoryType, @Nullable ClassLoader classLoader) { String factoryTypeName = factoryType.getName(); return loadSpringFactories(classLoader).getOrDefault(factoryTypeName, Collections.emptyList()); }
loadSpringFactories() 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 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); } }
1 public static final String FACTORIES_RESOURCE_LOCATION = "META-INF/spring.factories" ;
ApplicationContextInitializer类 1 2 3 4 5 6 7 8 9 10 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
1 2 3 4 5 6 7 8 org\springframework\boot\spring-boot-autoconfigure\2.3.2.RELEASE\ spring-boot-autoconfigure-2.3.2.RELEASE.jar!\ META-INF\ spring.factories org.springframework.context.ApplicationContextInitializer =\ org.springframework.boot.autoconfigure.SharedMetadataReaderFactoryContextInitializer,\ org.springframework.boot.autoconfigure.logging.ConditionEvaluationReportLoggingListener
ApplicationListener类 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 org\springframework\boot\spring-boot\2.3.2.RELEASE\ spring-boot-2.3.2.RELEASE.jar!\ META-INF\ spring.factories 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
1 2 3 4 5 6 7 org\springframework\boot\spring-boot-autoconfigure\2.3.2.RELEASE\ spring-boot-autoconfigure-2.3.2.RELEASE.jar!\ META-INF\ spring.factories org.springframework.context.ApplicationListener =\ org.springframework.boot.autoconfigure.BackgroundPreinitializer
deduceMainApplicationClass() 1 2 3 4 5 6 7 8 9 10 11 12 13 14 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 ; }
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 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类 1 2 3 4 5 6 7 org\springframework\boot\spring-boot\2.3.2.RELEASE\ spring-boot-2.3.2.RELEASE.jar!\ META-INF\ spring.factories org.springframework.boot.SpringApplicationRunListener =\ org.springframework.boot.context.event.EventPublishingRunListener
listeners.starting() 1 2 3 4 5 void starting () { for (SpringApplicationRunListener listener : this .listeners) { listener.starting(); } }
ConfigurableEnvironment prepareEnvironment() 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 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() 1 2 3 4 5 void environmentPrepared (ConfigurableEnvironment environment) { for (SpringApplicationRunListener listener : this .listeners) { listener.environmentPrepared(environment); } }
printBanner() 1 2 3 4 5 6 7 . ____ _ __ _ _ /\\ / ___'_ __ _ _(_)_ __ __ _ \ \ \ \ ( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \ \\/ ___)| |_)| | | | | || (_| | ) ) ) ) ' |____| .__|_| |_|_| |_\__, | / / / / =========|_| ==============|___/=/_/_/_/ :: Spring Boot :: (v2.3.2.RELEASE)
createApplicationContext() 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 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); }
1 2 public static final String DEFAULT_SERVLET_WEB_CONTEXT_CLASS = "org.springframework.boot." + "web.servlet.context.AnnotationConfigServletWebServerApplicationContext" ;
1 2 public static final String DEFAULT_REACTIVE_WEB_CONTEXT_CLASS = "org.springframework." + "boot.web.reactive.context.AnnotationConfigReactiveWebServerApplicationContext" ;
1 2 public static final String DEFAULT_CONTEXT_CLASS = "org.springframework.context." + "annotation.AnnotationConfigApplicationContext" ;
prepareContext() 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 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() 1 2 3 4 5 void contextPrepared (ConfigurableApplicationContext context) { for (SpringApplicationRunListener listener : this .listeners) { listener.contextPrepared(context); } }
listeners.contextLoaded() 1 2 3 4 5 void contextLoaded (ConfigurableApplicationContext context) { for (SpringApplicationRunListener listener : this .listeners) { listener.contextLoaded(context); } }
1 2 3 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() 1 2 3 4 5 void started (ConfigurableApplicationContext context) { for (SpringApplicationRunListener listener : this .listeners) { listener.started(context); } }
callRunners 1 2 3 4 5 6 7 8 9 10 11 12 13 14 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() 1 2 3 4 5 void running (ConfigurableApplicationContext context) { for (SpringApplicationRunListener listener : this .listeners) { listener.running(context); } }
SpringBoot缓存
CachingManager管理多个Cache组件,而每个组件都有自己的唯一一个名称
1 2 3 4 5 6 @Cacheable(cacheNames = {"studentInfo"} ,key ="#id") public Student findById (int id) { System.out.println("连接数据库啦!!" ); return studentDao.findById(id); }
@Cacheable注解 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 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 ; }
1 String[] cacheNames() default {};
1 String key () default "" ;
1 2 3 4 5 6 7 8 9 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代表参数的索引·
1 2 String keyGenerator () default "" ;
1 String cacheManager () default "" ;
1 2 String cacheResolver () default "" ;
1 String condition () default "" ;
1 String unless () default "" ;
自动配置类CacheAutoConfiguration 1 2 3 4 5 6 7 8 9 10 11 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; } }
1 2 3 4 5 6 7 8 9 10 11 /*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
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 @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); } }
1 2 3 4 5 public class ConcurrentMapCache extends AbstractValueAdaptingCache { private final String name; private final ConcurrentMap<Object, Object> store; }
运行流程 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 @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中
1 2 3 4 5 protected Cache createConcurrentMapCache (String name) { SerializationDelegate actualSerialization = (isStoreByValue() ? this .serialization : null ); return new ConcurrentMapCache (name, new ConcurrentHashMap <>(256 ), isAllowNullValues(), actualSerialization); }
根据key(默认方法参数的值 ,若参数有多个则传入多个参数包装的对象)来查询value,若value为空则调用目标方法
1 2 3 4 5 6 @Override @Nullable protected Object lookup (Object key) { return this .store.get(key); }
将目标的返回值放入缓存中
1 2 3 4 5 @Override public void put (Object key, @Nullable Object value) { this .store.put(key, toStoreValue(value)); }
@CachePut注解 用于更新数据 先调用目标方法,再将方法的返回值更新到缓存中(不仅可以更新数据库中的数据还可以更新缓存中的数据)
1 2 3 4 5 6 7 8 @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注解 用于清除缓存
1 2 3 @CacheEvict(cacheNames = {"studentInfo"} ,key = "#id" ,allEntries = true ,beforeInvocation = true) public void deleteCacheById (Integer id) {}
allEntries属性默认为false即根据只key来删除相对应的缓存,而true即代表删除该缓存组件中所有的缓存
beforeInvocation属性默认为false,代表方法执行完后清除缓存 true代表方法执行前删除缓存,无论方法非正常执行完都要清除缓存
Caching注解 可以指定多个属性
1 2 3 4 5 6 7 8 9 10 11 12 13 @Target({ElementType.TYPE, ElementType.METHOD}) @Retention(RetentionPolicy.RUNTIME) @Inherited @Documented public @interface Caching { Cacheable[] cacheable() default {}; CachePut[] put() default {}; CacheEvict[] evict() default {}; }
1 2 3 4 5 6 7 8 @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注解 标注在类上,指定该类中方法共有的缓存属性
1 2 3 4 5 6 7 8 9 @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 1 2 3 4 <dependency > <groupId > org.springframework.boot</groupId > <artifactId > spring-boot-starter-data-redis</artifactId > </dependency >
配置redis主机 1 2 3 spring: redis: host: 192.168 .0 .107
1 2 3 4 @Autowired StringRedisTemplate stringRedisTemplate; @Autowired RedisTemplate redisTemplate;
自定义redis序列化对象的规则 保存对象默认使用jdk序列化机制
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 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(); } }
1 2 3 4 5 6 7 8 9 10 @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的前缀
1 2 3 4 5 @Deprecated public RedisCacheConfiguration prefixKeysWith (String prefix) { Assert.notNull(prefix, "Prefix must not be null!" ); return computePrefixWith((cacheName) -> prefix); }
当容器中存在多个cacheManager时,需要在目标方法的类上使用@CacheConfig
注解指明cacheManager
直接在组件方法上使用@Primary
注解表明这是默认的cacheManager
1 2 3 4 5 6 7 8 9 10 11 12 @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; }