文章内容输出来源:拉钩教育Java高薪训练营
SpringBoot
基础回顾
概念
-
快速的使用spring的一种方式,并且不需要编写大量的配置文件。基于约定优于配置的思想。
-
Spring优缺点
- 优点:J2EE的轻量级代替品,IoC,AOP
- 缺点:配置繁琐;依赖管理复杂
-
SpringBoot
- 起步依赖:将具备某种功能的坐标打包到一起,并提供一些默认的功能。
- 自动配置:自动将一些配置类注册进ioc容器,需要时直接使用@autowired或者@resource注解。
案例实现
包扫描:项目主程序启动类所在包及其子包
单元测试与热部署
-
添加
spring-boot-starter-test测试依赖启动器(Spring Initializer方式会自动加入)<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency>@RunWith(SpringRunner.class) // 测试启动器,并加载spring boot测试注解 @SpringBootTest // 标记为spring boot单元测试类,并加载项目上下文环境 class Springboot01DemoApplicationTests { @Autowired private HelloController helloController; @Test void contextLoads() { String demo = helloController.demo(); System.out.println(demo); } } -
添加
spring-boot-devtools热部署依赖启动器<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-devtools</artifactId> </dependency>
全局配置文件
application.properties
-
对一些默认值进行修改,例如系统属性、环境变量、命令参数等等
server.port=8081 -
自定义配置属性注入到实体类
-
添加
spring-boot-configuration-processor配置处理依赖器<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-configuration-processor</artifactId> <optional>true</optional> </dependency>
-
application.yaml
-
JSON超集文件格式,更简洁
-
扩展名可以为
.yml或者.yaml -
key: value(中间有空格)格式,使用缩进来控制层级关系 -
示例
server: port: 8081 path: /hello person: hobby: [play,read,sleep] map: k1: v1 k2: v2 -
application.properties配置文件会覆盖(优先级高于)application.yaml配置文件
配置文件属性值的注入
-
@ConfigurationProperties
将配置文件中的自定义属性值批量注入到某个Bean对象的多个对应属性中。(本质是使用set方法)
@Component @ConfigurationProperties(prefix = "person") // 将配置文件中以person开头的属性注入到 public class Person { private int id; private String name; // 省略其他属性 // 省略get/set方法 } -
@Value
读取配置文件中的属性值并逐个注入到Bean对象的对应属性中。无需编写set方法!
不支持:Map集合、对象以及yaml文件格式的行内式写法
@Component public class Student { @Value("3") private int id; @Value("${person.name}") private String name; }
自定义配置
@PropertySource加载配置文件
@Component
@PropertySource("classpath:test.properties")
@ConfigurationProperties(prefix = "test")
public class MyProperties {
private int id;
private String name;
// 省略
}
@Configuration编写自定义配置类
// 配置类
@Configuration
public class MyConfig {
// 返回值添加到容器中,id默认为方法名
@Bean("service")
public MyService myService() {
return new MyService();
}
}
// spring容器实例
@Autowired
private ApplicationContext context;
@Test
void iocTest() {
System.out.println(context.containsBean("service"));
}
随机数设置及参数间引用
随机值设置
利用Spring Boot内嵌的RandomValuePropertySource类,对属性值进行随机值注入
# 随机值
my.secret=${random.value}
# 随机整数
my.number=${random.int}
# 随机long类型数
my.bignumber=${random.long}
# 随机uuid类型数
my.uuid=${random.uuid}
# 小于10的随机整数
my.ten=${random.int(10)}
# 范围在[]之间的随机整数
my.range=${random.int[1024,65535]}
参数间引用
后配置的属性值直接引用前面以及定义过的值,抽取思想
app.name=MyApp
app.description=${app.name} is a Spring Boot application
原理深入及源码分析
依赖管理
-
为什么导入dependency时不需要指定版本?
-
pom文件的继承关系:当前工程 <--
spring-boot-starter-parent-2.3.4.RELEASE.pom<--spring-boot-dependencies-2.3.4.RELEASE.pom -
在
spring-boot-dependencies-2.3.4.RELEASE.pom中统一进行了版本管理<properties> <activemq.version>5.15.13</activemq.version> </properties> <dependencyManagement> <dependencies> <dependency> <groupId>org.apache.activemq</groupId> <artifactId>activemq-amqp</artifactId> <version>${activemq.version}</version> </dependency> </dependencies> </dependencyManagement>
-
-
项目运行依赖的jar包从何而来?
-
spring-boot-starter-web依赖启动器提供了Web开发场景所需的底层依赖<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> -
其他开发场景的依赖启动器,可以查询Spring Boot官网搜索Starters
-
自动配置(启动流程)
@SpringBootApplication
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@SpringBootConfiguration // 等同于@Configuartion
@EnableAutoConfiguration // 见下
@ComponentScan(
excludeFilters = {@Filter(
type = FilterType.CUSTOM,
classes = {TypeExcludeFilter.class}
), @Filter(
type = FilterType.CUSTOM,
classes = {AutoConfigurationExcludeFilter.class}
)}
)
public @interface SpringBootApplication {
// 省略
}
@EnableAutoConfiguration
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage // 扫描主程序类所在包及其子包
// @Import是Spring框架的底层类,给容器中导入某个组件类
@Import({AutoConfigurationImportSelector.class})
public @interface EnableAutoConfiguration {
// 省略
}
@AutoConfigurationPackage
@Import(AutoConfigurationPackages.Registrar.class),扫描主配置类同级目录及子包,注册相应组件进IoC容器
@Import(AutoConfigurationImportSelector.class)
帮助springboot应用将所有符合条件的@Configuration配置都加载到当前SpringBoot的IoC容器中。通过selectImports方法,使用内部工具类SpringFactoriesLoader,查找classpath上所有jar包中的META-INF/spring.factories进行加载,实现将配置类信息交给SpringFactory加载器进行一系列的容器创建过程。
AutoConfigurationImportSelector#selectImports
|
loadMetadata // <- spring-autoconfigure-metadata.properties
|
getCandidateConfigurations
|
SpringFactoriesLoader.loadFactoryNames // <- spring.factories
|
将配置项通过反射实例化为对应标注了@Configuration的JavaConfig形式的配置类,并加载到IoC容器
根据条件进行筛选
@ConditionalOnClass//要求某个class位于类路径上
@ConditionalOnMissingClass//要求某个class不存在于类路径上
@ConditionalOnBean//DI容器中存在该类型Bean
@ConditionalOnMissingBean//DI容器中不存在该类型的Bean
@ConditionalOnSingleCandidate//DI容器中该类型Bean只有一个或@Primary的只有一个
@ConditionalOnExpression//表达式结果为true时
@ConditionalOnProperty//参数设置或值一致时
@ConditionalOnResource//指定的文件存在时
@ConditionalOnJndi//指定的JNDI存在时
@ConditionalOnJava//指定的Java版本存在时
@ConditionalOnWebApplication//Web应用环境下
@ConditionalOnNotWebApplication//非Web应用环境下
@ComponentScan
相当于包扫描器 <context:component-scan base-package="xxx.xxx">
总结
|- @SpringBootConfiguration
|- @Configuration //通过javaconfig的方式来添加组件到IOC容器中
|- @EnableAutoConfiguration
|- @AutoConfigurationPackage //自动配置包,与@ComponentScan组合扫描到IOC容器
// 将META-INF/spring-factories中定义的bean添加到IOC容器
|- @Import(AutoConfigurationImportSelector.class)
|- @ComponentScan //包扫描
自定义Starter
-
starter可以理解为一个可插拔的插件,某功能的开发者不再关注依赖库和具体配置,由starter自动通过classpath路径下发现需要的Bean,并织入。例如,想使用redis,可以使用
spring-boot-starter-redis等等。 -
自定义starter:抽取可独立的配置模块,进行封装
-
命名规则:
xxx-spring-boot-starter,以区分SpringBoot生态提供的 -
示例
pom.xml
<dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-autoconfigure</artifactId> <version>2.3.4.RELEASE</version> </dependency> </dependencies>SimpleBean.java
@EnableConfigurationProperties(SimpleBean.class) @ConfigurationProperties(prefix = "simplebean") public class SimpleBean { private int id; private String name; // 省略getter,setter,toString() }MyAutoConfiguration.java
@Configuration @ConditionalOnClass public class MyAutoConfiguration { static { System.out.println("MyAutoConfiguration init."); } @Bean public SimpleBean simpleBean() { return new SimpleBean(); } }resources下创建/META-INF/spring.factories
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\ wenbin.carol.wu.config.MyAutoConfiguration -
使用
pom.xml
<dependency> <groupId>wenbin.carol.wu</groupId> <artifactId>zdy-spring-boot-starter</artifactId> <version>1.0-SNAPSHOT</version> </dependency>
执行原理
- SpringApplication.run() 如何启动项目?
@SpringBootApplication
public class Springboot01DemoApplication {
public static void main(String[] args) {
SpringApplication.run(Springboot01DemoApplication.class, args);
}
}
public static ConfigurableApplicationContext run(Class<?>[] primarySources, String[] args) {
return (new SpringApplication(primarySources)).run(args);
}
实例化SpringApplication对象
public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) {
// 省略属性赋值
this.primarySources = new LinkedHashSet(Arrays.asList(primarySources));
// 通过查看类路径下是否存在某个特征类,来判断类WebApplicationType
// 1. REACTIVE(Spring 5开始出现的WebFlux交互式应用)
// 2. SERVLET(Spring 5之前传统MVC)
// 3. NONE
this.webApplicationType = WebApplicationType.deduceFromClasspath();
// 设置初始化器(见下图)
this.setInitializers(this.getSpringFactoriesInstances(ApplicationContextInitializer.class));
// 设置监听器(见下图)
this.setListeners(this.getSpringFactoriesInstances(ApplicationListener.class));
// 初始化 mainApplicationClass 属性,用于推断并设置项目main()方法启动的主程序类
this.mainApplicationClass = this.deduceMainApplicationClass();
}
调用run方法
public ConfigurableApplicationContext run(String... args) {
// stopWatch统计启动过程的时长
StopWatch stopWatch = new StopWatch();
stopWatch.start();
// 初始化应用上下文和异常报告集合
ConfigurableApplicationContext context = null;
Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList();
this.configureHeadlessProperty();
//(1)获取并启动监听器
SpringApplicationRunListeners listeners = this.getRunListeners(args);
listeners.starting();
Collection exceptionReporters;
try {
// 初始化默认应用参数类
ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
//(2)项目运行环境的预配置
ConfigurableEnvironment environment = this.prepareEnvironment(listeners, applicationArguments);
this.configureIgnoreBeanInfo(environment);
// 打印Spring的图标
Banner printedBanner = this.printBanner(environment);
//(3)创建Spring容器
// SERVLET - AnnotationConfigServletWebServerApplicationContext
// REACTIVE - AnnotationConfigReactiveWebServerApplicationContext
// NONE - AnnotationConfigApplicationContext
context = this.createApplicationContext();
// 异常报告器
exceptionReporters = this.getSpringFactoriesInstances(SpringBootExceptionReporter.class, new Class[]{ConfigurableApplicationContext.class}, context);
//(4)Spring容器前置处理,包括:将项目启动类注入容器,为后续开启自动化配置奠定基础
this.prepareContext(context, environment, listeners, applicationArguments, printedBanner);
//(5)刷新容器
// 5.1 refresh Spring容器,初始化(Bean资源的定位、解析、注册等等)
// 5.2 向JVM注册一个关机钩子,JVM关闭时关闭这个上下文
this.refreshContext(context);
//(6)Spring容器后置处理,默认为空,可扩展
this.afterRefresh(context, applicationArguments);
// 停止stopwatch,打印log
stopWatch.stop();
if (this.logStartupInfo) {
(new StartupInfoLogger(this.mainApplicationClass)).logStarted(this.getApplicationLog(), stopWatch);
}
//(7)发出结束执行的事件通知
listeners.started(context);
//(8)执行Runners
// 调用项目中自定义的执行器XxxRunner类,服务启动时立即执行一些特定程序,进行初始化操作
// 提供了 ApplicationRunner和CommandLineRunner两种接口
this.callRunners(context, applicationArguments);
} catch (Throwable var10) {
this.handleRunFailure(context, var10, exceptionReporters, listeners);
throw new IllegalStateException(var10);
}
//(9)发布应用上下文就绪事件
try {
listeners.running(context);
return context;
} catch (Throwable var9) {
this.handleRunFailure(context, var9, exceptionReporters, (SpringApplicationRunListeners)null);
throw new IllegalStateException(var9);
}
}
数据访问
-
Spring Boot默认采用整合SpringData的方式统一处理数据访问层,引入各种数据访问模板xxxTemplate以及统一的Repository接口,从而达到简化数据访问层的操作。
-
常见的数据库依赖启动器
- spring-boot-starter-data-jpa
- spring-boot-starter-data-mongodb
- spring-boot-starter-data-neo4j
- spring-boot-starter-data-redis
- mybatis-spring-boot-starter
Spring Boot整合MyBatis
注解方式
配置文件
Spring Boot整合JPA
Spring Boot整合Redis
视图技术
- 支持FreeMarker、Thymeleaf、Mustache
- 不支持JSP模板
Thymeleaf
Thymeleaf语法
常用标签
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<link rel="stylesheet" type="text/css" media="all" href="../../css/gtvg.css" th:href="@{/css/gtvg.css}" />
<title>Title</title>
</head>
<body>
<p th:text="${hello}"> Welcome </p>
</body>
</html>
| th:标签 | 说明 |
|---|---|
| th:insert | 布局标签,替换内容到引入的文件 |
| th:replace | 布局标签,替换整个标签到引入的文件 |
| th:each | 元素遍历 |
| th:if | 条件判断,真 |
| th:unless | 条件判断,假 |
| th:switch | |
| th:case | |
| th:value | 属性值修改,指定标签属性值 |
| th:href | 链接地址 |
| th:src | 链接地址 |
| th:text | 指定标签显示的文本内容 |
标准表达式
-
变量表达式:${...}
获取上下文中的变量,
<p th:text="${title}">没有变量则显示这里的默认值</p>Thymeleaf为变量所在域提供了一些内置对象
# ctx: 上下文对象 # vars: 上下文变量 # locale: 上下文区域设置 # request # response # session # servletContext -
选择变量表达式:*{}
从选定对象中,而不是上下文中获取属性值
<div th:object="${book}"> <p> title: <span th:text="*{title}">标题</span> </p> </div> -
消息表达式:#{}
国际化文件配置文件,见后续
-
链接URL表达式:@{}
<a th:href="@{http://localhost:8080/order/details(orderId=${o.id})}">view</a> <a th:href="@{/order/details(orderId=${o.id})}">view</a> -
片段表达式:~{}
<div th:insert="~{thymeleafDemo::title}"></div>自动查找"/resources/templates"目录下的thymeleafDemo模板,title为片段名称
基本使用
pom.xml
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
application.properties
#thymeleaf
spring.thymeleaf.cache=false
spring.thymeleaf.encoding=UTF-8
spring.thymeleaf.mode=HTML5
spring.thymeleaf.prefix=classpath:/template/
spring.thymeleaf.suffix=.html
静态资源访问
resources目录下public、resources、static子目录
页面展示
配置国际化
-
Spring Boot默认识别的语言配置文件为类路径下resources下的
messages.properties -
其他语言国际化文件的名称必须严格按照
文件前缀名_语言代码_国家代码.properties的形式命名 -
配置
application.properties# 配置国际化文件基础名 spring.messages.basename=i18n.login -
默认
org.springframework.web.servlet.LocaleResolver根据请求头Request Headers中的Accept-Language属性 -
自定义区域解析器
@Configuration public class MyLocaleResolver implements LocaleResolver { @Override public Locale resolveLocale(HttpServletRequest httpServletRequest) { String l = httpServletRequest.getParameter("l"); Locale locale = null; if (!StringUtils.isEmpty(l)) { String[] s = l.split("_"); locale = new Locale(s[0], s[1]); } else { String header = httpServletRequest.getHeader("Accept-Language");//zh-CN,zh;q=0.9 String[] split = header.split(","); String[] split1 = split[0].split("-"); locale = new Locale(split1[0], split1[1]); } return locale; } @Override public void setLocale(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Locale locale) { } @Bean // id必须为localeResolver public LocaleResolver localeResolver() { return new MyLocaleResolver(); } }
缓存管理
默认缓存管理
-
使用
@EnableCaching注解开启基于注解的缓存支持(spring框架提供,通常配置在项目启动类上) -
使用
@Cacheable注解对数据操作方法进行缓存管理(spring框架提供,标注在service类的查询方法上)属性名 说明 value/cacheNames 指定缓存空间的名称,对应缓存唯一标识,必须配置,二选一 key 指定缓存数据的key,默认使用方法:如果函数有且只有一个参数,key为参数值;否则,使用 SimpleKeyGenerator生成
可以使用SpEL表达式keyGenerator 指定缓存数据的key生成器,与key属性二选一 cacheManager 指定缓存管理器 cacheResolver 指定缓存解析器,与cacheManager属性二选一 condition 指定在符合某条件下,进行数据缓存 unless 指定在符合某条件下,不进行数据缓存 sync 指定是否使用异步缓存,默认false -
底层结构,SpringBoot默认装配
SimpleCacheConfiguration, 使用ConcurrentMap作为底层数据结构。 -
@CachePut用于修改操作,哪怕缓存中已经存在目标值,这个方法依然会执行,执行之后的结果被保存在缓存中 -
@CacheEvict用于数据删除方法上,先进行方法调用,然后将缓存进行清除
整合Redis缓存实现
Spring Boot支持的缓存组件
-
数据的缓存管理存储依赖于Spring框架中cache相关的
org.springframework.cache.Cache和org.springframework.cache.CacheManager缓存管理器接口 -
按指定顺序尝试启用
(1) Generic (2) JCache(JSR-107) (3) EhCache 2.x (4) Hazelcast (5) Infinispan (6) Couchbase (7) Redis (8) Caffeine (9) Simple <- 默认
基于注解的Redis缓存实现
pom.xml
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
application.properties
# Redis
spring.redis.host=127.0.0.1
spring.redis.port=6379
spring.redis.password=
# 设置有效期,但不够灵活
spring.cache.redis.time-to-live=60000
service.java
@Service
public class CommentService {
@Autowired
private CommentRepository commentRepository;
@Cacheable(cacheNames = "comment", unless = "#result==null")
public Comment findById(Integer id) {
Optional<Comment> byId = commentRepository.findById(id);
return byId.orElse(null);
}
@CachePut(cacheNames = "comment", key = "#result.id")
public Comment updateComment(Comment comment) {
commentRepository.updateComment(comment.getAuthor(), comment.getId());
return comment;
}
@CacheEvict(cacheNames = "comment")
public void deleteComment(Integer id) {
commentRepository.deleteById(id);
}
}
基于API的Redis缓存实现
@Service
public class ApiCommentService {
@Autowired
private CommentRepository commentRepository;
@Autowired
private RedisTemplate redisTemplate;
public Comment findById(Integer id) {
Object o = redisTemplate.opsForValue().get("comment_" + id);
if (o != null) {
return (Comment) o;
} else {
Optional<Comment> byId = commentRepository.findById(id);
if (byId.isPresent()) {
Comment comment = byId.get();
redisTemplate.opsForValue().set("comment_" + id, comment, 1, TimeUnit.DAYS);
}
return byId.orElse(null);
}
}
public Comment updateComment(Comment comment) {
commentRepository.updateComment(comment.getAuthor(), comment.getId());
redisTemplate.opsForValue().set("comment_" + comment.getId(), comment);
return comment;
}
public void deleteComment(Integer id) {
redisTemplate.delete("comment_" + id);
commentRepository.deleteById(id);
}
}
自定义Redis缓存序列化机制
Redis API 默认序列化机制
- 基于API的Redis缓存实现是使用
RedisTemplate模板进行数据缓存操作的 - 默认使用
JdkSerializationRedisSerializer序列化方式,所以进行数据缓存的实体类必须实现JDK自带的序列化接口(例如Serializable) - 如果定义了缓存序列化方式
defaultSerializer,将使用自定义的序列化方式
自定义RedisTemplate
RedisAutoConfiguration.java
@Bean
// 没有名称为redisTemplate的Bean时,才会进入
@ConditionalOnMissingBean(name = {"redisTemplate"})
public RedisTemplate<Object, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) throws UnknownHostException {
RedisTemplate<Object, Object> template = new RedisTemplate();
template.setConnectionFactory(redisConnectionFactory);
return template;
}
RedisConfig.java
@Configuration
public class RedisConfig {
@Bean
public RedisTemplate<Object, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) {
RedisTemplate<Object, Object> template = new RedisTemplate();
template.setConnectionFactory(redisConnectionFactory);
Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class);
// 解决查询缓存转换异常的问题
ObjectMapper om = new ObjectMapper();
om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
jackson2JsonRedisSerializer.setObjectMapper(om);
// 设置RedisTemplate模板API的序列化方式为JSON
template.setDefaultSerializer(jackson2JsonRedisSerializer);
return template;
}
}
自定义RedisCacheManager
@Bean
public RedisCacheManager cacheManager(RedisConnectionFactory redisConnectionFactory) {
// 分别创建String和JSON格式序列化对象,对缓存数据key和value进行转换
RedisSerializer<String> strSerializer = new StringRedisSerializer();
Jackson2JsonRedisSerializer jacksonSeial = new Jackson2JsonRedisSerializer(Object.class);
// 解决查询缓存转换异常的问题
ObjectMapper om = new ObjectMapper();
om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
jacksonSeial.setObjectMapper(om);
// 定制缓存数据序列化方式及时效
RedisCacheConfiguration config = RedisCacheConfiguration
.defaultCacheConfig().entryTtl(Duration.ofDays(1))
.serializeKeysWith(
RedisSerializationContext
.SerializationPair.fromSerializer(strSerializer))
.serializeValuesWith(
RedisSerializationContext
.SerializationPair.fromSerializer(jacksonSeial))
.disableCachingNullValues();
return RedisCacheManager.builder(redisConnectionFactory)
.cacheDefaults(config).build();
}