资料来源:https://www.bilibili.com/video/BV1KW411F7oX?p=1

一、Spring Boot与缓存

1、JSR107

Java Caching定义了5个核心接口,分别是CachingProvider, CacheManager, Cache, Entry 和 Expiry。

  • CachingProvider定义了创建、配置、获取、管理和控制多个CacheManager。一个应用可以在运行期访问多个CachingProvider。 —- 缓存提供者
  • CacheManager定义了创建、配置、获取、管理和控制多个唯一命名的Cache,这些Cache存在于CacheManager的上下文中。一个CacheManager仅被一个CachingProvider所拥有。 —- 缓存管理器
  • Cache是一个类似Map的数据结构并临时存储以Key为索引的值。一个Cache仅被一个CacheManager所拥有。
  • Entry是一个存储在Cache中的key-value对。
  • Expiry每一个存储在Cache中的条目有一个定义的有效期。一旦超过这个时间,条目为过期的状态。一旦过期,条目将不可访问、更新和删除。缓存有效期可以通过ExpiryPolicy设置。

image.png

2、Spring缓存抽象

Spring从3.1开始定义了org.springframework.cache.Cache和org.springframework.cache.CacheManager接口来统一不同的缓存技术;并支持使用JCache(JSR-107)注解简化我们开发;

  • Cache接口为缓存的组件规范定义,包含缓存的各种操作集合;
  • Cache接口下Spring提供了各种xxxCache的实现;如RedisCache,EhCacheCache , ConcurrentMapCache等;
  • 每次调用需要缓存功能的方法时,Spring会检查检查指定参数的指定的目标方法是否已经被调用过;如果有就直接从缓存中获取方法调用后的结果,如果没有就调用方法并缓存结果后返回给用户。下次调用直接从缓存中获取。
  • 使用Spring缓存抽象时我们需要关注以下两点;
    • 1、确定方法需要被缓存以及他们的缓存策略
    • 2、从缓存中读取之前缓存存储的数据

image.png

3、缓存工作原理

将方法的运行结果进行缓存;以后再要相同的数据,直接从缓存中获取,不用调用方法;
CacheManager管理多个Cache组件的,对缓存的真正CRUD操作在Cache组件中,每一个缓存组件有自己唯一一个名字;
默认使用的是ConcurrentMapCacheManager==ConcurrentMapCache;将数据保存在 ConcurrentMap中。
image.png
开发中使用缓存中间件;redis、memcached、ehcache;

原理:
1、自动配置类:CacheAutoConfiguration
2、缓存的配置类
org.springframework.boot.autoconfigure.cache.GenericCacheConfiguration
org.springframework.boot.autoconfigure.cache.JCacheCacheConfiguration
org.springframework.boot.autoconfigure.cache.EhCacheCacheConfiguration
org.springframework.boot.autoconfigure.cache.HazelcastCacheConfiguration
org.springframework.boot.autoconfigure.cache.InfinispanCacheConfiguration
org.springframework.boot.autoconfigure.cache.CouchbaseCacheConfiguration
org.springframework.boot.autoconfigure.cache.RedisCacheConfiguration
org.springframework.boot.autoconfigure.cache.CaffeineCacheConfiguration
org.springframework.boot.autoconfigure.cache.GuavaCacheConfiguration
org.springframework.boot.autoconfigure.cache.SimpleCacheConfiguration【默认】
org.springframework.boot.autoconfigure.cache.NoOpCacheConfiguration
3、哪个配置类默认生效:SimpleCacheConfiguration;
application.properties中配置debug=true,可查看以下数据
image.png
4、给容器中注册了一个CacheManager:ConcurrentMapCacheManager
image.png
5、可以获取和创建ConcurrentMapCache类型的缓存组件;他的作用将数据保存在ConcurrentMap中

4、缓存注解

