一、SpringBoot 基础
1.1 SpringBoot 特性
SpringBoot特性
1、SpringBoot Starter : 将常用的的依赖分组进行整合,将其合并到一个依赖中,这样就可以一次性添加项目的Maven或Gradle构建中.(起步依赖:就是把具备某种功能的坐标打包到一起,并提供一些默认功能)。
2、 使编码变得简单,SpringBoot采用 JavaConfig的方式对Spring进行配置,并且提供了大量的注解,
极大的提高了工作效率。
3、 自动配置:SpringBoot的自动配置特性利用了Spring对条件化配置的支持,合理地推测应用所需的
bean并自动化配置他们;
4、 使部署变得简单,SpringBoot内置了三种Servlet容器,Tomcat,Jetty,undertow.我们只需要一个
Java的运行环境就可以跑SpringBoot的项目了,SpringBoot的项目可以打成一个jar包。
SpringBoot起步依赖和自动配置为SpringBoot两大核心特性
1.2 关于SpringBoot启动过程中的几个疑问
- starter是什么?我们如何去使用这些starter?
2. 为什么包扫描只会扫描核心启动类所在的包及其子包(SpringBoot启动时包扫描时只会扫描核心包及子包。)
3. 在springBoot启动的过程中,是如何完成自动装配的?
4. 内嵌Tomcat是如何被创建及启动的?
5. 使用了web场景对应的starter,springmvc是如何自动装配?
1.3 SpringBoot应用回顾
1、SpringBoot热部署插件
Spring-boot-devtools 应用于开发环境。只会重新加载们项目中的代码 不重新加载依赖的第三方包
依赖项:
<!-- 引入热部署依赖 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
</dependency>
开发工具Idea: 选择IDEA工具界面的【File】->【Settings】选项,打开Compiler面板设置页面 ,选择Build下的Compiler选项,在右侧勾选“Build project automatically”选项将项目设置为自动编译,
在项目任意页面中使用组合快捷键“Ctrl+Shift+Alt+/”打开Maintenance选项框,选中并打开
Registry页面,列表中找到“compiler.automake.allow.when.app.running”,将该选项后的Value值勾选,用于指
定IDEA工具在程序运行过程中自动编译,最后单击【Close】按钮完成设置 .
热部署插件原理:
就是我们在编辑器上启动项目,然后改动相关的代码,然后编辑器自动触发编译替换掉历史的.class文件后,项目检测到有文件变更后会重启srpring-boot项目。引入了插件后,插件会监控我们classpath的资源变化,当classpath有变化后,会触发重启。
该插件重启快速的原因:这里对类加载采用了两种类加载器,对于第三方jar包采用base-classloader来加载,对于开发人员自己开发的代码则使用restart-ClassLoader来进行加载,这使得比停掉服务重启要快的多,因为使用插件只是重启开发人员编写的代码部分。
验证代码:
@Component
public class Devtools implements InitializingBean {
private static final Logger log = LoggerFactory.getLogger(Devtools.class);
@Override
public void afterPropertiesSet() throws Exception {
log.info("guava-jar classLoader: " + DispatcherServlet.class.getClassLoader().toString());
log.info("Devtools ClassLoader: " + this.getClass().getClassLoader().toString());
}
}
这边先去除spring-boot-devtools插件,跑下工程:
可以看到,DispatcherServlet(第三方jar包)和Devtools(自己编写的类)使用的都是AppClassLoader加载的。
我们现在加上插件,然后执行下代码:
发现第三方的jar包的类加载器确实是使用的系统的类加载器,而我们自己写的代码的类加载器为
RestartClassLoader,并且每次重启,类加载器的实例都会改变,销毁旧的创建新的。
某些资源在更改后不一定需要触发重新启动。例如,Thymeleaf模板可以就地编辑。默认情况下,改变资源 /META-INF/maven , /META-INF/resources , /resources , /static , /public , 或 /templates 不触发重新启动,但确会触发现场重装。如果要自定义这些排除项,则可以使用该spring.devtools.restart.exclude 属性。例如,仅排除 /static , /public 您将设置以下属性
spring.devtools.restart.exclude=static/**,public/**
2、全局配置文件
Spring Boot使用一个application.properties或者application.yaml的文件作为全局配置文件
按照优先级从高到低的顺序 ):
1. 先去项目根目录找config文件夹下找配置文件件
2. 再去根目录下找配置文件
3. 去resources下找config文件夹下找配置文件
4. 去resources下找配置文件
–file:./config/
–file:./
–classpath:/config/
–classpath:/
配置文件位置:
SpringBoot会从这四个位置全部加载主配置文件,如果高优先级中配置文件属性与低优先级配置文件不冲突的属性,则会共同存在-> 互补配置.
备注:
这里说的配置文件,都还是项目里面。最终都会被打进jar包里面的,需要注意。
1、如果同一个目录下,有application.yml也有application.properties,默认先读取 application.properties。
2、如果同一个配置属性,在多个配置文件都配置了,默认使用第1个读取到的,后面读取的不覆盖前面读取到的。
3、创建SpringBoot项目时,一般的配置文件放置在“项目的resources目录下”
如果配置文件名字不叫application.properties或者application.yml,可以通过以下参数来指定
配置文件的名字,myproject是配置文件名
$ java -jar myproject.jar --spring.config.name=myproject --指定默认配置文件
java -jar run-0.0.1-SNAPSHOT.jar -- spring.config.location=D:/application.properties --指定文件
指定配置文件和默认加载的这些配置文件共同起作用形成互补配置。
Spring Boot 2.4 改进了处理 application.properties 和 application.yml 配置文件的方式,
如果是2.4.0之前版本,优先级properties>yaml
但是如果是2.4.0的版本,优先级yaml>properties
application.properties配置文件
自定义属性注入pojo
@Component //用于将Person类作为Bean注入到Spring容器中
@ConfigurationProperties(prefix = "person")
//将配置文件中以person开头的属性注入到该类中
public class Person {
private int id;//id
private String name; //名称
private List hobby; //爱好
private String[] family; //家庭成员
private Map map;
private Pet pet; //宠物
// 省略属性getXX()和setXX()方法
// 省略toString()方法
}
@ConfigurationProperties(prefix = “person”)注解的作用是将配置文件中以person开头的属性值通过setXX()方法注入到实体类对应属性中
@Component注解的作用是将当前注入属性值的Person类对象作为Bean组件放到Spring容器中,只有这样才能被@ConfigurationProperties注解进行赋值
编写application.properties配置文件时,由于要配置的Person对象属性是我们自定义的,SpringBoot无法自动识别,所以不会有任何书写提示。在实际开发中,为了出现代码提示的效果来方便配置,在使用@ConfigurationProperties注解进行配置文件属性值注入时,可以在pom.xml文件中添加一个Spring Boot提供的配置处理器依赖:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
<optional>true</optional>
</dependency>
3、属性注入
@Configuration:声明一个类作为配置类
@Bean:声明在方法上,将方法的返回值加入Bean容器
@Value:属性注入(无需get/set方法)
@ConfigurationProperties(prefix = “jdbc”):批量属性注入 (需要set方法)
@PropertySource(“classpath:/jdbc.properties”)指定外部属性文件。在类上添加
eg:
@Value(“${jdbc.url}”)
添加@ConfigurationProperties注解后有警告:Springboot 配置注释处理器未配置(编写配置文件此时无提示)。
@EnableConfigurationProperties 是 Spring Boot 提供的一个注解,使用该注解用于启用应用对另
外一个注解 @ConfigurationProperties 的支持,用于设置一组使用了注解@ConfigurationProperties 的类,用于作为 bean 定义注册到容器中。
第三方配置
@ConfigurationProperties 除了用于注释类之外,您还可以在公共 @Bean 方法上使用它。当要将属
性绑定到控件之外的第三方组件时,这样做特别有用。
//自定义组件
@Data
public class AnotherComponent {
private boolean enabled;
private InetAddress remoteAddress;
}
@Configuration
public class MyService {
@ConfigurationProperties("another")
@Bean
public AnotherComponent anotherComponent(){
return new AnotherComponent();
}
}
//xml配置属性
another.enabled=true
another.remoteAddress=192.168.10.11
松散绑定
Spring Boot使用一些宽松的规则将环境属性绑定到@ConfigurationProperties bean,因此环境属性名和bean属性名之间不需要完全匹配
@Data
@Component
@ConfigurationProperties("acme.my-person.person")
public class OwnerProperties {
private String firstName;
}
属性文件中配置 说明
acme.my-project.person.first-name 羊肉串模式case, 推荐使用
acme.myProject.person.firstName 标准驼峰模式
acme.my_project.person.first_name 下划线模式
ACME_MYPROJECT_PERSON_FIRSTNAME 大写下划线,如果使用系统环境时候推荐使用
4、SpringBoot日志框架
日志-抽象层 | 日志-实现层 |
---|---|
JCL(Jakarta Commons Logging)、SLF4J(Simple Logging Facade for Java)、jboss-logging | jul(java.util.logging)、log4j、logback、log4j2 |
Spring 框架选择使用了 JCL 作为默认日志输出。而 Spring Boot 默认选择了 SLF4J 结合 LogBack
下图是 SLF4J 结合各种日志框架的官方示例,从图中可以清晰的看出 SLF4J API 永远作为日志的门面,
直接应用与应用程序中。
统一日志框架的使用
遗留问题:A项目(slf4J + logback): Spring(commons logging)、Hibernate(jboss-logging)、mybatis….
一般情况下,在项目中存在着各种不同的第三方 jar ,且它们的日志选择也可能不尽相同,显然这样是不利于我们使用的,那么如果我们想为项目设置统一的日志框架该怎么办呢?
在 SLF4J 官方,也给了我们参考的例子
从图中我们得到一种统一日志框架使用的方式,可以使用一种和要替换的日志框架类完全一样的 jar 进行替换,这样不至于原来的第三方 jar 报错,而这个替换的 jar 其实使用了 SLF4J API. 这样项目中的日志就都可以通过 SLF4J API 结合自己选择的框架进行日志输出。
统一日志框架使用步骤归纳如下:
1. 排除系统中的其他日志框架。
2. 使用中间包替换要替换的日志框架。
3. 导入我们选择的 SLF4J 实现。
在 Spring Boot 的 Maven 依赖里可以清楚的看到 Spring Boot 排除了其他日志框架。
SpringBoot 日志框架的引用关系
SpringBoot可以自动的适配日志框架,底层使用 SLF4j + LogBack 记录日志,如果我们自行引入其他框架,需要排除其日志框架。
- SLF4J 日志级别从小到大trace,debug,info,warn,error ,默认日志级别为Info
Logback 日志格式可以知道 Spring Boot 默认日志格式是
自定义日志格式:%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n
# %d{yyyy-MM-dd HH:mm:ss.SSS} 时间
# %thread 线程名称 # %-5level 日志级别从左显示5个字符宽度
# %logger{50} 类名
# %msg%n 日志信息加换行
关于日志的输出路径,可以使用 logging.file 或者 logging.path 进行定义,两者存在关系如下表。# 日志配置
# 指定具体包的日志级别
logging.level.com.lagou=debug
# 控制台和日志文件输出格式
logging.pattern.console=%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n
logging.pattern.file=%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n
# 日志输出路径,默认文件spring.log
logging.file.path=spring.log
#logging.file.name=log.log
二、自定义Starter
SpringBoot中的starter是一种非常重要的机制,能够抛弃以前繁杂的配置,将其统一集成进starter,应用者只需要在maven中引入starter依赖,SpringBoot就能自动扫描到要加载的信息并启动相应的默认配置。starter让我们摆脱了各种依赖库的处理,需要配置各种信息的困扰。SpringBoot会自动通过classpath路径下的类发现需要的Bean,并注册进IOC容器。SpringBoot提供了针对日常企业应用研发各种场景的spring-boot-starter依赖模块。所有这些依赖模块都遵循着约定成俗的默认配置,并允许我们调整这些配置,即遵循“约定大于配置”的理念。
比如我们在springboot里面要引入redis,那么我们需要在pom中引入以下内容
这其实就是一个starter。简而言之,starter就是一个外部的项目,我们需要使用它的时候就可以在当springboot项目中引入它。
自定义starter的命名规则
SpringBoot提供的starter以 spring-boot-starter-xxx 的方式命名的。
官方建议自定义的starter使用 xxx-spring-boot-starter 命名规则。以区分SpringBoot生态提供的starter
自定义starter代码实现整个过程分为两部分:
自定义starter 、使用starter
(1)自定义starter
首先,先完成自定义starter
(1)新建maven jar工程,工程名为zdy-spring-boot-starter,导入SpringBoot的自动配置依赖
(2)编写javaBean 用于其他项目引用时在配置文件中配置的属性值
(3)编写配置类MyAutoConfiguration
(4)resources下创建/META-INF/spring.factories(SpringBoot启动时会加载各个jar包下此文件)
注意:META-INF是自己手动创建的目录,spring.factories也是手动创建的文件,在该文件中配置自己的自动配置类
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.lagou.config.MyAutoConfiguration
上面这句话的意思就是SpringBoot启动的时候会去加载我们的simpleBean到IOC容器中。这其实是一种变形的SPI机制
热拔插技术
改造zdy工程新增热插拔支持类
新增标记类ConfigMarker
public class ConfigMarker { }
新增EnableRegisterServer注解
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Import({ConfigMarker.class})
public @interface EnableRegisterServer { }
改造 MyAutoConfiguration 新增条件注解 @ConditionalOnBean(ConfigMarker.class) , @ConditionalOnBean 这个是条件注解,前面的意思代表只有当期上下文中含有 ConfigMarker对象,被标注的类才会被实例化。
三、 SpringBoot数据访问
1、数据源配置方式:
在maven中配置数据库驱动
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
配置数据库连接
在application.properties中配置数据库连接
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.url=jdbc:mysql:///springboot_h?
useUnicode=true&characterEncoding=utf-8&useSSL=true&serverTimezone=UTC
spring.datasource.username=root
spring.datasource.password=root
# spring.datasource.type=com.alibaba.druid.pool.DruidDataSource
配置spring-boot-starter-jdbc
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>
连接池配置方式
选择数据库连接池的库文件
SpringBoot提供了三种数据库连接池:
HikariCP
Commons DBCP2Tomcat JDBC Connection Pool
其中spring boot2.x版本默认使用HikariCP,maven中配置如下:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>
如果不使用HikariCP,而改用Commons DBCP2,则配置如下:
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-dbcp2</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jdbc</artifactId>
<exclusions>
<exclusion>
<groupId>com.zaxxer</groupId>
<artifactId>HikariCP</artifactId>
</exclusion>
</exclusions>
</dependency>
如果不使用HikariCP,而改用Tomcat JDBC Connection Pool,则配置如下:
<dependency>
<groupId>org.apache.tomcat</groupId>
<artifactId>tomcat-jdbc</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jdbc</artifactId>
<exclusions>
<exclusion>
<groupId>com.zaxxer</groupId>
<artifactId>HikariCP</artifactId>
</exclusion>
</exclusions>
</dependency>
思考:为什么说springboot默认使用的连接池类型是HikariCP,在那指定的?参考数据源自动配置源码分析
四、动态数据源切换
Spring内置了一个AbstractRoutingDataSource,它可以把多个数据源配置成一个Map,然后,根据不同的key返回不同的数据源。因为AbstractRoutingDataSource也是一个DataSource接口,因此,应用程序可以先设置好key, 访问数据库的代码就可以从AbstractRoutingDataSource拿到对应的一个真实的数据源,从而访问指定的数据库
AbstractRoutingDataSource 实现了DataSource, 由此可以看出它是一个标准数据源
AbstractRoutingDataSource中获取连接发方法:
源码中还有另外一个核心的方法 setTargetDataSources(Map
它是一个abstract类,所以我们使用的话,推荐的方式是创建一个类来继承它并且实现的determineCurrentLookupKey() 方法
切换数据源思路分析:
1.项目中准备两部分数据源配置信息,master:product_master slave:product_slave
2.要创建数据源自动配置类,完成master/slave这两个数据源对象的创建
3.创建AbstractRoutingDataSource的子类,重写determineCurrentLookupKey方法
4.要将两个数据源对象添加到AbstractRoutingDataSource的targetDataSources的这个map中
map.put(“master”,masterdataSource);
map.put(“slave”,slavedataSource);
问题:如何存储动态选择的key
可通过ThreadLocal来存储dataSource的key
五、SpringBoot缓存应用
JSR-107 就是关于如何使用缓存的规范,是java提供的一个接口规范,类似于JDBC规范,没有具体的实现,具体的实现就是reids等这些缓存。
Java Caching(JSR-107)定义了5个核心接口,分别是CachingProvider、CacheManager、Cache、Entry和Expiry。
1.CachingProvider(缓存提供者):创建、配置、获取、管理和控制多个CacheManager
2.CacheManager(缓存管理器):创建、配置、获取、管理和控制多个唯一命名的Cache,
3.Cache存在于CacheManager的上下文中。一个CacheManager仅对应一个CachingProvider
4.Cache(缓存):是由CacheManager管理的,CacheManager管理Cache的生命周期,
5.Cache存在于CacheManager的上下文中,是一个类似map的数据结构,并临时存储以key为索引的值。一个Cache仅 被一个CacheManager所拥有
6.Entry(缓存键值对):是一个存储在Cache中的key-value对
- Expiry(缓存时效):每一个存储在Cache中的条目都有一个定义的有效期。一旦超过这个时间,条目就自动过期,过期后,条目将不可以访问、更新和删除操作。缓存有效期可以通过ExpiryPolicy设置
一个应用里面可以有多个缓存提供者(CachingProvider),一个缓存提供者可以获取到多个缓存管理器(CacheManager),一个缓存管理器管理着不同的缓存(Cache),缓存中是一个个的缓存键值对(Entry),每个entry都有一个有效期(Expiry)。缓存管理器和缓存之间的关系有点类似于数据库中连接池和连接的关系。
5.1 Spring的缓存抽象
Spring从3.1开始定义了org.springframework.cache.Cache和org.springframework.cache.CacheManager接口来统一不同的缓存技术;并支持使用Java
Caching(JSR-107)注解简化我们进行缓存开发。
Spring Cache 只负责维护抽象层,具体的实现由自己的技术选型来决定。将缓存处理和缓存技术解除耦合。
每次调用需要缓存功能的方法时,Spring会检查指定参数的指定的目标方法是否已经被调用过,如果有就直接从缓存中获取方法调用后的结果,如果没有就调用方法并缓存结果后返回给用户。下次调用直接从缓存中获取。
使用Spring缓存抽象时我们需要关注以下两点:
① 确定哪些方法需要被缓存(经常被查询且很少修改的数据需要被缓存)
② 缓存策略 (key-value, key的生成方式,value序列化方式)
重要接口
Cache:缓存抽象的规范接口,缓存实现有:RedisCache、EhCache、ConcurrentMapCache等
CacheManager:缓存管理器,管理Cache的生命周期
5.2 Spring缓存使用
概念和注解
说明:
① @Cacheable标注在方法上,表示该方法的结果需要被缓存起来,缓存的键由keyGenerator的策略决定,缓存的值的形式则由serialize序列化策略决定(JDK序列化还是json格式);标注上该注解之后,在缓存时效内再次调用该方法时将不会调用方法本身而是直接从缓存获取结果
② @CachePut也标注在方法上,和@Cacheable相似也会将方法的返回值缓存起来,不同的是标注@CachePut的方法每次都会被调用,而且每次都会将结果缓存起来,适用于对象的更新
5.2 缓存使用
① 开启基于注解的缓存功能:主启动类标注@EnableCaching
@SpringBootApplication
@MapperScan("com.example.springbootcache.mapper")
@EnableCaching
public class SpringbootCacheApplication {
public static void main(String[] args) {
SpringApplication.run(SpringbootCacheApplication.class, args);
}
}
② 标注缓存相关注解:@Cacheable、CacheEvict、CachePut
@Cacheable:将方法运行的结果进行缓存,以后再获取相同的数据时,直接从缓存中获取,不再调用方法
/*
*@Cacheable :缓存查询,会将该方法的返回值存到缓存中,
* value/cacheNames:指定了缓存的名称 cacheManager是管理多个cache,以名称进行区分
* key:缓存数据时指定key值(key,value)默认是方法的参数值,也可以使用spEL来计算key的值
* keyGenerator:key的生成策略,和key进行二选一,自定义keyGenerator
* cacheManager:指定缓存管理器 redis:emp ehcache:emp
* cacheResolver:功能根cacheManager相同,二选一即可
* condition: 条件属性,满足这个条件才会进行缓存
* unless: 否定条件,满足这个条件,不进行缓存
* sync:是否进行异步模式进行缓存 true
* (1)condition和unless同时满足,不缓存
* (2)sync为true时,unless不被支持
**/
@Cacheable(cacheNames = {"emp"})
public Employee getEmpById(Integer id){
Employee emp = employeeMapper.getEmpById(id);
return emp;
}
可用的SpEL表达式见下表:
@CachePut
1、说明:既调用方法,又更新缓存数据,一般用于更新操作,在更新缓存时一定要和想更新的缓存有相同的缓存名称和相同的key(可类比同一张表的同一条数据)
2、运行时机:
①先调用目标方法
②将目标方法的结果缓存起来
@CachePut(value = "emp",key = "#employee.id")
public Employee updateEmp(Employee employee){
employeeMapper.updateEmp(employee);
return employee;
}
@CachePut标注的方法总会被调用,且调用之后才将结果放入缓存,因此可以使用#result获取到方法的返回值。
@CacheEvict
1、说明:缓存清除,清除缓存时要指明缓存的名字和key,相当于告诉数据库要删除哪个表中的哪条数据,key默认为参数的值
2、属性:
value/cacheNames:缓存的名字
key:缓存的键
allEntries:是否清除指定缓存中的所有键值对,默认为false,设置为true时会清除缓存中的所有键值对,与key属性二选一使用
beforeInvocation:在@CacheEvict注解的方法调用之前清除指定缓存,默认为false,即在方法调用之后清除缓存,设置为true时则会在方法调用之前清除缓存(在方法调用之前还是之后清除缓存的区别在于方法调用时是否会出现异常,若不出现异常,这两种设置没有区别,若出现异常,设置为在方法调用之后清除缓存将不起作用,因为方法调用失败了)
@CacheEvict(value = "emp",key = "#id",beforeInvocation = true)
public void delEmp(Integer id){
employeeMapper.deleteEmpById(id);
}
@CacheConfig
1、作用:标注在类上,抽取缓存相关注解的公共配置,可抽取的公共配置有缓存名字、主键生成器等(如注解中的属性所示)
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface CacheConfig {
String[] cacheNames() default {};
String keyGenerator() default "";
String cacheManager() default "";
String cacheResolver() default "";
}
通过@CacheConfig的cacheNames 属性指定缓存的名字之后,该类中的其他缓存注解就不必再写value或者cacheName了,会使用该名字作为value或cacheName的值,当然也遵循就近原则
@Service
@CacheConfig(cacheNames = "emp")
public class EmployeeService {
@Autowired
EmployeeMapper employeeMapper;
@Cacheable
public Employee getEmpById(Integer id) {
Employee emp = employeeMapper.getEmpById(id);
return emp;
}
@CachePut(key = "#employee.id")
public Employee updateEmp(Employee employee) {
employeeMapper.updateEmp(employee);
return employee;
}
@CacheEvict(key = "#id", beforeInvocation = true)
public void delEmp(Integer id) {
employeeMapper.deleteEmpById(id);
}
}
5.3 基于Redis的缓存实现
SpringBoot默认开启的缓存管理器是ConcurrentMapCacheManager,创建缓存组件是ConcurrentMapCache,将缓存数据保存在一个个的ConcurrentHashMap
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
②配置redis:只需要配置redis的主机地址(端口默认即为6379,因此可以不指定)
#将日志级别调整成debug
logging.level.com.example.springbootcache.mapper = debug
spring.redis.host=127.0.0.1
spring.redis.password=123456
使用redis存储对象时,该对象必须可序列化(实现Serializable接口),否则会报错,此时存储的结果在redis的管理工具中查看如下:由于序列化的原因值和键都变为了另外一种形式
SpringBoot默认采用的是JDK的对象序列化方式,我们可以切换为使用JSON格式进行对象的序列化操作,这时需要我们自定义序列化规则(当然我们也可以使用Json工具先将对象转化为Json格式之后再保存至redis,这样就无需自定义序列化)
Redis注解默认序列化机制
/*
* Copyright 2012-2019 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.boot.autoconfigure.cache;
import java.util.LinkedHashSet;
import java.util.List;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.boot.autoconfigure.AutoConfigureAfter;
import org.springframework.boot.autoconfigure.cache.CacheProperties.Redis;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.data.redis.RedisAutoConfiguration;
import org.springframework.cache.CacheManager;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Conditional;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.ResourceLoader;
import org.springframework.data.redis.cache.RedisCacheManager;
import org.springframework.data.redis.cache.RedisCacheManager.RedisCacheManagerBuilder;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.serializer.JdkSerializationRedisSerializer;
import org.springframework.data.redis.serializer.RedisSerializationContext.SerializationPair;
/**
* Redis cache configuration.
*
* @author Stephane Nicoll
* @author Mark Paluch
* @author Ryon Day
*/
@Configuration(proxyBeanMethods = false)
@ConditionalOnClass(RedisConnectionFactory.class)
@AutoConfigureAfter(RedisAutoConfiguration.class)
@ConditionalOnBean(RedisConnectionFactory.class)
@ConditionalOnMissingBean(CacheManager.class)
@Conditional(CacheCondition.class)
class RedisCacheConfiguration {
@Bean
RedisCacheManager cacheManager(CacheProperties cacheProperties, CacheManagerCustomizers cacheManagerCustomizers,
ObjectProvider<org.springframework.data.redis.cache.RedisCacheConfiguration> redisCacheConfiguration,
ObjectProvider<RedisCacheManagerBuilderCustomizer> redisCacheManagerBuilderCustomizers,
RedisConnectionFactory redisConnectionFactory, ResourceLoader resourceLoader) {
RedisCacheManagerBuilder builder = RedisCacheManager.builder(redisConnectionFactory).cacheDefaults(
determineConfiguration(cacheProperties, redisCacheConfiguration, resourceLoader.getClassLoader()));
List<String> cacheNames = cacheProperties.getCacheNames();
if (!cacheNames.isEmpty()) {
builder.initialCacheNames(new LinkedHashSet<>(cacheNames));
}
redisCacheManagerBuilderCustomizers.orderedStream().forEach((customizer) -> customizer.customize(builder));
return cacheManagerCustomizers.customize(builder.build());
}
private org.springframework.data.redis.cache.RedisCacheConfiguration determineConfiguration(
CacheProperties cacheProperties,
ObjectProvider<org.springframework.data.redis.cache.RedisCacheConfiguration> redisCacheConfiguration,
ClassLoader classLoader) {
return redisCacheConfiguration.getIfAvailable(() -> createConfiguration(cacheProperties, classLoader));
}
private org.springframework.data.redis.cache.RedisCacheConfiguration createConfiguration(
CacheProperties cacheProperties, ClassLoader classLoader) {
Redis redisProperties = cacheProperties.getRedis();
org.springframework.data.redis.cache.RedisCacheConfiguration config = org.springframework.data.redis.cache.RedisCacheConfiguration
.defaultCacheConfig();
config = config.serializeValuesWith(
SerializationPair.fromSerializer(new JdkSerializationRedisSerializer(classLoader)));
if (redisProperties.getTimeToLive() != null) {
config = config.entryTtl(redisProperties.getTimeToLive());
}
if (redisProperties.getKeyPrefix() != null) {
config = config.prefixKeysWith(redisProperties.getKeyPrefix());
}
if (!redisProperties.isCacheNullValues()) {
config = config.disableCachingNullValues();
}
if (!redisProperties.isUseKeyPrefix()) {
config = config.disableKeyPrefix();
}
return config;
}
}
上述核心源码中可以看出,RedisCacheConfiguration内部同样通过Redis连接工厂RedisConnectionFactory定义了一个缓存管理器RedisCacheManager;同时定制RedisCacheManager时,也默认使用了JdkSerializationRedisSerializer序列化方式。如果想要使用自定义序列化方式的RedisCacheManager进行数据缓存操作,可以参考上述核心代码创建一个名为cacheManager的Bean组件,并在该组件中设置对应的序列化方式即可
自定义RedisCacheManager
在项目的Redis配置类RedisConfig中,按照上一步分析的定制方法自定义名为 cacheManager的Bean组件,参考源码修改
@Configuration
public class RedisConfig {
@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();
RedisCacheManager cacheManager = RedisCacheManager
.builder(redisConnectionFactory)
.cacheDefaults(config)
.build();
return cacheManager;
}
}
上述代码中,在RedisConfig配置类中使用@Bean注解注入了一个默认名称为方法名的cacheManager组件。在定义的Bean组件中,通过RedisCacheConfiguration对缓存数据的key和value分别进行了序列化方式的定制,其中缓存数据的key定制为StringRedisSerializer(即String格式),而value定制为了Jackson2JsonRedisSerializer(即JSON格式),同时还使用entryTtl(Duration.ofDays(1))方法将缓存数据有效期设置为1天
六、SpringBoot部署与监控
6.1 项目部署
1、SpringBoot将项目打包成jar包(官方推荐)
a.首先在pom.xml文件中导入Springboot的maven依赖
<!--将应用打包成一个可以执行的jar包-->
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
b.执行package,package完成以后,target中会生成一个.jar包;
c.可以将jar包上传到Linux服务器上,以jar运行(此处本地验证打包成功)
java -jar springboot-cache-0.0.1-SNAPSHOT.jar
2、将SpringBoot项目打成war包
传统的部署方式:将项目打成war包,放入tomcat 的webapps目录下面,启动tomcat,即可访问。
SpringBoot项目改造打包成war的流程
1、pom.xml配置修改
<packaging>jar</packaging>
//修改为
<packaging>war</packaging>
2、pom文件添加如些依赖
<!--添加servlet-api的依赖,用来打war包 -->
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<scope>provided</scope>
</dependency>
3、排除springboot内置的tomcat干扰
<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>
4、改造启动类
如果是war包发布,需要增加SpringBootServletInitializer子类,并重写其configure方法, 或者将main函数所在的类继承SpringBootServletInitializer,并重写configure方法 当时打包为war时上传到tomcat服务器中访问项目始终报404错就是忽略了这个步骤!!!
@SpringBootApplication
@MapperScan("com.example.springbootcache.mapper")
@EnableCaching
public class SpringbootCacheApplication extends SpringBootServletInitializer {
public static void main(String[] args) {
SpringApplication.run(SpringbootCacheApplication.class, args);
}
@Override
protected SpringApplicationBuilder configure(SpringApplicationBuilder builder) {
// 注意这里要指向原先用main方法执行的Application启动类
return builder.sources(SpringbootCacheApplication.class);
}
}
5、pom文件中不要忘了maven编译插件
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
6、在IDEA中使用mvn clean命令清除旧的包,并使用mvn package生成新的war包
7、使用外部Tomcat运行该 war 文件(把 war 文件直接丢到 tomcat的webapps目录,启动tomcat)
注意事项:
将项目打成war包,部署到外部的tomcat中,这个时候,不能直接访问spring boot 项目中配置文件配置的端口。application.yml中配置的server.port配置的是spring boot内置的tomcat的端口号, 打成war包部署在独立的tomcat上之后, 配置的server.port是不起作用的。一定要注意这一点!!
jar包和war包方式对比
1、SpringBoot项目打包时能打成 jar 与 war包,对比两种打包方式:
jar更加简单方便,使用 java -jar xx.jar 就可以启动。所以打成 jar 包的最多。
而 war包可以部署到tomcat的 webapps 中,随Tomcat的启动而启动。具体使用哪种方式,应视应用场景而定。
2、打jar包时不会把src/main/webapp(ServletContext的根目录) 下的内容打到jar包里 (你认为的打到jar包里面,路径是不行的会报404)
打war包时会把src/main/webapp 下的内容打到war包里
springboot访问静态资源,默认有两个默认目录,
一个是 classpath/static 目录 (src/mian/resource)
一个是 ServletContext 根目录下( src/main/webapp )
3、打成什么文件包进行部署与项目业务有关,就像提供 rest 服务的项目需要打包成 jar文件,用命令运行很方便。。。 而有大量css、js、html,且需要经常改动的项目,打成 war 包去运行比较方便,因为改动静态资源可以直接覆盖, 很快看到改动后的效果,这是 jar 包不能比的
(举个‘栗’子:项目打成 jar 包运行,一段时间后,前端要对其中某几个页面样式进行改动,使其更美观,那么改动几个css、html后,需要重新打成一个新的 jar 包,上传服务器并运行,这种改动频繁时很不友好,文件大时上传服务器很耗时,那么 war包就能免去这种烦恼,只要覆盖几个css与html即可)
6.2 多环境部署
Spring Boot 对此提供了支持,一方面是注解@Profile,另一方面还有多资源配置文件。
@Profile
@Profile 注解的作用是指定类或方法在特定的 Profile 环境生效,任何 @Component 或 @Configuration 注解的类都可以使用 @Profile 注解。在使用DI来依赖注入的时候,能够根据@Profile 标明的环境,将注入符合当前运行环境的相应的bean。
使用要求:
@Component 或 @Configuration 注解的类可以使用 @profile
@Profile 中需要指定一个字符串,约定生效的环境
@Profile 的使用位置
(1) @Prifile 修饰类
@Configuration
@Profile("prod")
public class JndiDataConfig {
@Bean(destroyMethod="")
public DataSource dataSource() throws Exception {
Context ctx = new InitialContext();
return (DataSource) ctx.lookup("java:comp/env/jdbc/datasource");
}
}
@Profile 修饰方法
@Configuration
public class AppConfig {
@Bean("dataSource")
@Profile("dev")
public DataSource standaloneDataSource() {
return new EmbeddedDatabaseBuilder()
.setType(EmbeddedDatabaseType.HSQL)
.addScript("classpath:com/bank/config/sql/schema.sql")
.addScript("classpath:com/bank/config/sql/test-data.sql")
.build();
}
@Bean("dataSource")
@Profile("prod")
public DataSource jndiDataSource() throws Exception {
Context ctx = new InitialContext();
return (DataSource) ctx.lookup("java:comp/env/jdbc/datasource");
}
}
(3) @Profile 修饰注解
@Profile 注解支持定义在其他注解之上,以创建自定义场景注解。这样就创建了一个 @Dev 注解,该注解可以标识bean使用于 @Dev 这个场景。后续就不再需要使用 @Profile(“dev”) 的方式,这样即可以简化代码。
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Profile("prod")
public @interface Production { }
2、profile激活
实际使用中,注解中标示了prod、test、qa等多个环境,运行时使用哪个profile由spring.profiles.active控制,以下说明2种方式:配置文件方式、命令行方式。
(1)配置文件方式激活profile
确定当前使用的是哪个环境,这边环境的值与application-prod.properties中-后面的值对应,这是SpringBoot约定好的。
在resources/application.properties中添加下面的配置。需要注意的是,spring.profiles.active的取值应该与 @Profile 注解中的标示保持一致。
除此之外,同理还可以在resources/application.yml中配置,效果是一样的:
(2)命令行方式激活profile
dev在打包后运行的时候,添加参数:
java -jar spring-boot-config-0.0.1-SNAPSHOT.jar —spring.profiles.active=dev;
多Profile的资源文件
除了@profile注解的可以标明某些方法和类具体在哪个环境下注入。springboot的环境隔离还可以使用多资源文件的方式,进行一些参数的配置。
①资源配置文件
Springboot的资源配置文件除了application.properties之外,还可以有对应的资源文件application-{profile}.properties。
假设,一个应用的工作环境有:dev、test、prod那么,我们可以添加 4 个配置文件:
applcation.properties - 公共配置
application-dev.properties - 开发环境配置
application-test.properties - 测试环境配置
application-prod.properties - 生产环境配置
不同的properties配置文件也可以是在 applcation.properties 文件中来激活 profile:
spring.profiles.active = test