SpringBoot总结(二)

456 阅读23分钟

1. SpringBoot 整合 定时任务

1.1 常见cron表达式查询网站

(1) cron.qqe2.com/

注意:这里要注意,这个工具 '年' 是不支持的。cron位数严格要求为6位。

image.png

(2) crontab.guru

1.2 常见cron表达式

image.png

1.3 整合 定时任务task

SpringBoot整合定时任务task非常的简单,共分为以下三步:

  1. 在启动类加上@EnableScheduling注解

  2. 在controller的类上加上@Component注解

  3. 在controller的方法上加上@Scheduled注解即可

之后启动程序,就会自动开始执行任务了。

(1) 首先启动类上添加注解 @EnableScheduling 来 开启定时任务

/*开启定时任务*/
@EnableScheduling
@SpringBootApplication
@MapperScan(basePackages = "com.learn.tm.mapper")
public class TianmengApplication {
    public static void main(String[] args) {
        SpringApplication.run(TianmengApplication.class, args);
    }
}

(2) 然后要定时执行的方法上添加 @Scheduled 注解并指定定时规则

/*每隔3s执行一次*/
@Scheduled(fixedRate = 3000)
public void cronJobs(){
    System.out.println(new Date());
}

1.4 整合 XXL-JOB

官网地址:www.xuxueli.com/xxl-job/

源码地址 :

github.com/xuxueli/xxl…

gitee.com/xuxueli0323…

1.4.1 xxl-job单独服务

1.4.1.1 下载

首先打开官网地址,开始下载源码,即上面的源码地址,哪个都可以。下载之后使用idea工具打开,项目结构如下:

image.png

xxl-job-admin:调度中心
xxl-job-core:公共依赖
xxl-job-executor-samples:执行器Sample示例(选择合适的版本执行器,可直接使用,也可以参考其并将现有项目改造成执行器)
    :xxl-job-executor-sample-springboot:Springboot版本,通过Springboot管理执行器,推荐这种方式;
    :xxl-job-executor-sample-frameless:无框架版本;

1.4.1.2 执行sql文件

文件路径 : doc\db\tables_xxl_job.sql

执行完成后会生成如下数据库和表 :

image.png

XXL-JOB调度模块基于自研调度组件并支持集群部署,调度数据库表说明如下:

  • xxl_job_lock:任务调度锁表;
  • xxl_job_group:执行器信息表,维护任务执行器信息;
  • xxl_job_info:调度扩展信息表: 用于保存XXL-JOB调度任务的扩展信息,如任务分组、任务名、机器地址、执行器、执行入参和报警邮件等等;
  • xxl_job_log:调度日志表: 用于保存XXL-JOB任务调度的历史信息,如调度结果、执行结果、调度入参、调度机器和执行器等等;
  • xxl_job_log_report:调度日志报表:用户存储XXL-JOB任务调度日志的报表,调度中心报表功能页面会用到;
  • xxl_job_logglue:任务GLUE日志:用于保存GLUE更新历史,用于支持GLUE的版本回溯功能;
  • xxl_job_registry:执行器注册表,维护在线的执行器和调度中心机器地址信息;
  • xxl_job_user:系统用户表;

1.4.1.3 修改配置文件

# 首先修改xxl-job-admin模块下的application.properties配置
    修改端口号:
        server.port=8090
    修改数据库连接账号密码:
        spring.datasource.url=jdbc:mysql://192.168.198.100:3306/xxl_job
        spring.datasource.username=root
        spring.datasource.password=123456

# 然后修改xxl-job-executor-samples下的xxl-job-executor-sample-springboot模块下的application.properties配置
    修改端口号:
        server.port=8091
    修改xxl.job.admin.addresses配置:
        xxl.job.admin.addresses=http://127.0.0.1:8090/xxl-job-admin
    修改xxl.job.executor.port端口号:(不冲突可不修改)
        xxl.job.executor.port=9999

1.4.1.4 添加测试方法

xxl-job-executor-samples下的xxl-job-executor-sample-springboot模块下的SampleXxlJob类添加测试方法

@XxlJob("testJobHandler")
public void testJobHandler(){
    System.out.println("测试定时任务");
}

1.4.1.5 测试

启动2个服务xxl-job-adminxxl-job-executor-sample-springboot

浏览器访问 : http://localhost:8090/xxl-job-admin/ 进入到登录页面 默认账号密码为admin 123456

image.png

登录进来效果 :

image.png

新建定时任务 :

JobHandler配置的内容就是@XxlJob("testJobHandler")注解里的testJobHandler

image.png

配置完成后,点击启动,等到整分钟的时候,就可以看到控制台打印日志了,并且也可以通过系统查看日志:

image.png

1.4.2 xxl-job整合到已有springboot服务

1.4.2.1 添加配置信息

在已有服务的yml配置文件中添加如下内容 :

这里注意几个点

(1) accessToken 默认值为 default_token ,不填写可能会有问题

(2) appname 这里我自定义了一个,后面需要在页面上配置一下,否则会导致定时任务不生效,名称暂时定为 xxl-job-SpringBoot-learn

(3) addresses 后面配置的信息需要跟一会启动的xxl-job-admin服务保持一致。

xxl:
  job:
    admin:
      # 调度中心部署跟地址 [选填]:如调度中心集群部署存在多个地址则用逗号分隔。
      addresses: http://127.0.0.1:8090/xxl-job-admin
    # 执行器通讯TOKEN [选填]:非空时启用
    accessToken: default_token
    executor:
      # 执行器的应用名称
      appname: xxl-job-SpringBoot-learn
      # 执行器注册 [选填]:优先使用该配置作为注册地址
      address: ""
      # 执行器IP [选填]:默认为空表示自动获取IP
      ip: ""
      # 执行器端口号 [选填]:小于等于0则自动获取;默认端口为9999
      port: 9999
      # 执行器运行日志文件存储磁盘路径 [选填] :需要对该路径拥有读写权限;为空则使用默认路径;
      logpath: /data/applogs/xxl-job/jobhandler
      # 执行器日志文件保存天数 [选填] : 过期日志自动清理, 限制值大于等于3时生效; 否则, 如-1, 关闭自动清理功能;
      logretentiondays: -1