Cache 缓存接口,定义缓存操作。
实现有:RedisCache、EhCacheCache、ConcurrentMapCache等
CacheManager 缓存管理器,管理各种缓存(Cache)组件
@Cacheable 主要针对方法配置,能够根据方法的请求参数对其结果进行缓存
@CacheEvict 清空缓存
@CachePut 保证方法被调用,又希望结果被缓存
@EnableCaching 开启基于注解的缓存
keyGenerator 缓存数据时key生成策略
serialize 缓存数据时value序列化策略

image.png
image.png

4.1 @Cacheable

1、运行流程

第一步:service方法运行之前,先去查询Cache(缓存组件),按照cacheNames指定的名字获取缓存; (CacheManager先获取相应的缓存)
image.png
第一次获取缓存时,如果没有Cache组件会自动创建。
image.png
第二步:首先根据方法的参数按照某种策略生成key,然后根据key去Cache中查找缓存的内容。第一次查询无缓存,所以store为空
image.png
key是按照某种策略生成的;默认是使用keyGenerator生成的,默认使用SimpleKeyGenerator生成key
image.png
SimpleKeyGenerator生成key的默认策略;
如果没有参数;key = new SimpleKey();
如果有一个参数:key = 参数的值
如果有多个参数:key = new SimpleKey(params);
image.png
第三步:没有查到缓存就调用目标方法返回结果
image.png

第四步:将目标方法返回的结果,放进缓存中
image.png

@Cacheable标注的方法执行之前先来检查缓存中有没有这个数据,默认按照参数的值作为key去查询缓存
如果没有就运行方法并将结果放入缓存;以后再来调用就可以直接使用缓存中的数据;
image.png
image.png

2、核心原理

1)使用CacheManager【ConcurrentMapCacheManager】按照名字得到Cache【ConcurrentMapCache】组件
2)key使用keyGenerator生成的,默认是SimpleKeyGenerator

3、几个属性

cacheNames/value:指定缓存组件的名字;将方法的返回结果放在哪个缓存中,是数组的方式,可以指定多个缓存;
key:缓存数据使用的key;可以用它来指定。默认是使用方法参数的值 1 - 方法的返回值
编写SpEL:#id;参数id的值 #a0 #p0 #root.args[0]
getEmp[2]:key = “#root.methodName+’[‘+#id+’]’”
image.pngkeyGenerator:key的生成器;可以自己指定key的生成器的组件id
key/keyGenerator:二选一使用;

  1. @Configuration
  2. public class MyCacheConfig {
  3. @Bean("myKeyGenerator")
  4. public KeyGenerator keyGenerator(){
  5. return new KeyGenerator(){
  6. @Override
  7. public Object generate(Object target, Method method, Object... params) {
  8. return method.getName()+"["+ Arrays.asList(params).toString()+"]";
  9. }
  10. };
  11. }
  12. }

image.png
cacheManager:指定缓存管理器;或者cacheResolver指定获取解析器

condition:指定符合条件的情况下才缓存;
condition = “#id>0”
condition = “#a0>1”:第一个参数的值 >1的时候才进行缓存
image.png
unless:否定缓存;当unless指定的条件为true,方法的返回值就不会被缓存;可以获取到结果进行判断
unless = “#result == null”
unless = “#a0==2”:如果第一个参数的值是2,结果不缓存;
image.png
sync:是否使用异步模式,此时不支持unless

4.2 @CachePut

既调用方法,又更新缓存数据;同步更新缓存
修改了数据库的某个数据,同时更新缓存;

  1. @Cacheable(value = {"emp"})
  2. public Employee getEmp(Integer id){
  3. System.out.println("查询" + id + "号员工");
  4. Employee emp = employeeMapper.getEmpById(id);
  5. return emp;
  6. }
  7. @CachePut(value = "emp")
  8. public Employee updateEmp(Employee employee){
  9. System.out.println("updateEmp:" + employee);
  10. employeeMapper.updateEmp(employee);
  11. return employee;
  12. }

运行时机:
1、先调用目标方法
2、将目标方法的结果缓存起来

