导图社区 Springboot核心技术
Springboot学习笔记,包含入门简介、配置、日志、Web开发、Docker、数据访问、启动配置原理、自定义starter等模块,值得收藏学习哦!
编辑于2021-08-27 16:43:12Springboot核心技术篇
1. Spring Boot 入门
简化Spring开发
Spring Boot J2EE一站式解决方案
Spring Cloud 分布式整体解决方案
优点
快速创建独立运行的Spring项目以及主流框架集成
使用嵌入式的Servlet容器,无需打成war包
starters自动依赖与版本控制
大量的自动配置,简化开发,也可修改默认值
无需配置xml,无代码生成,开箱即用
准生产环境的运行时应用监控
与云计算的天然集成
微服务
一种架构风格
一个应用应该是一组小型服务;可以通过HTTP的方式进行互通;
每一个功能元素最终都是一个可独立替换和独立升级的软件单元
环境准备
jdk1.8
maven 3.x
IDEA
SpringBoot 1.5.9
标注主程序(主配置类)@SpringbootApplication
SpringApplication.run(<ClassName>.class, args);
创建一个可执行的jar包需要的pom插件
<build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins> </build>
场景启动器
pom
<parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.4.3</version> <relativePath/> <!-- lookup parent from repository --> </parent>
<parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-dependencies</artifactId> <version>2.4.3</version> </parent>
含有各种依赖的版本号
springboot的版本仲裁中心
spring-boot-starter-xxx
场景启动器
自动配置
主入口类
@SpringbootApplication
@Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) @Documented @Inherited
// springboot配置(springboot定义的注解),表示这个springboot的一个配置类 @SpringBootConfiguration
@Configuration
@Component
也是一个组件
spring定义的注解
// 开启自动配置功能 @EnableAutoConfiguration
@Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) @Documented @Inherited
// 自动配置包 @AutoConfigurationPackage
@Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) @Documented @Inherited
@Import(AutoConfigurationPackages.Registrar.class)
Spring底层注解@Import,给容器中导入一个组件,导入的组件为AutoConfigurationPackages.Registrar.class
AutoConfigurationPackages.Registrar.class
public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) { register(registry, new PackageImports(metadata).getPackageNames().toArray(new String[0])); }
将主配置类所在包及子包下的所有组件扫描到Spring容器中
@Import(AutoConfigurationImportSelector.class)
导入哪些组件的选择器
public String[] selectImports(AnnotationMetadata annotationMetadata) { if (!isEnabled(annotationMetadata)) { return NO_IMPORTS; } AutoConfigurationEntry autoConfigurationEntry = getAutoConfigurationEntry(annotationMetadata); return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations()); }
将所有需要导入的组件以全类名的方式放回;这样组件就会添加到容器中
protected AutoConfigurationEntry getAutoConfigurationEntry(AnnotationMetadata annotationMetadata) { if (!isEnabled(annotationMetadata)) { return EMPTY_ENTRY; } AnnotationAttributes attributes = getAttributes(annotationMetadata); List<String> configurations = getCandidateConfigurations(annotationMetadata, attributes);// 96个配置 configurations = removeDuplicates(configurations); Set<String> exclusions = getExclusions(annotationMetadata, attributes); checkExcludedClasses(configurations, exclusions); configurations.removeAll(exclusions); configurations = getConfigurationClassFilter().filter(configurations); fireAutoConfigurationImportEvents(configurations, exclusions); return new AutoConfigurationEntry(configurations, exclusions); }
protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) { List<String> configurations = SpringFactoriesLoader.loadFactoryNames(getSpringFactoriesLoaderFactoryClass(), 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; }
protected Class<?> getSpringFactoriesLoaderFactoryClass() { return EnableAutoConfiguration.class; }
protected ClassLoader getBeanClassLoader() { return this.beanClassLoader; }
public static List<String> loadFactoryNames(Class<?> factoryType, @Nullable ClassLoader classLoader) { ClassLoader classLoaderToUse = classLoader; if (classLoaderToUse == null) { classLoaderToUse = SpringFactoriesLoader.class.getClassLoader(); } String factoryTypeName = factoryType.getName(); return loadSpringFactories(classLoaderToUse).getOrDefault(factoryTypeName, Collections.emptyList()); }
private static Map < String, List < String >> loadSpringFactories(ClassLoader classLoader) { Map < String, List < String >> result = cache.get(classLoader); if (result != null) { return result; } result = new HashMap < > (); try { Enumeration < URL > urls = classLoader.getResources(FACTORIES_RESOURCE_LOCATION); 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(); String[]factoryImplementationNames = StringUtils.commaDelimitedListToStringArray((String)entry.getValue()); for (String factoryImplementationName: factoryImplementationNames) { result.computeIfAbsent(factoryTypeName, key->new ArrayList < > ()) .add(factoryImplementationName.trim()); } } } // Replace all lists with unmodifiable lists containing unique elements result.replaceAll((factoryType, implementations)->implementations.stream().distinct() .collect(Collectors.collectingAndThen(Collectors.toList(), Collections::unmodifiableList))); cache.put(classLoader, result); } catch (IOException ex) { throw new IllegalArgumentException("Unable to load factories from location [" + FACTORIES_RESOURCE_LOCATION + "]", ex); } return result; }
SpringFactoriesLoader#FACTORIES_RESOURCE_LOCATION= "META-INF/spring.factories"
J2EE的整体整合解决方案和自动配置都在 org.springframework.boot:spring-boot-autoconfigure:<version>包中
@ComponentScan(excludeFilters = { @Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class), @Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) })
使用Spring Initializer 向导快速创建springboot应用
resources文件夹中目录结构
static:保存所有的静态资源; js css images;
templates:保存所有的模板页面;(Spring Boot默认jar包使用嵌入式的Tomcat,默认不支持JSP页面);可以使用模板引擎(freemarker、thymeleaf);
application.properties:Spring Boot应用的配置文件;可以修改一些默认设置;
http://start.spring.io
2. Spring Boot配置
配置文件
全局的配置文件
配置文件名是固定
application.properties
application.yml
标记语言
使用缩进表示层级关系
缩进时不允许使用Tab键,只允许使用空格
缩进的空格数量不重要,相同层级的元素左对齐
大小写敏感
1. 基本语法
k: v
空格必须有
以空格的缩进来控制层级关系;只要是左对齐的一列数据,都是同一个层级的
2. 值的写法
字面量:普通的值(数字,字符串,布尔)
字符串默认不用加上单引号或者双引号;
"":双引号;不会转义字符串里面的特殊字符;特殊字符会作为本身想表示的意思
name: "zhangsan \n lisi":输出;zhangsan 换行 lisi
'':单引号;会转义特殊字符,特殊字符最终只是一个普通的字符串数据
name: ‘zhangsan \n lisi’:输出;zhangsan \n lisi
对象、Map(属性和值)(键值对)
k: v:在下一行来写对象的属性和值的关系;注意缩进
friends: lastName: zhangsan age: 20
行内写法
friends: {lastName: zhangsan,age: 18}
数组(List、Set)
用[- 值]表示数组中的一个元素
pets: ‐ cat ‐ dog ‐ pig
行内写法
pets: [cat,dog,pig]
配置文件值注入
配置文件yml
person: lastName: hello age: 18 boss: false birth: 2017/12/12 maps: {k1: v1,k2: 12} lists: ‐ lisi ‐ zhaoliu dog: name: 小狗 age: 12
注意,驼峰的大写字母可以改为-
lastName -> last-name
配置文件properties
person.last-name=张三 person.age=18 person.birth=2017/12/15 person.boss=false person.map.k1=v1 person.map.k2=v2 person.list=a,b,c person.dog.name=dog person.dog.age=15
properties配置文件编码问题
Settings -> Editor -> File Encodings -> [check] Transparent native-to-ascii conversion
javaBean
/** * 将配置文件中配置的每一个属性的值,映射到这个组件中 * @ConfigurationProperties:告诉SpringBoot将本类中的所有属性和配置文件中相关的配置进行绑定; * prefix = "person":配置文件中哪个下面的所有属性进行一一映射 * * 只有这个组件是容器中的组件,才能容器提供的@ConfigurationProperties功能; * */ @Component @ConfigurationProperties(prefix = "person") public class Person { private String lastName; private Integer age; private Boolean boss; private Date birth; private Map String,Object > maps; private List < Object > lists; private Dog dog; }
@Value和@ConfigurationProperties的区别
@Value
支持范围
字面量
${key}从环境变量,配置文件中取值
#{SpEL}
@ConfigurationProperties
区别
@Value vs @ConfigurationProperties
一个个绑定 vs 批量绑定
不支持松散绑定 vs 支持(lastName last-name)
支持SpEL vs 不支持SpEL
不支持JSR303配置文件值校验 vs 支持JSR303配置文件值校验
@Validated public class Person {
复杂类型封装不支持 vs 复杂类型封装支持
另:@Value注入map
配置文件
maps = {'k1':'v1','k2':'v2'}
JaveBean
@Value("#{${maps}}")
@Value("#{${mps:{'k3':'v3', 'k4':'v4'}}}")
导入配置文件处理器,以后编写配置就有提示
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring‐boot‐configuration‐processor</artifactId> <optional>true</optional> </dependency
@PropertySource
加载指定的配置文件(非application.properties)
value = {"classpath:person.properties"}
@ImportResource
导入Spring的配置文件(xml),让配置文件生效
locations = {"classpath:beans.xml"}
@Bean
将方法的返回值注入容器,id是方法名
配置文件占位符
配置文件中可以使用随机数
${random.int}
${random.int(value, [max])}
${random.long}
${random.long(value, [max]}
${random.value}
${random.uuid}
配置文件中可以使用已经配置的值
如果使用的值取不到值,会直接打印表达式,所以要指定默认值
${key:defaultValue}
profile多环境支持
默认使用application.properties
使用其它配置文件 spring.profile.acitve=
相同配置会被覆盖
yaml本身有文档块功能,所以可以一个文档分隔
---
定义该文档块环境
spring: profiles: dev
命令行模式激活
java -jar --spring.profiles.active=dev xxx.jar
虚拟机参数
java -jar -Dspring.profiles.active=dev xxx.jar
加载顺序
内部配置加载顺序
配置文件的加载位置
file:./config/
当前项目下根目录的config文件夹
file:./
当前项目根目录
classpath:/config/
classpath:./
优先级
以上是按照优先级从高到低的顺序,所有位置的配置文件都会被加载,高优先级的配置会覆盖低优先级的配置
互补配置
可以通过spring.config.location配置文件位置,以命令行参数的形式增加配置文件位置
比file:./config/优先级高
外部配置加载顺序
SpringBoot也可以从以下位置加载配置; 优先级从高到低;高优先级的配置覆盖低优先级的配置,所有的配置会形成互补配置
1.命令行参数
所有的配置都可以在命令行上进行指定
java -jar spring-boot-02-config-02-0.0.1-SNAPSHOT.jar --server.port=8087 --server.context-path=/abc
多个配置用空格分开; --配置项=值
2.来自java:comp/env的JNDI属性
3.Java系统属性(System.getProperties())
4.操作系统环境变量
5.RandomValuePropertySource配置的random.*属性值
由jar包外向jar包内进行寻找;
优先加载带profile
6.jar包外部的application-{profile}.properties或application.yml(带spring.profile)配置文件
7.jar包内部的application-{profile}.properties或application.yml(带spring.profile)配置文件
再来加载不带profile
8.jar包外部的application.properties或application.yml(不带spring.profile)配置文件
9.jar包内部的application.properties或application.yml(不带spring.profile)配置文件
10.@Configuration注解类上的@PropertySource
11.通过SpringApplication.setDefaultProperties指定的默认属性
配置原理
1. Springboot启动的时候,开启了自动配置功能@EnableAutoConfiguration
2. @EnableAutoConfiguration作用
使用选择器,导入组件
3. 每一个自动配置类进行自动配置功能;
4. 以HttpEncodingAutoConfiguration(Http编码自动配置)为例解释自动配置原理;
@Configuration //表示这是一个配置类,以前编写的配置文件一样,也可以给容器中添加组件 @EnableConfigurationProperties(HttpEncodingProperties.class) //启动指定类的ConfigurationProperties功能;将配置文件中对应的值和HttpEncodingProperties绑定起来;并把HttpEncodingProperties加入到ioc容器中 @ConditionalOnWebApplication //Spring底层@Conditional注解(Spring注解版),根据不同的条件,如果满足指定的条件,整个配置类里面的配置就会生效; 判断当前应用是否是web应用,如果是,当前配置类生效 @ConditionalOnClass(CharacterEncodingFilter.class) //判断当前项目有没有这个类CharacterEncodingFilter;SpringMVC中进行乱码解决的过滤器; @ConditionalOnProperty(prefix = "spring.http.encoding", value = "enabled", matchIfMissing =true) //判断配置文件中是否存在某个配置 spring.http.encoding.enabled;如果不存在,判断也是成立的 //即使我们配置文件中不配置pring.http.encoding.enabled=true,也是默认生效的; public class HttpEncodingAutoConfiguration { // 他已经和SpringBoot的配置文件映射了 private final HttpEncodingProperties properties; // 只有一个有参构造器的情况下,参数的值就会从容器中拿 public HttpEncodingAutoConfiguration(HttpEncodingProperties properties) { this.properties = properties; } @Bean //给容器中添加一个组件,这个组件的某些值需要从properties中获取 @ConditionalOnMissingBean(CharacterEncodingFilter.class) //判断容器没有这个组件? public CharacterEncodingFilter characterEncodingFilter() { CharacterEncodingFilter filter = new OrderedCharacterEncodingFilter(); filter.setEncoding(this.properties.getCharset().name()); filter.setForceRequestEncoding(this.properties.shouldForce(Type.REQUEST)); filter.setForceResponseEncoding(this.properties.shouldForce(Type.RESPONSE)); return filter; }
@Conditional派生注解
@Conditional扩展注解 作用(判断是否满足当前指定条件) @ConditionalOnJava 系统的java版本是否符合要求 @ConditionalOnBean 容器中存在指定Bean; @ConditionalOnMissingBean 容器中不存在指定Bean; @ConditionalOnExpression 满足SpEL表达式指定 @ConditionalOnClass 系统中有指定的类 @ConditionalOnMissingClass 系统中没有指定的类 @ConditionalOnSingleCandidate 容器中只有一个指定的Bean,或者这个Bean是首选Bean @ConditionalOnProperty 系统中指定的属性是否有指定的值 @ConditionalOnResource 类路径下是否存在指定资源文件 @ConditionalOnWebApplication 当前是web环境 @ConditionalOnNotWebApplication 当前不是web环境 @ConditionalOnJndi JNDI存在指定项
作用:必须是@Conditional指定的条件成立,才给容器中添加组件,配置配里面的所有内容才生效;
5. 所有在配置文件中能配置的属性都是在xxxxProperties类中封装者‘;配置文件能配置什么就可以参照某个功能对应的这个属性类
自动配置类必须在一定的条件下才能生效
怎么知道哪些自动配置类生效
以通过启用 debug=true属性;来让控制台打印自动配置报告
总结
1)、SpringBoot启动会加载大量的自动配置类
2)、我们看我们需要的功能有没有SpringBoot默认写好的自动配置类;
3)、我们再来看这个自动配置类中到底配置了哪些组件;(只要我们要用的组件有,我们就不需要再来配置了)
4)、给容器中自动配置类添加组件的时候,会从properties类中获取某些属性。我们就可以在配置文件中指定这些属性的值;
3. Spring Boot与日志
日志框架分类和选择
市面上的日志框架
JUL JCL jboss-logging logback log4j log4j2 slf4j
JCL 2014年最后一次更新
日志门面SLF4J;
日志实现Logback
SpringBoot:底层是Spring框架,Spring框架默认是用JCL
SpringBoot选用 SLF4j和logback;
Slf4j使用原理
导入slf4j的jar和logback的jar
slf4j和其他实现包