1.4.2.2 新增依赖

在已有服务中添加如下依赖 :

<!-- xxl-job-core -->
<dependency>
   <groupId>com.xuxueli</groupId>
    <artifactId>xxl-job-core</artifactId>
    <version>2.3.1</version>
</dependency>

1.4.2.3 新增配置文件

在已有服务中添加如下配置 :

import com.xxl.job.core.executor.impl.XxlJobSpringExecutor;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class XxlJobConfig {
    private Logger logger = LoggerFactory.getLogger(XxlJobConfig.class);

    @Value("${xxl.job.admin.addresses}")
    private String adminAddresses;

    @Value("${xxl.job.accessToken}")
    private String accessToken;

    @Value("${xxl.job.executor.appname}")
    private String appname;

    @Value("${xxl.job.executor.address}")
    private String address;

    @Value("${xxl.job.executor.ip}")
    private String ip;

    @Value("${xxl.job.executor.port}")
    private int port;

    @Value("${xxl.job.executor.logpath}")
    private String logPath;

    @Value("${xxl.job.executor.logretentiondays}")
    private int logRetentionDays;

    @Bean
    public XxlJobSpringExecutor xxlJobExecutor() {
        logger.info(">>>>>>>>>>> xxl-job config init.");
        XxlJobSpringExecutor xxlJobSpringExecutor = new XxlJobSpringExecutor();
        xxlJobSpringExecutor.setAdminAddresses(adminAddresses);
        xxlJobSpringExecutor.setAppname(appname);
        xxlJobSpringExecutor.setAddress(address);
        xxlJobSpringExecutor.setIp(ip);
        xxlJobSpringExecutor.setPort(port);
        xxlJobSpringExecutor.setAccessToken(accessToken);
        xxlJobSpringExecutor.setLogPath(logPath);
        xxlJobSpringExecutor.setLogRetentionDays(logRetentionDays);

        return xxlJobSpringExecutor;
    }
}

1.4.2.4 添加测试方法

在已有服务中添加如下测试方法

@XxlJob("testJobHandler")
public void testJobHandler() throws InterruptedException {
    System.out.println("进入测试方法");
}

1.4.2.5 配置执行器

浏览器访问 : http://localhost:8090/xxl-job-admin 默认账号密码为 admin 123456

首先在执行器管理中新增一个执行器 :

image.png

然后任务管理中新增一个任务,注意执行器一定要选择正确,否则会一直报执行器为空,导致定时任务不能正常执行。

image.png

一切都完成后,启动两个服务 : 已有服务xxl-job-admin 服务,可以看到控制台打印的日志内容。

具体xxl-job-admin的源码下载,参考上面即可。

2. SpringBoot 整合 redis

在 springboot 1.5.x版本的默认的Redis客户端是 Jedis实现的,springboot 2.x版本中默认客户端是用 lettuce实现的

lettcus与jedis的区别:

(1)jedis:采用的直连,多个线程操作的话,是不安全的。如果要避免不安全,使用jedis pool连接池。更像BIO模式。但是这样整体性能就大受影响。

(2)lettuce基于Netty框架进行与Redis服务器连接,实例可以在多个线程中共享,不存在线程不安全的情况。可以减少线程数据了,更像NIO模式

2.1 redisTemplate

2.1.1 添加依赖

<!--redis依赖-->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>

2.1.2 创建配置文件

package com.learn.tm.config;

import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;

@Configuration
public class RedisConfig {
    @Bean
    @SuppressWarnings("all")
    public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory factory) {
        RedisTemplate<String, Object> template = new RedisTemplate<String, Object>();
        template.setConnectionFactory(factory);
        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);
        StringRedisSerializer stringRedisSerializer = new StringRedisSerializer();
        // key采用String的序列化方式
        template.setKeySerializer(stringRedisSerializer);
        // hash的key也采用String的序列化方式
        template.setHashKeySerializer(stringRedisSerializer);
        // value序列化方式采用jackson
        template.setValueSerializer(jackson2JsonRedisSerializer);
        // hash的value序列化方式采用jackson
        template.setHashValueSerializer(jackson2JsonRedisSerializer);
        template.afterPropertiesSet();
        return template;
    }
}

2.1.3 添加配置信息

spring:
  redis:
    # Redis数据库索引(默认为0)
    database: 0
    # Redis服务器地址(根据业务对应修改)
    host: 192.168.198.100
    # Redis服务器连接端口
    port: 6379
    # Redis服务器连接密码(默认为空)
    # password: 123456
    # 连接超时时间(毫秒)
    timeout: 1000
    lettuce:
      pool:
        # 连接池中的最小空闲连接
        min-idle: 0
        # 连接池最大连接数(使用负值表示没有限制)
        max-active: 200
        # 连接池中的最大空闲连接
        max-idle: 10
        # 连接池最大阻塞等待时间(使用负值表示没有限制)
        max-wait: -1

2.1.4 测试

@Autowired
private RedisTemplate<String,String> redisTemplate;//这里要注意泛型的类型
    
@GetMapping("getMsg")
public Result<String> getMsg(){
    redisTemplate.opsForValue().set("k","v");
    String k = redisTemplate.opsForValue().get("k");
    System.out.println(k);//输出v
    //"code":0,"msg":"操作成功","data":"v"}
    return new Result<String>().ok(k);
}

2.2 jedis

因为 springboot2.0中默认是使用 Lettuce来集成Redis服务,spring-boot-starter-data-redis默认只引入了 Lettuce包,并没有引入 jedis包支持。所以在我们需要手动引入 jedis的包,并排除掉 lettuce的包

2.2.1 添加依赖

<dependency>
    <groupId>redis.clients</groupId>
    <artifactId>jedis</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis</artifactId>
    <exclusions>
        <exclusion>
            <groupId>io.lettuce</groupId>
            <artifactId>lettuce-core</artifactId>
        </exclusion>
    </exclusions>
</dependency>

2.2.2 添加配置信息