注意:缓存更新的key与缓存查询的key必须一致
测试步骤:
1、查询1号员工,查到的结果会放在缓存中;
此时的key:1 value:lastName:张三
image.png
2、以后查询还是之前的结果,直接查询缓存数据,不用查询数据库
3、更新1号员工【lastName:zhangsan;gender:0】
将方法的返回值也放进缓存了,此时的key:传入的employee对象 value:返回的employee对象
image.png
4、再次查询1号员工
1)没有更新先前的查询结果。原因是:1号员工没有在缓存中更新,先前缓存数据的key为1,更新操作后的缓存数据的key为employee对象
image.png
2)要想查询结果为更新后的数据:

  1. @CachePut(value = "emp", key = "#result.id")
  2. public Employee updateEmp(Employee employee){
  3. System.out.println("updateEmp:" + employee);
  4. employeeMapper.updateEmp(employee);
  5. return employee;
  6. }

方式一:key = “#employee.id”:使用传入的参数的员工id
方式二:key = “#result.id”:使用返回后的id
image.png
注意:@Cacheable的key不能使用#result,原因是方法运行之前就要生成key。此时没有result

4.3 @CacheEvict

key:指定要清除的数据
allEntries = true:指定清除这个缓存中所有的数据
beforeInvocation = false:缓存的清除是否在方法之前执行
默认代表缓存清除操作是在方法执行之后执行,如果出现异常缓存就不会清除
beforeInvocation = true:
代表清除缓存操作是在方法运行之前执行,无论方法是否出现异常,缓存都清除

  1. @CacheEvict(value="emp", key = "#id", beforeInvocation = true)
  2. public void deleteEmp(Integer id){
  3. System.out.println("deleteEmp:"+id);
  4. //employeeMapper.deleteEmpById(id);
  5. int i = 10/0;
  6. }

4.4 @Caching

定义复杂的缓存规则

  1. @Caching(cacheable = { @Cacheable(value="emp", key = "#lastName") },
  2. put = { @CachePut(value="emp", key = "#result.id"),
  3. @CachePut(value="emp", key = "#result.email")})
  4. public Employee getEmpByLastName(String lastName){
  5. return employeeMapper.getEmpByLastName(lastName);
  6. }

5、缓存的使用

1、引入spring-boot-starter-cache模块

  1. <dependency>
  2. <groupId>org.springframework.boot</groupId>
  3. <artifactId>spring-boot-starter-cache</artifactId>
  4. </dependency>

2、@EnableCaching开启缓存

  1. @SpringBootApplication
  2. @EnableCaching
  3. public class Springboot01CacheApplication {
  4. public static void main(String[] args) {
  5. SpringApplication.run(Springboot01CacheApplication.class, args);
  6. }
  7. }

3、使用缓存注解

  1. @Cacheable(value = {"emp"}, condition = "#a0>1", unless = "#a0==2")
  2. public Employee getEmp(Integer id){
  3. System.out.println("查询" + id + "号员工");
  4. Employee emp = employeeMapper.getEmpById(id);
  5. return emp;
  6. }

4、切换为其他缓存

6、整合redis实现缓存

  1. <dependency>
  2. <groupId>org.springframework.boot</groupId>
  3. <artifactId>spring-boot-starter-data-redis</artifactId>
  4. </dependency>
  1. 引入spring-boot-starter-data-redis
  2. application.yml配置redis连接地址
  3. 使用RestTemplate操作redis
  4. redisTemplate.opsForValue(); //操作字符串
  5. redisTemplate.opsForHash(); //操作hash
  6. redisTemplate.opsForList(); //操作list
  7. redisTemplate.opsForSet(); //操作set
  8. redisTemplate.opsForZSet(); //操作有序set
  9. 配置缓存、CacheManagerCustomizers
  10. 测试使用缓存、切换缓存、CompositeCacheManager

