7个Spring Boot实用小技巧

本文和大家分享一下,Spring Boot的一些小技巧,知道这些小技巧,有助于你在实际项目中解决一些特殊的需求。

技巧一:调整 bean 的加载顺序

调整 bean 的加载顺序的方式有很多种:
1、可以通过 @Autowired 的方式,让一个 bean 进行初始化

例如: ServiceA 加载之前,一定要初始化 ServiceB,则可以将 ServiceB 通过 @Autowired 时行注入,让 ServiceB 在 ServiceA 之前时行初始化

2、通过注解@ConditionalOnBean(xxx) 说明这两个Bean的依赖关系。

技巧二:classloader.getResource() 拿不到资源

在特定的环境下:A包里面有resources/res.txt资源
在B包中引用了A包,在获取res.txt资源时就会报错,拿不到资源

使用 IDEA 运行 springboot 程序与 java -jar 运行 springboot 程序时 ClassLoader 不同,导致 classloader.getResource() 拿不到资源

使用 this.getClass().getClassLoader() 获取 classloader 时,运行方式不同,结果不一样

A、使用 IDEA 运行 springboot 程序时,sun.misc.Launcher$AppClassLoader@18b4aac2

B、使用 java -jar 运行打包后的 jar 包时,org.springframework.boot.loader.LaunchedURLClassLoader@71dac704

原因在于:在Jar包中的文件在磁盘是没有实际路径的,所以这时候通过 this.getClass().getResource() 无法获取文件。

对于原始的JarFile URL,只支持一个’!/’,SpringBoot 扩展了此协议,使其支持多个’!/’,以实现 jar in jar 的加载资源方式。

但是,取到了资源路径,原生的 new File() 还是处理不了这种资源路径的。因为里面带多个!/路径,正常是无法获取到资源的。会报错提示:

Caused by: java.io.FileNotFoundException: class path resource [res.txt] cannot be resolved to absolute file path because it does not reside in the file system: jar:file:/Users/kyle/CodeJava/qyun-web/qyun-system-api/target/qyun-system-api.jar!/BOOT-INF/lib/qyun-component-mybatis-1.0.jar!/res.txt

测试下面的办法是取不到路径的:

String resourcePath = "res.txt";
String path = this.getClass().getResource("/").getPath() + resourcePath;
URL fileURL = this.getClass().getResource(resourcePath);

我们通过传统的方式取不到资源了,即使取到了,也没法直接使用,那在 SpringBoot 中我们应该怎么获取资源呢?

不太优雅的方案:

private static final ResourcePatternResolver resourceResolver = new PathMatchingResourcePatternResolver();

public org.springframework.core.io.Resource[] resolveMapperLocations(String[] mapperLocations) {
    return Stream.of(Optional.ofNullable(mapperLocations).orElse(new String[0]))
            .flatMap(location -> Stream.of(getResources(location))).toArray(org.springframework.core.io.Resource[]::new);
}

private org.springframework.core.io.Resource[] getResources(String location) {
    try {
        return resourceResolver.getResources(location);
    } catch (IOException e) {
        return new org.springframework.core.io.Resource[0];
    }
}

从路径中解析出资源,读取时,可以直接用
res.getContentAsString(StandardCharsets.UTF_8)

最后再提供一个最简方案,使用Hutool工具库 https://github.com/dromara/hutool

URL file4 = ResourceUtil.getResource(fileName);
System.out.println("Test 4");
System.out.println(Objects.nonNull(file4) ? FileUtils.readFileToString(new File(file4.getFile()), StandardCharsets.UTF_8) : "test 4 file is null");

技巧三:排除自动配置的几种方式

1、指定class排除

@SpringBootApplication(exclude = DataSourceAutoConfiguration.class)

2、指定类名

@SpringBootApplication(excludeName = "org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration")

3、配置文件配置

spring:
  autoconfigure:
     exclude:
       - org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration

技巧四: @ConditionalOnProperty 根据配置加载不同的 bean

场景:对 redis 配置进行封装,实现自动化配置,能兼容哨兵模式和集群模式。

想到在 redis 配置中加一个 redis.type 来区分集群和哨兵模式(redis.type=cluster 或 sentinel),然后根据 type 来分别加载 JedisConnectionFactory、RedisClusterConfiguration、RedisSentinelConfiguration

配置如下:

@ConditionalOnProperty(name = "redis.type", havingValue = "cluster")
@ConditionalOnMissingBean
@Bean
public RedisClusterConfiguration redisClusterConfiguration() {

}

@ConditionalOnProperty(name = "redis.type", havingValue = "sentinel")
@ConditionalOnMissingBean
@Bean
public RedisSentinelConfiguration redisSentinelConfiguration() {
    
    Set<String> sentinelHostAndPorts = Sets.newHashSet(Splitter.on(",").split(hostName).iterator());
    
    RedisSentinelConfiguration redisSentinelConfiguration = new RedisSentinelConfiguration(masterName, sentinelHostAndPorts);
    
    return redisSentinelConfiguration;

}

技巧五: 实现用户行为记录的四种方式

根据产品经理要求,需要对用户的行为进行记录。重点记录用户的姓名、IP、操作行为、请求参数和返回参数。先采用暴力解决问题,每个行为增加记录行为,当然这个是最不恰当的方案,工作量大不说,还不具有扩展性。有4种方案来实现该需求:

1、AOP切面编程实现:在每一个需要记录日志的方法前增加切点,然后对切点进行处理即可。

2、Interceptor拦截器实现:定义拦截器类 OperateLogInterceptor 类并实现 HandlerInterceptor 接口。

3、Filter过滤器实现

4、ArgumentResolver:参数解析器是 Spring 提供的用于解析自定义参数的工具,使用它,我们可以将参数在进入Controller Action之前就组合成我们想要的样子。

技巧六: 测试中使用随机HTTP端口

在Spring Boot测试中不应该使用静态端口,为了对指定的测试设置该选项,你需要设置 @SpringBootTest 注解中的 webEnvironment 字段,将它的指定为 RANDOM_PORT 而不是默认的 DEFINED_PORT 。配置完后你可以使用 @LocalServerPort 注解将这个随机生成的端口号注入到测试类中。

比如:

@RunWith(SpringRunner.class)
@SpringBootTest(classes = StartUpApplication.class,
webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)

技巧七:使用@DataJpaTest来测试JPA接口

对于集成测试,通常情况下你可能会使用 @SpringBootTest 来注释测试类,这样做的问题在于它启动了整个应用程序上下文,这会增加运行测试所需的总时间。更好的选择是:你可以使用 @DataJpaTest 来启动JPA组件和带有 @Repository 注解的bean。默认情况下它会在日志中记录SQL查询语句,因此一个好主意是使用 showSql 字段禁用这个特性。此外,如果你希望将带有 @Service 或 @Component 注解的bean包含到测试中,可以使用 @Import 注解。

发表评论

您的电子邮箱地址不会被公开。 必填项已用 * 标注