spring:
  redis:
    # Redis数据库索引(默认为0)
    database: 0
    # Redis服务器地址(根据业务对应修改)
    host: 192.168.198.100
    # Redis服务器连接端口
    port: 6379
    # Redis服务器连接密码(默认为空)
	# password: 123456
    # 连接超时时间(毫秒)
    timeout: 1000
    jedis:
      pool:
        # 连接池中的最小空闲连接
        min-idle: 0
        # 连接池最大连接数(使用负值表示没有限制)
        max-active: 8
        # 连接池中的最大空闲连接
        max-idle: 10
        # 连接池最大阻塞等待时间(使用负值表示没有限制)
        max-wait: -1

2.2.3 创建配置文件

因为在 springoot 2.x版本中,默认采用的是 Lettuce实现的,所以无法初始化出 Jedis的连接对象 JedisConnectionFactory,所以我们需要手动配置并注入

这里要提醒的一点就是,在springboot 2.x版本中 JedisConnectionFactory设置连接的方法已过时

在 springboot 2.x版本中推荐使用 RedisStandaloneConfiguration类来设置连接的端口,地址等属性

import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.cache.RedisCacheManager;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.connection.RedisPassword;
import org.springframework.data.redis.connection.RedisStandaloneConfiguration;
import org.springframework.data.redis.connection.jedis.JedisClientConfiguration;
import org.springframework.data.redis.connection.jedis.JedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;
import redis.clients.jedis.JedisPoolConfig;
import java.io.Serializable;
import java.time.Duration;

@Configuration
public class JedisRedisConfig {

   @Value("${spring.redis.database}")
   private int database;
   @Value("${spring.redis.host}")
   private String host;
   @Value("${spring.redis.port}")
   private int port;
   @Value("${spring.redis.password}")
   private String password;
   @Value("${spring.redis.timeout}")
   private int timeout;
   @Value("${spring.redis.jedis.pool.max-active}")
   private int maxActive;
   @Value("${spring.redis.jedis.pool.max-wait}")
   private long maxWaitMillis;
   @Value("${spring.redis.jedis.pool.max-idle}")
   private int maxIdle;
   @Value("${spring.redis.jedis.pool.min-idle}")
   private int minIdle;

   /**
    * 连接池配置信息
    */

   @Bean
   public JedisPoolConfig jedisPoolConfig() {
      JedisPoolConfig jedisPoolConfig = new JedisPoolConfig();
      // 最大连接数
      jedisPoolConfig.setMaxTotal(maxActive);
      // 当池内没有可用连接时,最大等待时间
      jedisPoolConfig.setMaxWaitMillis(maxWaitMillis);
      // 最大空闲连接数
      jedisPoolConfig.setMinIdle(maxIdle);
      // 最小空闲连接数
      jedisPoolConfig.setMinIdle(minIdle);
      // 其他属性可以自行添加
      return jedisPoolConfig;
   }

   /**
    * Jedis 连接
    * 
    * @param jedisPoolConfig
    * @return
    */
   @Bean
   public JedisConnectionFactory jedisConnectionFactory(JedisPoolConfig jedisPoolConfig) {
      JedisClientConfiguration jedisClientConfiguration = JedisClientConfiguration.builder().usePooling()
            .poolConfig(jedisPoolConfig).and().readTimeout(Duration.ofMillis(timeout)).build();
      RedisStandaloneConfiguration redisStandaloneConfiguration = new RedisStandaloneConfiguration();
      redisStandaloneConfiguration.setHostName(host);
      redisStandaloneConfiguration.setPort(port);
      redisStandaloneConfiguration.setPassword(RedisPassword.of(password));
      return new JedisConnectionFactory(redisStandaloneConfiguration, jedisClientConfiguration);
   }

   /**
    * 缓存管理器
    * 
    * @param connectionFactory
    * @return
    */
   @Bean
   public RedisCacheManager cacheManager(RedisConnectionFactory connectionFactory) {
      return RedisCacheManager.create(connectionFactory);
   }

   @Bean
   public RedisTemplate<String, Serializable> redisTemplate(JedisConnectionFactory connectionFactory) {
      RedisTemplate<String, Serializable> redisTemplate = new RedisTemplate<>();
      redisTemplate.setKeySerializer(new StringRedisSerializer());
      redisTemplate.setValueSerializer(new GenericJackson2JsonRedisSerializer());
      redisTemplate.setConnectionFactory(jedisConnectionFactory(jedisPoolConfig()));
      return redisTemplate;
   }

}

2.2.4 测试

@Autowired
private RedisTemplate<String,String> redisTemplate;//这里要注意泛型的类型

@GetMapping("getMsg")
public Result<String> getMsg(){
    redisTemplate.opsForValue().set("k2","v2");
    String k = redisTemplate.opsForValue().get("k2");
    System.out.println(k);//输出v2
    //{"code":0,"msg":"操作成功","data":"v2"}
    return new Result<String>().ok(k);
}

2.3 Redission

2.3.1 添加依赖

<dependency>
    <groupId>org.redisson</groupId>
    <artifactId>redisson</artifactId>
    <version>3.13.6</version>
</dependency>

2.3.2 添加配置类

package com.learn.tm.config;

import org.redisson.Redisson;
import org.redisson.api.RedissonClient;
import org.redisson.config.Config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class RedissonConfig {

    @Bean
    public RedissonClient redissonClient() {
        // 配置信息
        Config config = new Config();
        //地址、密码
//        config.useSingleServer().setAddress("redis://192.168.198.100:6379").setPassword("");
        config.useSingleServer().setAddress("redis://192.168.198.100:6379");

        return Redisson.create(config);
    }
}

2.3.3 测试类

首先调用getMsg方法,获取锁成功,然后休眠5s,在此期间,调用getMsg1方法,发现卡住了,等到getMsg方法执行完毕后,getMsg1方法获取锁继续执行。

@Resource
private RedissonClient redissonClient;

@GetMapping("getMsg")
public Result<String> getMsg() throws InterruptedException {
    //使用 Redission 获取锁对象
    RLock lock = redissonClient.getLock("k");

    //尝试获取锁
    boolean isLock = lock.tryLock(10, TimeUnit.MINUTES);

    if(!isLock){
        return new Result<String>().error("获取锁失败");
    }

    try{
        //获取锁成功->执行业务代码
        System.out.println("获取锁成功->执行业务代码");
        Thread.sleep(5000);
    }catch (Exception e){
        e.printStackTrace();
    }finally {
        lock.unlock();
    }
    return new Result<String>().ok();
}