原理:CacheManager===Cache 缓存组件来实际给缓存中存取数据
1)引入redis的starter,容器中保存的是 RedisCacheManager(此时就作为默认缓存管理器);
2)RedisCacheManager 帮我们创建 RedisCache 来作为缓存组件;RedisCache通过操作redis缓存数据的
3)默认保存数据 k-v 都是Object;利用序列化保存;如何保存为json
1、引入了redis的starter,cacheManager变为 RedisCacheManager;
2、默认创建的 RedisCacheManager 操作redis的时候使用的是:RedisTemplate
3、RedisTemplate是默认使用JDK的序列化机制
4)自定义CacheManager

  1. @RunWith(SpringRunner.class)
  2. @SpringBootTest
  3. public class Springboot01CacheApplicationTests {
  4. @Autowired
  5. EmployeeMapper employeeMapper;
  6. @Autowired
  7. StringRedisTemplate stringRedisTemplate; //操作k-v都是字符串的
  8. @Autowired
  9. RedisTemplate redisTemplate; //k-v都是对象的
  10. @Autowired
  11. RedisTemplate<Object, Employee> empRedisTemplate;
  12. /**
  13. * Redis常见的五大数据类型
  14. * String(字符串)、List(列表)、Set(集合)、Hash(散列)、ZSet(有序集合)
  15. * stringRedisTemplate.opsForValue()[String(字符串)]
  16. * stringRedisTemplate.opsForList()[List(列表)]
  17. * stringRedisTemplate.opsForSet()[Set(集合)]
  18. * stringRedisTemplate.opsForHash()[Hash(散列)]
  19. * stringRedisTemplate.opsForZSet()[ZSet(有序集合)]
  20. */
  21. @Test
  22. public void test01(){
  23. // 给redis中保存数据
  24. stringRedisTemplate.opsForValue().append("msg","hello");
  25. String msg = stringRedisTemplate.opsForValue().get("msg");
  26. System.out.println(msg);
  27. stringRedisTemplate.opsForList().leftPush("mylist","1");
  28. stringRedisTemplate.opsForList().leftPush("mylist","2");
  29. }
  30. // 测试保存对象
  31. @Test
  32. public void test02(){
  33. Employee empById = employeeMapper.getEmpById(1);
  34. // 默认如果保存对象,使用jdk序列化机制,序列化后的数据保存到redis中
  35. redisTemplate.opsForValue().set("emp-01",empById);
  36. // 将数据以json的方式保存
  37. // 方式一:自己将对象转为json
  38. // 方式二:redisTemplate默认的序列化规则;改变默认的序列化规则;
  39. empRedisTemplate.opsForValue().set("emp-01",empById);
  40. }
  41. }
  1. @Configuration
  2. public class MyRedisConfig {
  3. @Bean
  4. public RedisTemplate<Object, Employee> empRedisTemplate(
  5. RedisConnectionFactory redisConnectionFactory)
  6. throws UnknownHostException {
  7. RedisTemplate<Object, Employee> template = new RedisTemplate<Object, Employee>();
  8. template.setConnectionFactory(redisConnectionFactory);
  9. Jackson2JsonRedisSerializer<Employee> ser = new Jackson2JsonRedisSerializer<Employee>(Employee.class);
  10. template.setDefaultSerializer(ser);
  11. return template;
  12. }
  13. @Bean
  14. public RedisTemplate<Object, Department> deptRedisTemplate(
  15. RedisConnectionFactory redisConnectionFactory)
  16. throws UnknownHostException {
  17. RedisTemplate<Object, Department> template = new RedisTemplate<Object, Department>();
  18. template.setConnectionFactory(redisConnectionFactory);
  19. Jackson2JsonRedisSerializer<Department> ser = new Jackson2JsonRedisSerializer<Department>(Department.class);
  20. template.setDefaultSerializer(ser);
  21. return template;
  22. }
  23. // CacheManagerCustomizers可以来定制缓存的一些规则
  24. @Primary // 将某个缓存管理器作为默认的
  25. @Bean
  26. public RedisCacheManager employeeCacheManager(RedisTemplate<Object, Employee> empRedisTemplate){
  27. RedisCacheManager cacheManager = new RedisCacheManager(empRedisTemplate);
  28. // key多了一个前缀
  29. // 使用前缀,默认会将CacheName作为key的前缀
  30. cacheManager.setUsePrefix(true);
  31. return cacheManager;
  32. }
  33. @Bean
  34. public RedisCacheManager deptCacheManager(RedisTemplate<Object, Department> deptRedisTemplate){
  35. RedisCacheManager cacheManager = new RedisCacheManager(deptRedisTemplate);
  36. // key多了一个前缀
  37. // 使用前缀,默认会将CacheName作为key的前缀
  38. cacheManager.setUsePrefix(true);
  39. return cacheManager;
  40. }
  41. }
  1. @Service
  2. public class DeptService {
  3. @Autowired
  4. DepartmentMapper departmentMapper;
  5. @Qualifier("deptCacheManager")
  6. @Autowired
  7. RedisCacheManager deptCacheManager;
  8. /**
  9. * 缓存的数据能存入redis;
  10. * 第二次从缓存中查询就不能反序列化回来;
  11. * 存的是dept的json数据;CacheManager默认使用RedisTemplate<Object, Employee>操作Redis
  12. */
  13. @Cacheable(cacheNames = "dept", cacheManager = "deptCacheManager")
  14. public Department getDeptById(Integer id){
  15. System.out.println("查询部门" + id);
  16. Department department = departmentMapper.getDeptById(id);
  17. return department;
  18. }
  19. // 使用缓存管理器得到缓存,进行api调用
  20. public Department getDeptById(Integer id){
  21. System.out.println("查询部门" + id);
  22. Department department = departmentMapper.getDeptById(id);
  23. //获取某个缓存
  24. Cache dept = deptCacheManager.getCache("dept");
  25. dept.put("dept:1",department);
  26. return department;
  27. }
  28. }