配置文件还是做成日志实现框架自己本身的配置文件;
其他日志框架统一转化为slf4j
a(slf4j+logback): Spring(commons-logging)、Hibernate(jboss-logging)、MyBatis、xxxx
统一别的框架的日志记录,统一使用slf4j进行输出

步骤
1、将系统中其他日志框架先排除出去;
2、用中间包来替换原有的日志框架;
3、我们导入slf4j其他的实现
Springboot日志关系
pom依赖
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring‐boot‐starter‐logging</artifactId> </dependency>
底层依赖关系

SpringBoot能自动适配所有的日志,而且底层使用slf4j+logback的方式记录日志,引入其他框架的时候,只需要把这个框架依赖的日志框架排除掉即可;
日志的级别
trace < debug < info < warn < error
没设置的用root级别 info
logging.level.<package>=<级别>
logging.file=<无路径的话就是当前项目下文件名>
logging.path=<日志文件的目录>
文件名默认是spring.log
logging.file 和logging.path二选一, 都指定的话是logging.file起作用
logging.pattern.console
logging.pattern.file
springboot默认配置
默认使用INFO级别
指定日志文件和日志profile功能
logback
logback-spring.xml
不直接由日志框架加载,由spring加载,支持多环境
<springProfile name="dev"> <!‐‐ configuration to be enabled when the "dev" profile is active 可以指定某段配置只在某个环境下生效‐‐> </springProfile>
logback-spring.groovy
logback.xml
<?xml version="1.0" encoding="UTF-8"?> <!-- scan:当此属性设置为true时,配置文件如果发生改变,将会被重新加载,默认值为true。 scanPeriod:设置监测配置文件是否有修改的时间间隔,如果没有给出时间单位,默认单位是毫秒当scan为true时,此属性生效。默认的时间间隔为1分钟。 debug:当此属性设置为true时,将打印出logback内部日志信息,实时查看logback运行状态。默认值为false。 --> <configuration scan="false" scanPeriod="60 seconds" debug="false"> <!-- 定义日志的根目录 --> <property name="LOG_HOME" value="/app/log" /> <!-- 定义日志文件名称 --> <property name="appName" value="atguigu-springboot"></property> <!-- ch.qos.logback.core.ConsoleAppender 表示控制台输出 --> <appender name="stdout" class="ch.qos.logback.core.ConsoleAppender"> <!-- 日志输出格式: %d表示日期时间, %thread表示线程名, %-5level:级别从左显示5个字符宽度 %logger{50} 表示logger名字最长50个字符,否则按照句点分割。 %msg:日志消息, %n是换行符 --> <layout class="ch.qos.logback.classic.PatternLayout"> <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n</pattern> </layout> </appender> <!-- 滚动记录文件,先将日志记录到指定文件,当符合某个条件时,将日志记录到其他文件 --> <appender name="appLogAppender" class="ch.qos.logback.core.rolling.RollingFileAppender"> <!-- 指定日志文件的名称 --> <file>${LOG_HOME}/${appName}.log</file> <!-- 当发生滚动时,决定 RollingFileAppender 的行为,涉及文件移动和重命名 TimeBasedRollingPolicy: 最常用的滚动策略,它根据时间来制定滚动策略,既负责滚动也负责出发滚动。 --> <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy"> <!-- 滚动时产生的文件的存放位置及文件名称 %d{yyyy-MM-dd}:按天进行日志滚动 %i:当文件大小超过maxFileSize时,按照i进行文件滚动 --> <fileNamePattern>${LOG_HOME}/${appName}-%d{yyyy-MM-dd}-%i.log</fileNamePattern> <!-- 可选节点,控制保留的归档文件的最大数量,超出数量就删除旧文件。假设设置每天滚动, 且maxHistory是365,则只保存最近365天的文件,删除之前的旧文件。注意,删除旧文件是, 那些为了归档而创建的目录也会被删除。 --> <MaxHistory>365</MaxHistory> <!-- 当日志文件超过maxFileSize指定的大小是,根据上面提到的%i进行日志文件滚动 注意此处配置SizeBasedTriggeringPolicy是无法实现按文件大小进行滚动的,必须配置timeBasedFileNamingAndTriggeringPolicy --> <timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP"> <maxFileSize>100MB</maxFileSize> </timeBasedFileNamingAndTriggeringPolicy> </rollingPolicy> <!-- 日志输出格式: --> <layout class="ch.qos.logback.classic.PatternLayout"> <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [ %thread ] - [ %-5level ] [ %logger{50} : %line ] - %msg%n</pattern> </layout> </appender> <!-- logger主要用于存放日志对象,也可以定义日志类型、级别 name:表示匹配的logger类型前缀,也就是包的前半部分 level:要记录的日志级别,包括 TRACE < DEBUG < INFO < WARN < ERROR additivity:作用在于children-logger是否使用 rootLogger配置的appender进行输出, false:表示只用当前logger的appender-ref,true: 表示当前logger的appender-ref和rootLogger的appender-ref都有效 --> <!-- hibernate logger --> <logger name="com.atguigu" level="debug" /> <!-- Spring framework logger --> <logger name="org.springframework" level="debug" additivity="false"></logger> <!-- root与logger是父子关系,没有特别定义则默认为root,任何一个类只会和一个logger对应, 要么是定义的logger,要么是root,判断的关键在于找到这个logger,然后判断这个logger的appender和level。 --> <root level="info"> <appender-ref ref="stdout" /> <appender-ref ref="appLogAppender" /> </root> </configuration>
logback.groovy
log4j2
log4j2-spring.xml
log4j2.xml
log4j
log4j.properties
### set log levels ### log4j.rootLogger = debug , stdout , D , E ### 输出到控制台 ### log4j.appender.stdout = org.apache.log4j.ConsoleAppender log4j.appender.stdout.Target = System.out log4j.appender.stdout.layout = org.apache.log4j.PatternLayout log4j.appender.stdout.layout.ConversionPattern = %d{ABSOLUTE} %5p %c{ 1 }:%L - %m%n #### 输出到日志文件 ### #log4j.appender.D = org.apache.log4j.DailyRollingFileAppender #log4j.appender.D.File = logs/log.log #log4j.appender.D.Append = true #log4j.appender.D.Threshold = DEBUG ## 输出DEBUG级别以上的日志 #log4j.appender.D.layout = org.apache.log4j.PatternLayout #log4j.appender.D.layout.ConversionPattern = %-d{yyyy-MM-dd HH:mm:ss} [ %t:%r ] - [ %p ] %m%n # #### 保存异常信息到单独文件 ### #log4j.appender.D = org.apache.log4j.DailyRollingFileAppender #log4j.appender.D.File = logs/error.log ## 异常日志文件名 #log4j.appender.D.Append = true #log4j.appender.D.Threshold = ERROR ## 只输出ERROR级别以上的日志!!! #log4j.appender.D.layout = org.apache.log4j.PatternLayout #log4j.appender.D.layout.ConversionPattern = %-d{yyyy-MM-dd HH:mm:ss} [ %t:%r ] - [ %p ] %m%n
JDK JUL
logging.properties
切换日志框架
切换成log4j
按照slf4j的日志适配图,进行相关的切换
切换成log4j2
按照slf4j的日志适配图,进行相关的切换
修改pom
排除spring-boot-starter-logging -> spring-boot-starter-log4j2
4. Spring Boot与Web开发
5. Spring Boot与Docker
简介
是一个开源的应用容器引擎;是一个轻量级容器技术;
支持将软件编译成一个镜像;然后在镜像中各种软件做好配置,将镜像发布出去,其他使用者可以直接使用这个镜像;
运行中的这个镜像称为容器,容器启动是非常快速的。
核心概念