@GetMapping("getMsg1")
public Result<String> getMsg1() throws InterruptedException {
    //使用 Redission 获取锁对象
    RLock lock = redissonClient.getLock("k");

    //尝试获取锁
    boolean isLock = lock.tryLock(5, TimeUnit.MINUTES);

    if(!isLock){
        return new Result<String>().error("获取锁1失败");
    }

    try{
        //获取锁成功->执行业务代码
        System.out.println("获取锁1成功->执行业务代码");
    }catch (Exception e){
        e.printStackTrace();
    }finally {
        lock.unlock();
    }

    return new Result<String>().ok();
}

结果日志如下:

获取锁成功->执行业务代码
获取锁1成功->执行业务代码

3. SpringBoot 整合 Spring Cache + Redis

@Cacheable

用的最多的注解,功能也很强大,一般用在查询方法上。

可以根据方法对其返回结果进行缓存,下次请求时,如果缓存存在,则直接读取缓存数据返回;如果缓存不存在,则执行方法,并把返回的结果存入缓存中。

说白了,比如我第一次查询缓存操作,如果不存在就去查询数据库,查到数据后返回,并且放到缓存里去,当第二次查询的时候就可以查询缓存里的数据。

查看源码,属性值如下:

  • value:缓存名,必填,它指定了你的缓存存放在哪块命名空间
  • cacheNames:与 value 差不多,二选一即可
  • key:可选属性,可以使用 SpEL 标签自定义缓存的key

@CachePut

一般用在新增方法上。

使用该注解标志的方法,每次都会执行,并将结果存入指定的缓存中。其他方法可以直接从响应的缓存中读取缓存数据,而不需要再去查询数据库。

查看源码,属性值如下:

  • value:缓存名,必填,它指定了你的缓存存放在哪块命名空间
  • cacheNames:与 value 差不多,二选一即可
  • key:可选属性,可以使用 SpEL 标签自定义缓存的key

@CacheEvict

一般用在更新或者删除方法上,使用该注解标志的方法,会清空指定的缓存。

查看源码,属性值如下:

  • value:缓存名,必填,它指定了你的缓存存放在哪块命名空间
  • cacheNames:与 value 差不多,二选一即可
  • key:可选属性,可以使用 SpEL 标签自定义缓存的key
  • allEntries:是否清空所有缓存,默认为 false。如果指定为 true,则方法调用后将立即清空所有的缓存
  • beforeInvocation:是否在方法执行前就清空,默认为 false。如果指定为 true,则在方法执行前就会清空缓存

(1) @CacheConfig:是类级别的注解,统一类的所有缓存key前缀;@CacheConfig(cacheNames = { "user" })代表了该类的所有缓存key值都是"user::"为前缀。第二种方式@CacheConfig(cacheNames = "c1")则意味着从配置文件里取 spring.cache.cache-names: c1对应的c1值

(2) @Cacheable(key="#id"):方法级别的注解,可以将运行结果缓存,以后查询相同的数据,直接从缓存中取,不需要调用方法;id的值作为key。

方法被调用时,先从缓存中读取数据,如果缓存没有找到数据,再执行方法体,最后把返回值添加到缓存中。 调用方法传入id=100,那redis对应的key=user::100 ,value通过采用GenericJackson2JsonRedisSerializer序列化为json

(3) @CachePut(key = "#obj.id"):方法级别的注解,用于更新缓存。

方法被调用时,先执行方法体,然后springcache通过返回值更新缓存,即key = "#obj.id",value=User

(4)@CacheEvict(key = "#id"):方法级别的注解,用于删除缓存。

方法被调用时,先执行方法体,在通过方法参数删除缓存。

注意 :

(1) 对于redis的缓存,SpringCache只支持String,其他的Hash 、List、Set、ZSet都不支持,所以对于Hash 、List、set、ZSet只能用RedisTemplate。

(2) 对于多表查询的数据缓存,SpringCache是不支持的,只支持单表的简单缓存。对于多表的整体缓存,只能用RedisTemplate。

3.1 添加配置文件

spring:
  redis:
    # Redis数据库索引(默认为0)
    database: 0
    # Redis服务器地址(根据业务对应修改)
    host: 192.168.198.100
    # Redis服务器连接端口
    port: 6379
    # Redis服务器连接密码(默认为空)
    # password: 123456
    # 连接超时时间(毫秒)
    timeout: 1000
    lettuce:
      pool:
        # 连接池中的最小空闲连接
        min-idle: 0
        # 连接池最大连接数(使用负值表示没有限制)
        max-active: 200
        # 连接池中的最大空闲连接
        max-idle: 10
        # 连接池最大阻塞等待时间(使用负值表示没有限制)
        max-wait: -1
  cache:
    cache-names: c1 

3.2 添加依赖

<!--redis-->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<!--spring cache-->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-cache</artifactId>
</dependency>
<!--spring cache连接池依赖包-->
<dependency>
    <groupId>org.apache.commons</groupId>
    <artifactId>commons-pool2</artifactId>
    <version>2.6.2</version>
</dependency>

3.3 添加配置类

import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;

@Configuration
public class RedisConfig {
    @Bean
    @SuppressWarnings("all")
    public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory factory) {
        RedisTemplate<String, Object> template = new RedisTemplate<String, Object>();
        template.setConnectionFactory(factory);
        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);
        StringRedisSerializer stringRedisSerializer = new StringRedisSerializer();
        // key采用String的序列化方式
        template.setKeySerializer(stringRedisSerializer);
        // hash的key也采用String的序列化方式
        template.setHashKeySerializer(stringRedisSerializer);
        // value序列化方式采用jackson
        template.setValueSerializer(jackson2JsonRedisSerializer);
        // hash的value序列化方式采用jackson
        template.setHashValueSerializer(jackson2JsonRedisSerializer);
        template.afterPropertiesSet();
        return template;
    }
}

3.4 测试类

这里要注意 : 实体类需要实现Serializable,否则可能会有报错。

import com.learn.tm.entity.User;
import com.learn.tm.exception.Result;
import com.learn.tm.service.UserService;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cache.annotation.CacheConfig;
import org.springframework.cache.annotation.CacheEvict;
import org.springframework.cache.annotation.CachePut;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.web.bind.annotation.*;