image.png

二、Spring Boot与消息
三、Spring Boot与检索

四、Spring Boot与任务

1、异步任务

在Java应用中,绝大多数情况下都是通过同步的方式来实现交互处理的;但是在处理与第三方系统交互的时候,容易造成响应迟缓的情况,之前大部分都是使用多线程来完成此类任务,其实,在Spring 3.x之后,就已经内置了@Async来完美解决这个问题。
两个注解: @EnableAysnc、@Aysnc

  1. @EnableAsync //开启异步注解功能
  2. @SpringBootApplication
  3. public class Springboot04TaskApplication {
  4. public static void main(String[] args) {
  5. SpringApplication.run(Springboot04TaskApplication.class, args);
  6. }
  7. }
  1. @RestController
  2. public class AsyncController {
  3. @Autowired
  4. AsyncService asyncService;
  5. @GetMapping("/hello")
  6. public String hello(){
  7. asyncService.hello();
  8. return "success";
  9. }
  10. }
  11. @Service
  12. public class AsyncService {
  13. // 告诉Spring这是一个异步方法,若是同步执行,controller会等待service方法执行完毕后才返回数据
  14. @Async
  15. public void hello(){
  16. try {
  17. Thread.sleep(3000);
  18. } catch (InterruptedException e) {
  19. e.printStackTrace();
  20. }
  21. System.out.println("处理数据中...");
  22. }
  23. }

image.png

2、定时任务