docker主机(Host):安装了Docker程序的机器(Docker直接安装在操作系统之上);
docker客户端(Client):连接docker主机进行操作;
docker仓库(Registry):用来保存各种打包好的软件镜像;
docker镜像(Images):软件打包好的镜像;放在docker仓库中;
docker容器(Container):镜像启动后的实例称为一个容器;容器是独立运行的一个或一组应用
linux环境准备
1. VMware、vitualBox
2. 导入虚拟机文件centos7
百度网盘中
3. 双击启动linux虚拟机
root/123456
4. 设置虚拟机网络
桥接网卡
选择本地网卡的类型(无线或者有线)
高级 -> 接入网线
service network restart
5. 查看ip地址
ip addr
6. 用ssh工具连接
docker安装、启动、停止
安装
查看centos版本,要求内核版本高于3.10
uname -r
升级内核
yum update
yum install docker -y
启动
systemctl start docker
docker -v
停止
systemctl stop docker
设置为开机启动
systemctl enable docker
常用命令
镜像操作
docker search xxx
检索镜像
docker pull xxx:tag
拉取镜像
docker images
查看所有本地镜像
docker rmi image-id
删除指定的本地镜像
容器操作
docker run ‐‐name mytomcat ‐d tomcat:latest
根据镜像启动容器
docker ps
查看运行中的容器
docker stop 容器的id
停止运行中的容器
docker ps ‐a
查看所有的容器
docker start 容器id
启动容器
docker rm 容器id
删除一个容器
端口映射的tomcat
docker run ‐d ‐p 8888:8080 tomcat
docker logs container‐name/container‐id
查看容器的日志
安装mysql
1. docker pull mysql
2.docker run ‐‐name mysql01 ‐p 3306:3306 ‐e MYSQL_ROOT_PASSWORD=123456 ‐d mysql
实际使用仍需参照官方文档
6. Spring Boot与数据访问
简介
Spring Data
原生JDBC
<dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <scope>runtime</scope> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-jdbc</artifactId> </dependency>
spring: datasource: username: root password: 123456 url: jdbc:mysql://<ip>:<port>/<database-name> driver-class-name: com.mysql.cj.jdbc.Driver
默认是用org.apache.tomcat.jdbc.pool.DataSource作为数据源
数据源的相关配置都在DataSourceProperties里面
List<Map<String, Object>> list = jdbcTemplate.queryForList(sql);
自动配置原理
org.springframework.boot.autoconfigure.jdbc
1、参考DataSourceConfiguration,根据配置创建数据源,默认使用Tomcat连接池;可以使用spring.datasource.type指定自定义的数据源类型;
2、SpringBoot默认可以支持;
org.apache.tomcat.jdbc.pool.DataSource、HikariDataSource、BasicDataSource
3、自定义数据源类型
/** * Generic DataSource configuration. */ @ConditionalOnMissingBean(DataSource.class) @ConditionalOnProperty(name = "spring.datasource.type") static class Generic { @Bean public DataSource dataSource(DataSourceProperties properties) { //使用DataSourceBuilder创建数据源,利用反射创建响应type的数据源,并且绑定相关属性 return properties.initializeDataSourceBuilder().build(); } }
spring.datasource.type
4、DataSourceInitializer:ApplicationListener;
1)、runSchemaScripts();运行建表语句;
2)、runDataScripts();运行插入数据的sql语句
默认只需要将文件命名为:schema‐*.sql、data‐*.sql
spring: datasource: schema: - classpath:department.sql
5、操作数据库:自动配置了JdbcTemplate操作数据库
整合Druid
pom.xml
<dependency> <groupId>com.alibaba</groupId> <artifactId>druid</artifactId> <version>1.1.22</version> </dependency>
spring.datasource.type=com.alibaba.druid.pool.DruidDataSource
其他的配置
spring: datasource: # 数据源基本配置 username: root password: 123456 driver-class-name: com.mysql.jdbc.Driver url: jdbc:mysql://localhost:3306/ssm_crud type: com.alibaba.druid.pool.DruidDataSource # 数据源其他配置 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,去掉后监控界面sql无法统计,'wall'用于防火墙 filters: stat,wall,log4j maxPoolPreparedStatementPerConnectionSize: 20 useGlobalDataSourceStat: true connectionProperties: druid.stat.mergeSql=true;druid.stat.slowSqlMillis=500
因为DataSource没有那么多属性,多的属性配置需要我们自定义Druid bean
import com.alibaba.druid.pool.DruidDataSource; import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import javax.sql.DataSource; @Configuration public class DruidConfig { @Bean @ConfigurationProperties(prefix = "spring.datasource") public DataSource druid() { return new DruidDataSource(); } }
配置数据源监控
@Configuration public class DruidConfig { @ConfigurationProperties(prefix = "spring.datasource") @Bean public DataSource druid() { return new DruidDataSource(); } //配置Druid的监控 //1、配置一个管理后台的Servlet @Bean public ServletRegistrationBean statViewServlet() { ServletRegistrationBean bean = new ServletRegistrationBean( new StatViewServlet(), "/druid/*" ); Map<String, String> initParams = new HashMap<>(); initParams.put("loginUsername", "admin"); initParams.put("loginPassword", "123456"); initParams.put("allow", ""); //默认就是允许所有访问 initParams.put("deny", "192.168.15.21"); bean.setInitParameters(initParams); return bean; } //2、配置一个web监控的filter @Bean public FilterRegistrationBean webStatFilter() { FilterRegistrationBean bean = new FilterRegistrationBean(); bean.setFilter(new WebStatFilter()); Map<String, String> initParams = new HashMap<>(); initParams.put("exclusions", "*.js,*.css,/druid/*"); bean.setInitParameters(initParams); bean.setUrlPatterns(Arrays.asList("/*")); return bean; } }
整合MyBatis
基础环境搭建
<dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <scope>runtime</scope> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-jdbc</artifactId> </dependency> <dependency> <groupId>org.mybatis.spring.boot</groupId> <artifactId>mybatis-spring-boot-starter</artifactId> <version>1.3.2</version> </dependency>
Druid数据源配置
数据库建表
创建对应的JavaBean
注解版MyBatis
@Mapper public interface DepartmentMapper { @Select("select * from department where id=#{id}") public Department getDeptById(Integer id); @Delete("delete from department where id=#{id}") public int deleteDeptById(Integer id); @Options(useGeneratedKeys = true, keyProperty = "id") @Insert("insert into department(departmentName) values (#{departmentName})") public int insertDept(Department department); @Update("update department set departmentName=#{departmentName} where id=#{id}") public int updateDept(Integer id); }
使短杠和驼峰对应生效
自定义MyBatis的配置规则;给容器中添加一个ConfigurationCustomizer;
@org.springframework.context.annotation.Configuration public class MyBatisConfig { @Bean public ConfigurationCustomizer configurationCustomizer() { return new ConfigurationCustomizer() { @Override public void customize(Configuration configuration) { configuration.setMapUnderscoreToCamelCase(true); } }; } }
如果不标注@Mapper, 则使用@MapperScan(value = "com.atguigu.springboot.mapper")
配置版MyBatis
public interface EmployeeMapper { public Employee getEmpById(Integer id); public void insertEmp(Employee emp); }
配置文件
建目录
resources/mybatis/mapper/*
xml文件
<?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd"> <mapper namespace="<mapper接口的全路径>"> <select id="<方法名>" resultType="<返回参数类型>"> select * from Blog where id = #{id} </select> </mapper>
配置文件
resource/mybatis/mybatis-config.xml
<?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0//EN" "http://mybatis.org/dtd/mybatis-3-config.dtd"> <configuration> <!-- <environments default="development"> <environment id="development"> <transactionManager type="JDBC"/> <dataSource type="POOLED"> <property name="driver" value="${driver}"/> <property name="url" value="${url}"/> <property name="username" value="${username}"/> <property name="password" value="${password}"/> </dataSource> </environment> </environments> <mappers> <mapper resource="org/mybatis/example/BlogMapper.xml"/> </mappers> --> <settings> <setting name="mapUnderscoreToCamelCase" value="true" /> </settings> </configuration>
application.yml
mybatis: config-location: classpath:mybatis/mybatis-config.xml mapper-location: classpath:mybatis/mapper/*.xml
注解版和配置版可以混合使用
整合SpringData JPA
简介

底层使用Hibernate
创建项目
pom.xml
<dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <scope>runtime</scope> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-jdbc</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-jpa</artifactId> </dependency>
applicationo.yml
spring: datasource: url: jdbc:mysql://<ip>/<database> username: root password: 123456 driver-class-name: com.mysql.cj.jdbc.Driver jpa: hibernate: ddl-auto: update // 更新或新建表结构 show-sql: true
org.springframework.boot.autoconfigure.orm.jpa.JpaProperties
实体类和映射关系
@Entity
@Table(name = "")
如果省略,默认类名小写就是表名
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)// 自增主键
TABLE:使用一个特定的数据库表格来保存主键。
SEQUENCE:根据底层数据库的序列来生成主键,条件是数据库支持序列。
IDENTITY:主键由数据库自动生成(主要是自动增长型)
AUTO:主键由程序控制。
@Column(name = "", length = 50)
默认列名是属性名
创建Dao层(Repository类)
public interface UserRepository extends<User, Integer> {}
7. Spring Boot启动配置原理
启动流程
SpringBoot 1.5.9
1、创建SpringApplication对象
initialize(sources);
private void initialize(Object[]sources) { //保存主配置类 if (sources != null && sources.length > 0) { this.sources.addAll(Arrays.asList(sources)); } //判断当前是否一个web应用 this.webEnvironment = deduceWebEnvironment(); //从类路径下找到META‐INF/spring.factories配置的所有ApplicationContextInitializer;然后保存起来 setInitializers((Collection)getSpringFactoriesInstances( ApplicationContextInitializer.class)); //从类路径下找到ETA‐INF/spring.factories配置的所有ApplicationListener setListeners((Collection)getSpringFactoriesInstances(ApplicationListener.class)); //从多个配置类中找到有main方法的主配置类 this.mainApplicationClass = deduceMainApplicationClass(); }
2.run
public ConfigurableApplicationContext run(String...args) { StopWatch stopWatch = new StopWatch(); stopWatch.start(); ConfigurableApplicationContext context = null; FailureAnalyzers analyzers = null; configureHeadlessProperty(); //获取SpringApplicationRunListeners;从类路径下META‐INF/spring.factories SpringApplicationRunListeners listeners = getRunListeners(args); //回调所有的获取SpringApplicationRunListener.starting()方法 listeners.starting(); try { //封装命令行参数 ApplicationArguments applicationArguments = new DefaultApplicationArguments(args); //准备环境 创建IOC容器运行需要的环境 ConfigurableEnvironment environment = prepareEnvironment(listeners, applicationArguments); //创建环境完成后回调SpringApplicationRunListener.environmentPrepared();表示环境准备完成 Banner printedBanner = printBanner(environment); //创建ApplicationContext;决定创建web的ioc还是普通的ioc context = createApplicationContext(); analyzers = new FailureAnalyzers(context); //准备上下文环境;将environment保存到ioc中;而且applyInitializers(); //applyInitializers():回调之前保存的所有的ApplicationContextInitializer的initialize方法 //回调所有的SpringApplicationRunListener的contextPrepared(); prepareContext(context, environment, listeners, applicationArguments, printedBanner); //prepareContext运行完成以后回调所有的SpringApplicationRunListener的contextLoaded(); //刷新容器;ioc容器初始化(如果是web应用还会创建嵌入式的Tomcat);Spring注解版 //扫描,创建,加载所有组件的地方;(配置类,组件,自动配置) refreshContext(context); //从ioc容器中获取所有的ApplicationRunner和CommandLineRunner进行回调 //ApplicationRunner先回调,CommandLineRunner再回调 afterRefresh(context, applicationArguments); //所有的SpringApplicationRunListener回调finished方法 listeners.finished(context, null); stopWatch.stop(); if (this.logStartupInfo) { new StartupInfoLogger(this.mainApplicationClass) .logStarted(getApplicationLog(), stopWatch); } //整个SpringBoot应用启动完成以后返回启动的ioc容器; return context; } catch (Throwable ex) { handleRunFailure(context, listeners, analyzers, ex); throw new IllegalStateException(ex); } }
SpringBoot 2.5.2

1、创建SpringApplication对象
1. public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) { ... this.webApplicationType = WebApplicationType.deduceFromClasspath(); this.bootstrapRegistryInitializers = this.getBootstrapRegistryInitializersFromSpringFactories(); this.setInitializers(this.getSpringFactoriesInstances(ApplicationContextInitializer.class)); this.setListeners(this.getSpringFactoriesInstances(ApplicationListener.class)); this.mainApplicationClass = this.deduceMainApplicationClass(); .. };
推断Web应用类型
static WebApplicationType deduceFromClasspath() { if (ClassUtils.isPresent("org.springframework.web.reactive.DispatcherHandler", (ClassLoader)null) && !ClassUtils.isPresent("org.springframework.web.servlet.DispatcherServlet", (ClassLoader)null) && !ClassUtils.isPresent("org.glassfish.jersey.servlet.ServletContainer", (ClassLoader)null)) { return REACTIVE; } else { String[] var0 = SERVLET_INDICATOR_CLASSES; int var1 = var0.length; for(int var2 = 0; var2 < var1; ++var2) { String className = var0[var2]; if (!ClassUtils.isPresent(className, (ClassLoader)null)) { return NONE; } } return SERVLET; } }
Web Reactive:WebApplicationType.REACTIVE
Web Servlet: WebApplicationType.SERVLET
非Web:WebApplicationType.NONE
配置primarySources
配置环境是否为web环境
创建初始化构造器setInitializers
创建应用监听器
配置应用主方法所在类(就是main方法所在类)
2. run()
代码
/** * 运行spring应用程序,创建并刷新一个新的 {@link ApplicationContext}. * * @param args the application arguments (usually passed from a Java main method) * @return a running {@link ApplicationContext} */ public ConfigurableApplicationContext run(String... args) { // 计时工具 StopWatch stopWatch = new StopWatch(); stopWatch.start(); // 创建启动上下文对象 DefaultBootstrapContext bootstrapContext = createBootstrapContext(); ConfigurableApplicationContext context = null; configureHeadlessProperty(); // 第一步:获取并启动监听器 SpringApplicationRunListeners listeners = getRunListeners(args); listeners.starting(bootstrapContext, this.mainApplicationClass); try { ApplicationArguments applicationArguments = new DefaultApplicationArguments(args); // 第二步:准备环境 ConfigurableEnvironment environment = prepareEnvironment(listeners, bootstrapContext, applicationArguments); configureIgnoreBeanInfo(environment); // 第三步:打印banner,就是启动的时候在console的spring图案 Banner printedBanner = printBanner(environment); // 第四步:创建spring容器 context = createApplicationContext(); context.setApplicationStartup(this.applicationStartup); // 第五步:spring容器前置处理 prepareContext(bootstrapContext, context, environment, listeners, applicationArguments, printedBanner); // 第六步:刷新容器 refreshContext(context); // 第七步:spring容器后置处理 afterRefresh(context, applicationArguments); stopWatch.stop(); // 结束计时器并打印,这就是我们启动后console的显示的时间 if (this.logStartupInfo) { new StartupInfoLogger(this.mainApplicationClass).logStarted(getApplicationLog(), stopWatch); } // 发出启动结束事件 listeners.started(context); // 执行runner的run方法 callRunners(context, applicationArguments); } catch (Throwable ex) { // 异常处理,如果run过程发生异常 handleRunFailure(context, ex, listeners); throw new IllegalStateException(ex); } try { listeners.running(context); } catch (Throwable ex) { // 异常处理,如果run过程发生异常 handleRunFailure(context, ex, null); throw new IllegalStateException(ex); } // 返回最终构建的容器对象 return context; }
启动一个计时器,启动完成后会打印耗时
获取并启动监听器 SpringApplicationRunListeners
启动监听就是我们需要监听SpringBoot的启动流程监听,实现SpringApplicationRunListener类即可监听
public interface SpringApplicationRunListener { /** * 当调用run方法后会立即调用,可以用于非常早期的初始化 */ default void starting(ConfigurableBootstrapContext bootstrapContext) { starting(); } /** * 环境准备好之后调用 */ default void environmentPrepared(ConfigurableBootstrapContext bootstrapContext, ConfigurableEnvironment environment) { environmentPrepared(environment); } /** * 在加载资源之前,ApplicationContex准备好之后调用 */ default void contextPrepared(ConfigurableApplicationContext context) { } /** * 在加载应用程序上下文但在其刷新之前调用 */ default void contextLoaded(ConfigurableApplicationContext context) { } /** * 上下文已经刷新且应用程序已启动且所有{@link CommandLineRunner commandLineRunner} * 和{@link ApplicationRunner ApplicationRunners}未调用之前调用 */ default void started(ConfigurableApplicationContext context) { } /** * 当应用程序上下文被刷新并且所有{@link CommandLineRunner commandLineRunner} * 和{@link ApplicationRunner ApplicationRunners}都已被调用时,在run方法结束之前立即调用。 */ default void running(ConfigurableApplicationContext context) { } /** * 在启动过程发生失败时调用 */ default void failed(ConfigurableApplicationContext context, Throwable exception) { } }
配置环境 ConfigurableEnvironment
/** * 创建并配置SpringBooty应用j将要使用的Environment * * @param listeners * @param bootstrapContext * @param applicationArguments * @return */ private ConfigurableEnvironment prepareEnvironment(SpringApplicationRunListeners listeners, DefaultBootstrapContext bootstrapContext, ApplicationArguments applicationArguments) { // 根据不同的web类型创建不同实现的Environment对象 ConfigurableEnvironment environment = getOrCreateEnvironment(); // 配置环境 configureEnvironment(environment, applicationArguments.getSourceArgs()); ConfigurationPropertySources.attach(environment); // 发送环境已准备完成事件 listeners.environmentPrepared(bootstrapContext, environment); DefaultPropertiesPropertySource.moveToEnd(environment); // 根据命令行参数中spring.profiles.active属性配置Environment对象中的activeProfile(比如dev、prod、test) configureAdditionalProfiles(environment); // 绑定环境中spring.main属性绑定到SpringApplication对象中 bindToSpringApplication(environment); // 如果用户使用spring.main.web-application-type属性手动设置了webApplicationType if (!this.isCustomEnvironment) { // 将环境对象转换成用户设置的webApplicationType相关类型,他们是继承同一个父类,直接强转 environment = new EnvironmentConverter(getClassLoader()).convertEnvironmentIfNecessary(environment, deduceEnvironmentClass()); } ConfigurationPropertySources.attach(environment); return environment; }
1. 创建配置环境 ConfigurableEnvironment
2. 加载属性文件资源
3. 配置监听
Banner配置,就是控制台的那个spirng
/** * 打印banner * * @param environment * @return */ private Banner printBanner(ConfigurableEnvironment environment) { // banner模式,可以是console、log、off if (this.bannerMode == Banner.Mode.OFF) { return null; } ResourceLoader resourceLoader = (this.resourceLoader != null) ? this.resourceLoader : new DefaultResourceLoader(null); SpringApplicationBannerPrinter bannerPrinter = new SpringApplicationBannerPrinter(resourceLoader, this.banner); if (this.bannerMode == Mode.LOG) { return bannerPrinter.print(environment, this.mainApplicationClass, logger); } return bannerPrinter.print(environment, this.mainApplicationClass, System.out); }
基本就是依据不同情况打印banner
创建spring容器
最终获取到ConfigurableApplicationContext上下文对象
应用上下文模块前置处理
/** * Spring容器准备 */ private void prepareContext(DefaultBootstrapContext bootstrapContext, ConfigurableApplicationContext context, ConfigurableEnvironment environment, SpringApplicationRunListeners listeners, ApplicationArguments applicationArguments, Banner printedBanner) { // 设置上下文环境 context.setEnvironment(environment); // postProcessApplicationContext(context); // 执行所有ApplicationContextInitializer对象的initialize方法(这些对象是通过读取spring.factories加载) applyInitializers(context); // 发布上下文准备完成事件到所有监听器 listeners.contextPrepared(context); bootstrapContext.close(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()); } // Load the sources Set<Object> sources = getAllSources(); Assert.notEmpty(sources, "Sources must not be empty"); // 加载bean到上下文 load(context, sources.toArray(new Object[0])); // 发送上下文加载完成事件 listeners.contextLoaded(context); }
应用上下文模块刷新
/** * 刷新应用程序上下文 * * @param context */ private void refreshContext(ConfigurableApplicationContext context) { // 注册一个关闭钩子,在jvm停止时会触发,然后退出时执行一定的退出逻辑 if (this.registerShutdownHook) { try { // 添加:Runtime.getRuntime().addShutdownHook() // 移除:Runtime.getRuntime().removeShutdownHook(this.shutdownHook) context.registerShutdownHook(); } catch (AccessControlException ex) { // Not allowed in some environments. } } // ApplicationContext真正开始初始化容器和创建bean的阶段 refresh((ApplicationContext) context); } protected void refresh(ApplicationContext applicationContext) { Assert.isInstanceOf(ConfigurableApplicationContext.class, applicationContext); refresh((ConfigurableApplicationContext) applicationContext); } protected void refresh(ConfigurableApplicationContext applicationContext) { applicationContext.refresh(); }
关键
调用应用上下文对象的refresh()方法,接下来我i门到ConfigurableApplicationContext类中去看下这个方法
这是一个接口,且这个类是在spring框架中
它的实现类共有三个
AbstractApplicationContext
@Override public void refresh() throws BeansException, IllegalStateException { synchronized (this.startupShutdownMonitor) { StartupStep contextRefresh = this.applicationStartup.start("spring.context.refresh"); // 第一步:准备更新上下时的预备工作 prepareRefresh(); // 第二步:获取上下文内部BeanFactory ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory(); // 第三步:对BeanFactory做预备工作 prepareBeanFactory(beanFactory); try { // 第四步:允许在上下文子类中对bean工厂进行post-processing postProcessBeanFactory(beanFactory); StartupStep beanPostProcess = this.applicationStartup.start("spring.context.beans.post-process"); // 第五步:调用上下文中注册为bean的工厂 BeanFactoryPostProcessor invokeBeanFactoryPostProcessors(beanFactory); // 第六步:注册拦截bean创建的拦截器 registerBeanPostProcessors(beanFactory); beanPostProcess.end(); // 第七步:初始化MessageSource(国际化相关) initMessageSource(); // 第八步:初始化容器事件广播器(用来发布事件) initApplicationEventMulticaster(); // 第九步:初始化一些特殊的bean onRefresh(); // 第十步:将所有监听器注册到前两步创建的事件广播器中 registerListeners(); // 第十一步:结束bean的初始化工作(主要将所有单例BeanDefinition实例化) finishBeanFactoryInitialization(beanFactory); // 第十二步:afterRefresh(上下文刷新完毕,发布相应事件) finishRefresh(); } catch (BeansException ex) { if (logger.isWarnEnabled()) { logger.warn("Exception encountered during context initialization - " + "cancelling refresh attempt: " + ex); } // Destroy already created singletons to avoid dangling resources. destroyBeans(); // Reset 'active' flag. cancelRefresh(ex); // Propagate exception to caller. throw ex; } finally { // Reset common introspection caches in Spring's core, since we // might not ever need metadata for singleton beans anymore... resetCommonCaches(); contextRefresh.end(); } } }
ServletWebServerApplicationContext
@Override public final void refresh() throws BeansException, IllegalStateException { try { super.refresh(); } catch (RuntimeException ex) { WebServer webServer = this.webServer; if (webServer != null) { webServer.stop(); } throw ex; } }
应用上下文模块后置处理
protected void afterRefresh(ConfigurableApplicationContext context, ApplicationArguments args) { }
发出启动结束事件并结束计时
run方法异常处理
private void handleRunFailure(ConfigurableApplicationContext context, Throwable exception, SpringApplicationRunListeners listeners) { try { try { handleExitCode(context, exception); if (listeners != null) { listeners.failed(context, exception); } } finally { reportFailure(getExceptionReporters(context), exception); if (context != null) { context.close(); } } } catch (Exception ex) { logger.warn("Unable to close ApplicationContext", ex); } ReflectionUtils.rethrowRuntimeException(exception); }
脑图

事件监听机制相关测试
几个重要的事件回调机制
配置在META-INF/spring.factories
ApplicationContextInitializer
SpringApplicationRunListener
只需要放在ioc容器中
ApplicationRunner
CommandLineRunne
测试
resources/META-INF/spring.factories
org.springframework.context.ApplicationContextInitializer=\ <HelloApplicationContextInitializer的全路径> org.springframework.boot.SpringApplicationRunListener=\ <HelloSpringApplicationRunListener的全路径>
HelloApplicationContextInitializer
public class HelloApplicationContextInitializer implements ApplicationContextInitializer<ConfigurableApplicationContext> { @Override public void initialize(ConfigurableApplicationContext applicationContext) { System.out.println( "ApplicationContextInitializer...initialize..." + applicationContext ); } }
HelloSpringApplicationRunListener
public class HelloSpringApplicationRunListener implements SpringApplicationRunListener { //必须有的构造器 public HelloSpringApplicationRunListener( SpringApplication application, String[] args ) {} @Override public void starting() { System.out.println("SpringApplicationRunListener...starting..."); } @Override public void environmentPrepared(ConfigurableEnvironment environment) { Object o = environment.getSystemProperties().get("os.name"); System.out.println( "SpringApplicationRunListener...environmentPrepared.." + o ); } @Override public void contextPrepared(ConfigurableApplicationContext context) { System.out.println("SpringApplicationRunListener...contextPrepared..."); } @Override public void contextLoaded(ConfigurableApplicationContext context) { System.out.println("SpringApplicationRunListener...contextLoaded..."); } @Override public void finished( ConfigurableApplicationContext context, Throwable exception ) { System.out.println("SpringApplicationRunListener...finished..."); } }
只需要放在ioc容器中@Component
HelloApplicationRunner
@Component public class HelloApplicationRunner implements ApplicationRunner { @Override public void run(ApplicationArguments args) throws Exception { System.out.println("ApplicationRunner...run...."); } }
HelloCommandLineRunner
@Component public class HelloCommandLineRunner implements CommandLineRunner { @Override public void run(String... args) throws Exception { System.out.println("CommandLineRunner...run..." + Arrays.asList(args)); } }
8. Spring Boot自定义starters
starter场景启动器
1、这个场景需要使用到的依赖是什么?
2、如何编写自动配置
WebMvcAutoConfiguration为例
@Configuration //指定这个类是一个配置类
@ConditionalOnXXX //在指定条件成立的情况下自动配置类生效
@AutoConfigureAfter //指定自动配置类的顺序
@Bean //给容器中添加组件
@EnableConfigurationProperties //让xxxProperties生效加入到容器中自动配置类要能加载
@ConfigurationPropertie结合相关xxxProperties类来绑定相关的配置
将需要启动就加载的自动配置类,配置在META‐INF/spring.factories
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
org.springframework.boot.autoconfigure.admin.SpringApplicationAdminJmxAutoConfiguration,\
org.springframework.boot.autoconfigure.aop.AopAutoConfiguration,\
3. 模式
启动器只用来做依赖导入;
专门来写一个自动配置模块;
启动器依赖自动配置;别人只需要引入启动器(starter)
mybatis-spring-boot-starter;自定义启动器名-spring-boot-starter
4. 实践
new project -> empty project
Project Structure -> Modules -> new Moduel -> Maven -> groupId, artifactId(starter), version
Project Structure -> Modules -> new Moduel -> Spring Initializer -> groupId, artifactId(starter-autoconfiguration), version
starter
pom.xml
<dependency> starter-autoconfiguration </dependency>
starter-autoconfiguration
去掉resource下所有文件
删除主程序
删除test文件夹
pom.xml
去掉springboot-test
去掉maven插件
只保留springboot-starter
HelloProperties
import org.springframework.boot.context.properties.ConfigurationProperties; @ConfigurationProperties(prefix = "atguigu.hello") public class HelloProperties { private String prefix; private String suffix; public String getPrefix() { return prefix; } public void setPrefix(String prefix) { this.prefix = prefix; } public String getSuffix() { return suffix; } public void setSuffix(String suffix) { this.suffix = suffix; } }
HelloService
public class HelloService { HelloProperties helloProperties; public HelloProperties getHelloProperties() { return helloProperties; } public void setHelloProperties(HelloProperties helloProperties) { this.helloProperties = helloProperties; } public String sayHellAtguigu(String name) { return ( helloProperties.getPrefix() + "‐" + name + helloProperties.getSuffix() ); } }
HelloServiceAutoConfiguration
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication; import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @Configuration @ConditionalOnWebApplication //web应用才生效 @EnableConfigurationProperties(HelloProperties.class) public class HelloServiceAutoConfiguration { @Autowired HelloProperties helloProperties; @Bean public HelloService helloService() { HelloService service = new HelloService(); service.setHelloProperties(helloProperties); return service; } }
resources/META‐INF/spring.factories
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\ <HelloServiceAutoConfiguration的全路径>
9-16. 见 《9-16. SpringBoot整合篇》
浮动主题
service firewalld status ;查看防火墙状态
service firewalld stop:关闭防火墙
项目文件
文件都可以删除
.mvn
.gitignore
mvnw
mvnw.cmd
打包
设置MAVEN
使IDE自动按照我们想要的版本编译
<profile> <id>jdk-1.8</id> <activation> <jdk>1.8</jdk> <activeByDefault>true</activeByDefault> </activation> <properties> <maven.compiler.source>1.8</maven.compiler.source> <maven.compiler.target>1.8</maven.compiler.target> <maven.compiler.compilerVersion>1.8</maven.compiler.compilerVersion> </properties> </profile>
打jar包时将依赖一起打进来
<build> <finalName>${project.name}</finalName> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> <configuration> <executable>true</executable> </configuration> </plugin> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-surefire-plugin</artifactId> <version>2.5</version> <configuration> <skipTests>true</skipTests> </configuration> </plugin> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-compiler-plugin</artifactId> <version>3.8.1</version> <configuration> <source>8</source> <target>8</target> </configuration> </plugin> </plugins> </build>
官方文档
版本
GA
General Availability,正式发布的版本,官方开始推荐广泛使用,国外有的用GA来表示release版本。
RELEASE
正式发布版,官方推荐使用的版本,有的用GA来表示。比如spring。
Stable
稳定版,开源软件有的会用stable来表示正式发布的版本。比如Nginx。
Final
最终版,也是正式发布版的一种表示方法。比如Hibernate。
RC
Release Candidate,发行候选版本,基本不再加入新的功能,主要修复bug。是最终发布成正式版的前一个版本,将bug修改完就可以发布成正式版了。
alpha
α是希腊字母的第一个,表示最早的版本,内部测试版,一般不向外部发布,bug会比较多,功能也不全,一般只有测试人员使用。
Beta
β是希腊字母的第二个,公开测试版,比alpha版本晚些,主要会有“粉丝用户”测试使用,该版本仍然存在很多bug,但比alpha版本稳定一些。这个阶段版本还会不断增加新功能。分为Beta1、Beta2等,直到逐渐稳定下来进入RC版本。
番外篇
授权和功能划分:
Trial:试用版,通常都有时间限制,有些试用版软件还在功能上做了一定的限制。可注册或购买成为正式版
Unregistered:未注册版,通常没有时间限制,在功能上相对于正式版做了一定的限制。可注册或购买成为正式版。
Demo:演示版,仅仅集成了正式版中的几个功能,不能升级成正式版。
Lite:精简版。
Full version:完整版,属于正式版。
其他版本
Enhance :增强版或者加强版 属于正式版1
Free :自由版
Release :发行版 有时间限制
Upgrade :升级版
Retail :零售版
Cardware :属共享软件的一种,只要给作者回复一封电邮或明信片即可。(有的作者并由此提供注册码等),目前这种形式已不多见。/ S
Plus :属增强版,不过这种大部分是在程序界面及多媒体功能上增强。
Preview :预览版
Corporation & Enterprise :企业版
Standard :标准版
Mini :迷你版也叫精简版只有最基本的功能
Premium : 贵价版
Professional(Pro) : 专业版
Express : 特别版
Deluxe : 豪华版
Regged : 已注册版
Build:内部标号
Delux:豪华版 (deluxe: 豪华的,华丽的)
DEMO演示版,一般会有功能限制
Full:完全版
Plus:加强版
Trial:试用版(一般有时间或者功能限制)
28