@Slf4j
@RestController
@RequestMapping("/tm")
@Api(tags = "用户测试类")
@CacheConfig(cacheNames = {"user"})
//@CacheConfig(cacheNames = "c1")
public class TestController {

    @Autowired
    private UserService userService;

    @GetMapping("findUserById")
    @Cacheable(key="#id")
    @ApiOperation(value = "Cacheable注解测试",notes = "")
    public Result<User> findUserById(Integer id){
        return new Result<User>().ok(userService.getById(id));
    }

    @PutMapping("updateUser")
    @CachePut(key="#user.id")
    @ApiOperation(value = "CachePut注解测试",notes = "")
    public Result<User> updateUser(@RequestBody User user){
        userService.updateById(user);
        return new Result<User>().ok(userService.getById(user.getId()));
    }

    @DeleteMapping("deleteUser")
    @CacheEvict(key="#id")
    @ApiOperation(value = "CacheEvict注解测试",notes = "")
    public Result<User> deleteUser(Integer id){
        userService.removeById(id);
        return new Result<User>().ok(userService.getById(id));
    }
}


@Data
@TableName("user")
@ApiModel
public class User implements Serializable {
    @ApiModelProperty(position = 0,value = "用户ID")
    @TableId(value = "id",type = IdType.AUTO)
    private Long id;
    @ApiModelProperty(position = 1,value = "用户姓名")
    @TableField("name")
    private String name;
    @ApiModelProperty(position = 2,value = "用户年龄")
    private Long age;
    @ApiModelProperty(position = 3,value = "用户电话号")
    private String tel;
    @ApiModelProperty(position = 4,value = "用户性别")
    private Long sex;
    @ApiModelProperty(position = 5,value = "用户地址")
    private String address;
    @ApiModelProperty(position = 6,value = "用户邮箱")
    private String email;
    @ApiModelProperty(position = 7,value = "用户生日")
    private Date birthday;
}

启动类需要加上@EnableCaching注解

3.5 效果

首先调用findUserById方法,第一次进入断点,第二次不会进入,使用工具查看redis,可以看到成功缓存,对应的key就是@CacheConfig(cacheNames = {"user"})里配置的user加上两个冒号::再加上@Cacheable(key="#id")对应的参数id值

image.png

image.png

更新这里就不强调了,对于删除来说,执行完方法后,可以看到redis中对应的缓存也消失了。

image.png

image.png

3.6 注意

如果通过ID查询出来的结果是null,默认是还会缓存的,这样其实就会出现问题了。处理的办法就是注解里加上unless="#result == null"配置。

但是这里还要注意一点,就是下面方法1其实还是会缓存的,方法二不会,这是因为方法二userService.getById(id)结果是null 而方法一结果是{"code": 0,"msg": "操作成功","data": null}。所以可以看出,缓存实际上是根据方法的返回值来判断的。所以我们在使用对应注解的方法上,尽量让返回值是我们要缓存的内容,而不是经过包装后的内容。

# 方法1:
@GetMapping("findUserById")
@Cacheable(key="#id",unless="#result == null")
@ApiOperation(value = "Cacheable注解测试",notes = "")
public Result<User> findUserById(Integer id){
	return new Result<User>().ok(userService.getById(id));
}
# 方法2:
@GetMapping("findUserById")
@Cacheable(key="#id",unless="#result == null")
@ApiOperation(value = "Cacheable注解测试",notes = "")
public User findUserById(Integer id){
	return userService.getById(id);
}

4. SpringBoot 整合 Spring Cache + Ehcache

4.1 添加依赖

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-cache</artifactId>
</dependency>
<!--spring cache连接池依赖包-->
<dependency>
    <groupId>org.apache.commons</groupId>
    <artifactId>commons-pool2</artifactId>
    <version>2.6.2</version>
</dependency>
<dependency>
    <groupId>net.sf.ehcache</groupId>
    <artifactId>ehcache</artifactId>
    <version>2.10.6</version>
</dependency>

4.2 新增ehcache.xml配置文件

路径为 : src/main/resources/ehcache.xml 默认 spring boot 就会在 resources 目录下扫描到 ehcache 所以也不需要在 application.yml 中单独配置。

默认情况下,这个文件名是固定的,必须叫 ehcache.xml ,如果一定要换一个名字,那么需要在 application.yml 中明确指定配置文件名,配置方式如下:spring.cache.ehcache.config=classpath:aaa.xml

<?xml version="1.0" encoding="UTF-8"?>
<ehcache xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:noNamespaceSchemaLocation="https://www.ehcache.org/ehcache.xsd"
         updateCheck="false">

    <!-- 磁盘缓存位置 -->
    <diskStore path="java.io.tmpdir" />

    <!-- 默认缓存 -->
    <defaultCache
            maxElementsInMemory="10000"
            eternal="false"
            timeToIdleSeconds="120"
            timeToLiveSeconds="120"
            overflowToDisk="false"
            diskPersistent="false"
            diskExpiryThreadIntervalSeconds="120"
    />
    <!-- 测试 -->
    <cache name="user"
           maxElementsInMemory="10000"
           eternal="true"
           overflowToDisk="true"
           diskPersistent="true"
           diskExpiryThreadIntervalSeconds="600"/>

</ehcache>

参数配置含义:

name:缓存名称。

maxElementsInMemory:缓存最大个数。

eternal:对象是否永久有效,一但设置了,timeout将不起作用。

timeToIdleSeconds 当缓存闲置n秒后销毁.仅当eternal=false对象不是永久有效时使用,可选属性,默认值是0,也就是可闲置时间无穷大。

timeToLiveSeconds 当缓存存活n秒后销毁.最大时间介于创建时间和失效时间之间。仅当eternal=false对象不是永久有效时使用,默认是0.,也就是对象存活时间无穷大。

overflowToDisk:当内存中对象数量达到maxElementsInMemory时,Ehcache将会对象写到磁盘中。

diskSpoolBufferSizeMB:这个参数设置DiskStore(磁盘缓存)的缓存区大小。默认是30MB。每个Cache都应该有自己的一个缓冲区。

