P1M4_Springboot

139 阅读6分钟

文章内容输出来源:拉钩教育Java高薪训练营

SpringBoot

基础回顾

概念

  • 快速的使用spring的一种方式,并且不需要编写大量的配置文件。基于约定优于配置的思想。

  • Spring优缺点

    • 优点:J2EE的轻量级代替品,IoC,AOP
    • 缺点:配置繁琐;依赖管理复杂
  • SpringBoot

    • 起步依赖:将具备某种功能的坐标打包到一起,并提供一些默认的功能。
    • 自动配置:自动将一些配置类注册进ioc容器,需要时直接使用@autowired或者@resource注解。

案例实现

image-20201026211801578

image-20201026212110665

包扫描:项目主程序启动类所在包及其子包

image-20201026213108976

单元测试与热部署

  • 添加 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>
    

    image-20201026215406283

    image-20201026215605336

全局配置文件

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

原理深入及源码分析

依赖管理

  1. 为什么导入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>
      
  2. 项目运行依赖的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加载器进行一系列的容器创建过程。

image-20201029214540645

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();
}

image-20201030214250894

调用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

注解方式

gitee.com/carol7/repo…

配置文件

gitee.com/carol7/repo…

Spring Boot整合JPA

gitee.com/carol7/repo…

Spring Boot整合Redis

gitee.com/carol7/repo…

视图技术

  • 支持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子目录

页面展示

gitee.com/carol7/repo…

配置国际化

  • 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.Cacheorg.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();
}