项目开发中经常需要执行一些定时任务,比如需要在每天凌晨时候,分析一次前一天的日志信息。Spring为我们提供了异步执行任务调度的方式,提供TaskExecutor 、TaskScheduler 接口。
两个注解:@EnableScheduling、@Scheduled
cron表达式:
image.pngimage.png

  1. @EnableScheduling // 开启基于注解的定时任务
  2. @SpringBootApplication
  3. public class Springboot04TaskApplication {
  4. public static void main(String[] args) {
  5. SpringApplication.run(Springboot04TaskApplication.class, args);
  6. }
  7. }
  1. @Service
  2. public class ScheduledService {
  3. /**
  4. * second(秒), minute(分), hour(时), day of month(日), month(月), day of week(周几).
  5. * 0 * * * * MON-FRI
  6. * 【0 0/5 14,18 * * ?】 每天14点整,和18点整,每隔5分钟执行一次
  7. * 【0 15 10 ? * 1-6】 每个月的周一至周六10:15分执行一次
  8. * 【0 0 2 ? * 6L】每个月的最后一个周六凌晨2点执行一次
  9. * 【0 0 2 LW * ?】每个月的最后一个工作日凌晨2点执行一次
  10. * 【0 0 2-4 ? * 1#1】每个月的第一个周一凌晨2点到4点期间,每个整点都执行一次;
  11. */
  12. // @Scheduled(cron = "0 * * * * MON-SAT")
  13. // @Scheduled(cron = "0,1,2,3,4 * * * * MON-SAT") 周一到周六的每分钟的前四秒执行一次
  14. // @Scheduled(cron = "0-4 * * * * MON-SAT")
  15. @Scheduled(cron = "0/4 * * * * MON-SAT") // 每4秒执行一次
  16. public void hello(){
  17. System.out.println("hello ... ");
  18. }
  19. }

image.png

3、邮件任务

1)邮件发送需要引入spring-boot-starter-mail

  1. <dependency>
  2. <groupId>org.springframework.boot</groupId>
  3. <artifactId>spring-boot-starter-mail</artifactId>
  4. </dependency>

2)Spring Boot自动配置MailSenderAutoConfiguration
image.png
3)定义MailProperties内容,配置在application.yml中

  1. # 发件人邮箱用户名
  2. spring.mail.username=534096094@qq.com
  3. # 发件人邮箱授权码
  4. spring.mail.password=gtstkoszjelabijb
  5. # 发件人邮箱主机地址
  6. spring.mail.host=smtp.qq.com
  7. # 开启ssl,保证可靠连接,否则报错
  8. spring.mail.properties.mail.smtp.ssl.enable=true

image.png
4)自动装配JavaMailSender

  1. @Autowired
  2. JavaMailSenderImpl mailSender;

5)测试邮件发送

  1. @RunWith(SpringRunner.class)
  2. @SpringBootTest
  3. public class Springboot04TaskApplicationTests {
  4. @Autowired
  5. JavaMailSenderImpl mailSender;
  6. // 简单邮件发送
  7. @Test
  8. public void contextLoads() {
  9. SimpleMailMessage message = new SimpleMailMessage();
  10. // 邮件设置
  11. message.setSubject("通知-今晚开会");
  12. message.setText("今晚7:30开会");
  13. message.setTo("17512080612@163.com");
  14. message.setFrom("534096094@qq.com");
  15. mailSender.send(message);
  16. }
  17. @Test
  18. public void test02() throws Exception{
  19. // 1、创建一个复杂的消息邮件
  20. MimeMessage mimeMessage = mailSender.createMimeMessage();
  21. // 声明是否上传附件
  22. MimeMessageHelper helper = new MimeMessageHelper(mimeMessage, true);
  23. // 邮件设置
  24. helper.setSubject("通知-今晚开会");
  25. helper.setText("<b style='color:red'>今天 7:30 开会</b>", true);
  26. helper.setTo("17512080612@163.com");
  27. helper.setFrom("534096094@qq.com");
  28. // 上传文件
  29. helper.addAttachment("1.jpg",new File("D:\\\\1.jpg"));
  30. helper.addAttachment("2.jpg",new File("D:\\\\2.jpg"));
  31. mailSender.send(mimeMessage);
  32. }
  33. }

五、Spring Boot与安全
六、Spring Boot与分布式
七、Spring Boot与监控管理
八、Spring Boot与部署