diskPersistent:是否缓存虚拟机重启期数据

diskExpiryThreadIntervalSeconds:磁盘失效线程运行时间间隔,默认是120秒。

memoryStoreEvictionPolicy:当达到maxElementsInMemory限制时,Ehcache将会根据指定的策略去清理内存。默认策略是LRU(最近最少使用)。你可以设置为FIFO(先进先出)或是LFU(较少使用)。

clearOnFlush:内存数量最大时是否清除。

4.3 启动类添加注解

启动类上添加注解 @EnableCaching

4.4 测试类

这里要注意 :

(1) @CacheConfig(cacheNames = {"user"})里的user要与ehcache.xml里配置的保持一致

(2) 如果没有在类上配置@CacheConfig(cacheNames = {"user"}),那么需要在每个方法上的缓存注解里添加value属性指定缓存名称,比如 @Cacheable(value="user",key="#id",unless="#result == null")。其他用法与cache + redis组合一样

@Slf4j
@RestController
@RequestMapping("/tm")
@Api(tags = "用户测试类")
@CacheConfig(cacheNames = {"user"})
public class TestController {

    @Autowired
    private UserService userService;

    @GetMapping("findUserById")
    @Cacheable(key="#id",unless="#result == null")
    @ApiOperation(value = "Cacheable注解测试",notes = "")
    public Result<User> findUserById(Integer id){
        return new Result<User>().ok(userService.getById(id));
    }

    @PutMapping("updateUser")
    @CachePut(key="#user.id")
    @ApiOperation(value = "CachePut注解测试",notes = "")
    public Result<User> updateUser(@RequestBody User user){
        userService.updateById(user);
        return new Result<User>().ok(userService.getById(user.getId()));
    }

    @DeleteMapping("deleteUser")
    @CacheEvict(key="#id")
    @ApiOperation(value = "CacheEvict注解测试",notes = "")
    public Result<User> deleteUser(Integer id){
        userService.removeById(id);
        return new Result<User>().ok(userService.getById(id));
    }

5. SpringBoot 整合 swagger2

5.1 添加依赖

这里要注意:一定要保证springboot版本不能太高,版本太高,缺失相应的依赖,启动会报错。

Failed to start bean 'documentationPluginsBootstrapper'; nested exception is java.lang.NullPointerException

目前确定的是2.5.3可以,2.6.x,2.7.x都不可以

<dependency>
    <groupId>io.springfox</groupId>
    <artifactId>springfox-swagger2</artifactId>
    <version>2.7.0</version>
</dependency>
<dependency>
    <groupId>io.springfox</groupId>
    <artifactId>springfox-swagger-ui</artifactId>
    <version>2.7.0</version>
</dependency>

5.2 添加配置文件

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import springfox.documentation.builders.ApiInfoBuilder;
import springfox.documentation.builders.PathSelectors;
import springfox.documentation.builders.RequestHandlerSelectors;
import springfox.documentation.service.Contact;
import springfox.documentation.spi.DocumentationType;
import springfox.documentation.spring.web.plugins.Docket;
import springfox.documentation.swagger2.annotations.EnableSwagger2;

@Configuration
@EnableSwagger2
public class SwaggerConfig {
    @Bean
    public Docket createRestApi() {
        return new Docket(DocumentationType.SWAGGER_2)
                .pathMapping("/")
                .select()
                .apis(RequestHandlerSelectors.basePackage("com.learn.tm.controller"))
                .paths(PathSelectors.any())
                .build().apiInfo(new ApiInfoBuilder()
                        .title("SpringBoot整合Swagger2测试")
                        .description("SpringBoot整合Swagger,详细信息......")
                        .version("1.0")
                        .contact(new Contact("xxx系统","www.xxx.com","tm@gmail.com"))
                        .license("")
                        .licenseUrl("")
                        .build());
    }
}

5.3 测试类

浏览器访问:http://localhost:8080/swagger-ui.html

@Data
@TableName("user")
@ApiModel
public class User {
    @ApiModelProperty(position = 0,value = "用户ID")
    @TableId(value = "id",type = IdType.AUTO)
    private Long id;
    @ApiModelProperty(position = 1,value = "用户姓名")
    @TableField("name")
    private String name;
    @ApiModelProperty(position = 2,value = "用户年龄")
    private Long age;
    @ApiModelProperty(position = 3,value = "用户电话号")
    private String tel;
    @ApiModelProperty(position = 4,value = "用户性别")
    private Long sex;
    @ApiModelProperty(position = 5,value = "用户地址")
    private String address;
    @ApiModelProperty(position = 6,value = "用户邮箱")
    private String email;
    @ApiModelProperty(position = 7,value = "用户生日")
    private Date birthday;
}

@Slf4j
@RestController
@RequestMapping("/tm")
@Api(tags = "用户测试类")
public class TestController {

    @ApiOperation(value = "GET无参方法测试",notes = "")
    @GetMapping("getMsg")
    public Result<String> getMsg(){
        return new Result<String>().ok();
    }

    @GetMapping("getParamMsg")
    @ApiOperation(value = "GET有参方法测试",notes = "根据姓名和电话号确认用户信息")
    @ApiImplicitParams({
            @ApiImplicitParam(name = "name",value = "用户名",required = true,dataType = "String"),
            @ApiImplicitParam(name = "tel",value = "电话号",required = true,dataType = "Long")
    })
    public Result<String> getParamMsg(String name,Long tel){
        return new Result<String>().ok(name+tel);
    }

    @PostMapping("postMsg")
    @ApiOperation(value = "POST方法测试",notes = "处理用户信息")
    @ApiImplicitParam(name = "user",value = "用户实体类user",required = true,dataType = "User")
    public Result<String> postMsg(@RequestBody User user){
        return new Result<String>().ok();
    }
}

5.4 效果

image.png 接口1: image.png 接口2: image.png 接口3: image.png

6. SpringBoot 整合 Knife4j

6.1 添加依赖

<dependency>
    <groupId>com.github.xiaoymin</groupId>
    <artifactId>knife4j-spring-boot-starter</artifactId>
    <version>3.0.3</version>
</dependency>

6.2 添加配置类

import com.github.xiaoymin.knife4j.spring.annotations.EnableKnife4j;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import springfox.documentation.builders.ApiInfoBuilder;
import springfox.documentation.builders.PathSelectors;
import springfox.documentation.builders.RequestHandlerSelectors;
import springfox.documentation.service.ApiInfo;
import springfox.documentation.service.Contact;
import springfox.documentation.spi.DocumentationType;
import springfox.documentation.spring.web.plugins.Docket;
import springfox.documentation.swagger2.annotations.EnableSwagger2;

@Configuration
@EnableSwagger2
@EnableKnife4j
public class Knife4jConfiguration {

    @Bean
    public Docket createRestApi() {
        return new Docket(DocumentationType.SWAGGER_2)
                .useDefaultResponseMessages(false)
                .apiInfo(apiInfo())
                .select()
                .apis(RequestHandlerSelectors.basePackage("com.learn.tm.controller"))
                .paths(PathSelectors.any())
                .build();

    }

    private ApiInfo apiInfo() {
        return new ApiInfoBuilder()
                .description("Kinfe4j 集成测试文档")
                .contact(new Contact("TM", "https://juejin.cn/user/3167065128057870", "xxx@qq.com"))
                .version("v1.1.0")
                .title("API测试文档")
                .build();
    }
}

6.3 测试类

浏览器访问:http://localhost:8080/doc.html#/

@Data
@TableName("user")
@ApiModel
public class User {
    @ApiModelProperty(position = 0,value = "用户ID")
    @TableId(value = "id",type = IdType.AUTO)
    private Long id;
    @ApiModelProperty(position = 1,value = "用户姓名")
    @TableField("name")
    private String name;
    @ApiModelProperty(position = 2,value = "用户年龄")
    private Long age;
    @ApiModelProperty(position = 3,value = "用户电话号")
    private String tel;
    @ApiModelProperty(position = 4,value = "用户性别")
    private Long sex;
    @ApiModelProperty(position = 5,value = "用户地址")
    private String address;
    @ApiModelProperty(position = 6,value = "用户邮箱")
    private String email;
    @ApiModelProperty(position = 7,value = "用户生日")
    private Date birthday;
}

@Slf4j
@RestController
@RequestMapping("/tm")
@Api(tags = "用户测试类")
public class TestController {

    @ApiOperation(value = "GET无参方法测试",notes = "")
    @GetMapping("getMsg")
    public Result<String> getMsg(){
        return new Result<String>().ok();
    }

    @GetMapping("getParamMsg")
    @ApiOperation(value = "GET有参方法测试",notes = "根据姓名和电话号确认用户信息")
    @ApiImplicitParams({
            @ApiImplicitParam(name = "name",value = "用户名",required = true,dataType = "String"),
            @ApiImplicitParam(name = "tel",value = "电话号",required = true,dataType = "Long")
    })
    public Result<String> getParamMsg(@RequestParam(value = "name") String name,@RequestParam(value = "tel") Long tel){
        return new Result<String>().ok();
    }

    @PostMapping("postMsg")
    @ApiOperation(value = "POST方法测试",notes = "处理用户信息")
    @ApiImplicitParam(name = "user",value = "用户实体类user",required = true,dataType = "User")
    public Result<String> postMsg(@RequestBody User user){
        return new Result<String>().ok();
    }

}

6.4 效果

实体类:

image.png

接口:

image.png

7. SpringBoot 启动后执行方法

7.1 注解@PostConstruct

使用注解@PostConstruct是最常见的一种方式,存在的问题是如果执行的方法耗时过长,会导致项目在方法执行期间无法提供服务。

import org.springframework.stereotype.Component;
import javax.annotation.PostConstruct;

@Component
public class ApplicationStartExecute {
    
    @PostConstruct
    public void init(){
        //这里如果方法执行过长会导致项目一直无法提供服务
        System.out.println("=======PostConstruct=======");
    }
}

7.2 实现CommandLineRunner接口

实现CommandLineRunner接口 然后在run方法里面调用需要调用的方法即可,好处是方法执行时,项目已经初始化完毕,是可以正常提供服务的。

同时该方法也可以接受参数,可以根据项目启动时: java -jar demo.jar arg1 arg2 arg3 传入的参数进行一些处理。

import org.springframework.boot.CommandLineRunner;
import org.springframework.stereotype.Component;

@Component
public class CommandLineRunnerImpl implements CommandLineRunner {
    
    @Override
    public void run(String... args) throws Exception {
        System.out.println("=======CommandLineRunner=======");
    }
}

7.3 实现ApplicationRunner接口

实现ApplicationRunner接口和实现CommandLineRunner接口基本是一样的。

唯一的不同是启动时传参的格式,CommandLineRunner对于参数格式没有任何限制,ApplicationRunner接口参数格式必须是:–key=value

import java.util.Set;
import java.util.List;
import org.springframework.boot.ApplicationArguments;
import org.springframework.boot.ApplicationRunner;
import org.springframework.stereotype.Component;

@Component
public class ApplicationRunnerImpl implements ApplicationRunner {

    @Override
    public void run(ApplicationArguments args) throws Exception {
        Set<String> optionNames = args.getOptionNames();
        for (String optionName : optionNames) {
            List<String> values = args.getOptionValues(optionName);
            System.out.println(values.toString());
        }
        System.out.println("=======ApplicationRunner=======");
    }
}

7.4 实现ApplicationListener

实现接口ApplicationListener方式和实现ApplicationRunner,CommandLineRunner接口都不影响服务,都可以正常提供服务,注意监听的事件,通常是ApplicationStartedEvent 或者ApplicationReadyEvent,其他的事件可能无法注入bean。

import org.springframework.boot.context.event.ApplicationStartedEvent;
import org.springframework.context.ApplicationListener;
import org.springframework.stereotype.Component;

@Component
public class ApplicationListenerStartedImpl implements ApplicationListener<ApplicationStartedEvent> {
    @Override
    public void onApplicationEvent(ApplicationStartedEvent event) {
        System.out.println("=======ApplicationListenerStartedImpl=======");
    }
}

-----------------------------------------------------------------------------------------------

import org.springframework.boot.context.event.ApplicationReadyEvent;
import org.springframework.context.ApplicationListener;
import org.springframework.stereotype.Component;

@Component
public class ApplicationListenerReadyImpl implements ApplicationListener<ApplicationReadyEvent> {
    @Override
    public void onApplicationEvent(ApplicationReadyEvent event) {
        System.out.println("=======ApplicationListenerReadyImpl=======");
    }
}

7.5 四种方式的执行顺序

通过下面打印的日志可以看出

(1) 注解方式@PostConstruct 始终最先执行

(2) 如果监听的是ApplicationStartedEvent 事件,则一定会在CommandLineRunner和ApplicationRunner 之前执行。如果监听的是ApplicationReadyEvent 事件,则一定会在CommandLineRunner和ApplicationRunner 之后执行。

(3) CommandLineRunner和ApplicationRunner 默认是ApplicationRunner先执行,如果双方指定了@Order 则按照@Order的大小顺序执行,大的先执行。

......
2023-01-04 14:12:05.867 [main] INFO  org.apache.catalina.core.StandardEngine - Starting Servlet engine: [Apache Tomcat/9.0.50] 
2023-01-04 14:12:06.149 [main] INFO  o.a.c.core.ContainerBase.[Tomcat].[localhost].[/] - Initializing Spring embedded WebApplicationContext 
2023-01-04 14:12:06.150 [main] INFO  o.s.b.w.s.c.ServletWebServerApplicationContext - Root WebApplicationContext: initialization completed in 1498 ms 
=======PostConstruct=======
Logging initialized using 'class org.apache.ibatis.logging.stdout.StdOutImpl' adapter.
2023-01-04 14:12:06.381 [main] INFO  c.a.d.s.b.a.DruidDataSourceAutoConfigure - Init DruidDataSource 
2023-01-04 14:12:06.494 [main] INFO  com.alibaba.druid.pool.DruidDataSource - {dataSource-1} inited 
Property 'mapperLocations' was not specified.
 _ _   |_  _ _|_. ___ _ |    _ 
| | |\/|_)(_| | |_\  |_)||_|_\ 
     /               |         
                        3.4.0 
2023-01-04 14:12:07.662 [main] INFO  org.apache.coyote.http11.Http11NioProtocol - Starting ProtocolHandler ["http-nio-8080"] 
2023-01-04 14:12:07.677 [main] INFO  o.s.boot.web.embedded.tomcat.TomcatWebServer - Tomcat started on port(s): 8080 (http) with context path '' 
2023-01-04 14:12:07.797 [main] WARN  s.d.s.r.operation.OperationImplicitParameterReader - Unable to interpret the implicit parameter configuration with dataType: String, dataTypeClass: class java.lang.Void 
2023-01-04 14:12:07.801 [main] WARN  s.d.s.r.operation.OperationImplicitParameterReader - Unable to interpret the implicit parameter configuration with dataType: Long, dataTypeClass: class java.lang.Void 
2023-01-04 14:12:07.819 [main] WARN  s.d.s.r.operation.OperationImplicitParameterReader - Unable to interpret the implicit parameter configuration with dataType: User, dataTypeClass: class java.lang.Void 
2023-01-04 14:12:07.842 [main] INFO  com.learn.tm.TianmengApplication - Started TianmengApplication in 3.818 seconds (JVM running for 4.842) 
=======ApplicationListenerStartedImpl=======
=======ApplicationRunner=======
=======CommandLineRunner=======
=======ApplicationListenerReadyImpl=======

正常是ApplicationRunner优先于CommandLineRunner执行,但是如果在这两个类上添加order注解,如下:

@Order(1)
@Component
public class CommandLineRunnerImpl implements CommandLineRunner {
    @Override
    public void run(String... args) throws Exception {
        System.out.println("=======CommandLineRunner=======");
    }
}

@Order(2)
@Component
public class ApplicationRunnerImpl implements ApplicationRunner {
    @Override
    public void run(ApplicationArguments args) throws Exception {
        System.out.println("=======ApplicationRunner=======");
    }
}

则最后结果为 : CommandLineRunner优先于ApplicationRunner ,即@Order注解后的参数值越小,优先级越高

=======PostConstruct=======
=======ApplicationListenerStartedImpl=======
=======CommandLineRunner=======
=======ApplicationRunner=======
=======ApplicationListenerReadyImpl=======

8. SpringBoot 整合 ElasticSearch

8.1 添加依赖

<!-- https://mvnrepository.com/artifact/org.elasticsearch/elasticsearch -->
<dependency>
    <groupId>org.elasticsearch</groupId>
    <artifactId>elasticsearch</artifactId>
    <version>7.8.0</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.elasticsearch.client/elasticsearch-rest-high-level-client -->
<dependency>
    <groupId>org.elasticsearch.client</groupId>
    <artifactId>elasticsearch-rest-high-level-client</artifactId>
    <version>7.8.0</version>
</dependency>

8.2 添加配置类

import org.apache.http.HttpHost;
import org.elasticsearch.client.RestClient;
import org.elasticsearch.client.RestHighLevelClient;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class ElasticSearchConfig {
    @Bean
    public RestHighLevelClient restHighLevelClient    (){
        RestHighLevelClient    client = new RestHighLevelClient(
                RestClient.builder(
                        new HttpHost("192.168.198.100",9200,"http")));
        return client;
    }
}

8.3 添加配置信息

spring:
  elasticsearch:
    rest:
      #username: user
      #password: 123456
      uris: https://192.168.198.100:9200
      # http连接超时时间
      connection-timeout: 1000
      # socket连接超时时间
      socketTimeout: 30000
      # 获取连接的超时时间
      connectionRequestTimeout: 500
      # 最大连接数
      maxConnTotal: 100
      # 最大路由连接数
      maxConnPerRoute: 100
      # 任务最长可执行时间 (单位:小时)
      executeTimeout: 8

8.4 测试类

@GetMapping("/getAllIndex")
@ApiOperation(value = "elasticsearch测试",notes = "")
public void getAllIndex() throws IOException {
    //查看所有索引信息
    GetIndexRequest request = new GetIndexRequest("*");
    GetIndexResponse response = client.indices().get(request, RequestOptions.DEFAULT);
    String[] indices = response.getIndices();
    System.out.println(indices.toString());
}