实战:7个常用的springboot技巧

345 篇文章 1 订阅
268 篇文章 0 订阅

Spring Boot 是一个流行的企业级应用框架,简化了基于 Spring 的应用开发。

它提供了开箱即用的配置和依赖管理,使得开发者可以快速构建微服务和独立的应用程序。

然而,Spring Boot 的强大不仅限于其基础特性,它还隐藏着许多高级用法,能够显著提升应用的性能、可维护性和可扩展性。

1. 配置文件的高级技巧

Spring Boot 支持使用 application.properties 或 application.yml 文件来配置应用。除了基本的键值对配置,你还可以利用环境变量、配置文件分层和外部配置服务器等高级特性来管理不同环境下的配置差异。

spring.profiles.active 配置在 Spring 应用程序中用于指定当前激活的配置文件(profiles)。配置文件是 Spring 提供的一种机制,用于将应用程序的配置划分为不同的环境,比如开发(development)、测试(test)、生产(production)等。

作用

  1. 环境特定配置:
    1. 不同环境可能需要不同的配置。例如,开发环境可能使用本地数据库,而生产环境使用远程数据库。
    2. 可以为每个环境创建单独的属性文件,如 application-dev.properties、application-test.properties、application-prod.properties。
  2. 激活指定配置文件:
    1. 通过设置 spring.profiles.active,可以告诉 Spring 在启动时加载哪些配置文件。
    2. 可以在 application.properties 文件中指定,也可以通过命令行参数或环境变量来指定。
  3. 支持多个配置文件:
    1. 可以同时激活多个配置文件,使用逗号分隔,如 spring.profiles.active=dev,test。
自定义配置文件命名
如果你希望使用不同的命名方式,可以在主配置文件 application.properties 或 application.yml 中使用 spring.config.additional-location 或 spring.config.import 来指定其他配置文件的位置。

示例:自定义配置文件名称
假设你有一个自定义命名的配置文件 custom-dev-config.properties,可以通过以下方式引用它:

application.properties
spring.profiles.active=dev
spring.config.additional-location=classpath:/config/custom-dev-config.properties

或者

application.yml
spring:
  profiles:
    active: dev
  config:
    additional-location: classpath:/config/custom-dev-config.properties

使用 spring.config.import (Spring Boot 2.4+)
从 Spring Boot 2.4 开始,引入了 spring.config.import 属性,可以更灵活地引入其他配置文件:

application.properties
spring.profiles.active=dev
spring.config.import=optional:classpath:/config/custom-dev-config.properties

application.yml
spring:
  profiles:
    active: dev
  config:
    import: optional:classpath:/config/custom-dev-config.properties

完整示例
假设你有以下两个文件:
application.properties:
spring.profiles.active=dev
spring.config.import=optional:classpath:/config/custom-dev-config.properties

custom-dev-config.properties:
db.url=jdbc:mysql://localhost:3306/devdb
db.username=devuser
db.password=devpass


在这种配置下,Spring 启动时会加载 custom-dev-config.properties 中的内容,因为它与激活的 dev 配置文件关联。

总结
​ spring.profiles.active 配置使得应用程序能够在不同的环境中使用不同的配置,从而简化了开发、测试和部署过程中的配置管理。这一机制可以确保应用程序在不同环境中具有一致性和灵活性。

​ 虽然 application-{profile}.properties 是 Spring 的默认命名约定,但你可以使用 spring.config.additional-location 或 spring.config.import 属性来自定义配置文件的命名和位置。这使得你的配置更加灵活,可以根据项目需求进行调整。

1.1 多环境配置

通过使用 spring.profiles.active 属性,你可以指定不同的配置文件,如 application-dev.yml 和 application-prod.yml,来适应开发、测试和生产环境的不同需求。

示例
application.properties
spring.profiles.active=dev

application-dev.properties
db.url=jdbc:mysql://localhost:3306/devdb
db.username=devuser
db.password=devpass

application-prod.properties
db.url=jdbc:mysql://localhost:3306/proddb
db.username=produser
db.password=prodpass

1.2 配置绑定

Spring Boot 支持将配置文件中的属性自动绑定到 Java 对象中,通过使用 @ConfigurationProperties 注解,可以将复杂的配置映射到实体类上。

编程方式激活配置文件
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Profile;

@Configuration
@Profile("dev")
public class DevConfig {
    // 开发环境特定的 Bean 和配置
}


命令行激活配置文件
通过命令行参数在启动 Spring Boot 应用程序时激活配置文件:
java -jar myapp.jar --spring.profiles.active=prod

2. 热部署与监控

2.1 热部署

利用 Spring Loaded 或 DevTools,可以在不重启应用的情况下热加载代码变更。这对于开发阶段非常有用,可以即时看到代码修改的效果。

热部署依赖

<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-devtools</artifactId>
  <optional>true</optional>
</dependency>

2.2 应用监控

Spring Boot Actuator 提供了许多端点,如 /health, /info, /metrics 等,用于监控应用的健康状况和性能指标。这些端点可以通过 HTTP 或 JMX 进行访问。

监控依赖

<dependency>
   <groupId>org.springframework.boot</groupId>
   <artifactId>spring-boot-starter-actuator</artifactId>
</dependency>

yml配置
management.security.enabled=false

自定义健康监控信息指标

@Component
public class MyAppHealthIndicator implements HealthIndicator {

    @Override
    public Health health() {

        //自定义的检查方法
        //Health.up().build()代表健康
        return Health.down().withDetail("msg","服务异常").build();
    }

3. 异常处理与统一错误响应

Spring Boot 提供了一种机制来处理异常,一旦程序出现异常,可以自动重定向到 /error 的 URL。通过自定义 ErrorController 或使用 @RestControllerAdvice 注解,可以统一处理异常并返回格式化的错误响应。

package com.zxx.study.web.util;






import com.zxx.study.web.config.PageInfo;
import com.zxx.study.web.exception.BaseException;
import com.zxx.study.web.exception.BaseResultError;
import com.zxx.study.web.exception.BizException;
import com.zxx.study.web.exception.JwtException;
import lombok.Data;

import java.util.HashMap;
import java.util.Map;

@Data
public class ApiResult {

    /**
     * 状态码,比如1000代表响应成功
     */
    private int status;
    /**
     * 响应信息,用来说明响应情况
     */
    private String msg;
    /**
     * 响应的具体数据
     */
    private Object data;

    public ApiResult(int status, String msg, Object data) {
        setStatus(status);
        setMsg(msg);
        setData(data);
    }

    public static ApiResult success(Object data) {
        return new ApiResult(BaseResultError.SUCCESS.getCode(), BaseResultError.SUCCESS.getMsg(), data);
    }
    public static ApiResult success(PageInfo pageInfo, Object dataInfo) {
        Map<String,Object> data=new HashMap<>();
        data.put("pageInfo",pageInfo);
        data.put("dataInfo",dataInfo);
        return new ApiResult(BaseResultError.SUCCESS.getCode(), BaseResultError.SUCCESS.getMsg(), data);
    }
    public static ApiResult fail(BaseResultError resultError) {

        return new ApiResult(resultError.getCode(), resultError.getMsg(), null);
    }
    public static ApiResult fail(JwtException e) {
        return new ApiResult(e.getCode(), e.getExceptionMessage(), null);
    }
    public static ApiResult fail(BizException e) {
        return new ApiResult(e.getCode(), e.getExceptionMessage(), null);
    }
    public static ApiResult fail(BaseException e) {
        return new ApiResult(e.getCode(), e.getExceptionMessage(), null);
    }

    public static ApiResult fail(BaseResultError resultError, Object data) {

        return new ApiResult(resultError.getCode(), resultError.getMsg(), data);
    }

    public static ApiResult fail(int status, String msg) {

        return new ApiResult(status, msg, null);
    }
}

4. 使用 AOP(面向切面编程)

Spring Boot 支持 AOP 编程,通过使用 @Aspect 和 @Around, @Before, @After 等注解,可以实现横切关注点的代码复用,如日志记录、权限校验等。

5. 使用 Bean生命周期的扩展点:Bean Post Processor

BeanPostProcessor的设计目标主要是提供一种扩展机制,让开发者可以在Spring Bean的初始化阶段进行自定义操作。这种设计理念主要体现了Spring的一种重要原则,即“开放封闭原则”。开放封闭原则强调软件实体(类、模块、函数等等)应该对于扩展是开放的,对于修改是封闭的。在这里,Spring容器对于Bean的创建、初始化、销毁等生命周期进行了管理,但同时开放了BeanPostProcessor这种扩展点,让开发者可以在不修改Spring源码的情况下,实现对Spring Bean生命周期的自定义操作,这种设计理念大大提升了Spring的灵活性和可扩展性。

BeanPostProcessor不是Spring Bean生命周期的一部分,但它是在Spring Bean生命周期中起重要作用的组件

5.1  监控统计 每个环节耗时

5.2 利用BeanPostProcessor修改Bean的初始化结果的返回值

还是上面的例子,我们只修改一下MyBeanPostProcessor 类的方法后再次运行

package com.example.demo.processor;
import com.example.demo.bean.Elephant;
import com.example.demo.bean.Lion;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanPostProcessor;
public class MyBeanPostProcessor implements BeanPostProcessor {
 @Override
 public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
 System.out.println("Before initialization: " + bean);
 if (bean instanceof Lion) {
 return new Elephant();
 }
 return bean;
 }
 @Override
 public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
 System.out.println("After initialization: " + bean);
 return bean;
 }
}

运行结果:

BeanPostProcessor的两个方法都可以返回任意的Object,这意味着我们可以在这两个方法中更改返回的bean。例如,如果我们让postProcessBeforeInitialization方法在接收到Lion实例时返回一个新的Elephant实例,那么我们将会看到Lion实例变成了Elephant实例。

那既然BeanPostProcessor的两个方法都可以返回任意的Object,那我搞点破坏返回null会怎么样,会不会因为初始化bean为null而导致异常呢?

答案是不会的,我们来看一下:

package com.example.demo.processor;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanPostProcessor;
public class MyBeanPostProcessor implements BeanPostProcessor {
 @Override
 public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
 System.out.println("Before initialization: " + bean);
 return null;
 }
 @Override
 public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
 System.out.println("After initialization: " + bean);
 return bean;
 }
}

我们运行看结果

结果发现还是正常初始化的bean类型,不会有任何改变,我们继续调试看看是为什么

我们通过堆栈帧看到调用postProcessBeforeInitialization方法的上一个方法是applyBeanPostProcessorsBeforeInitialization,双击点开看一看这个方法

从我这个调试图中可以看到,如果postProcessBeforeInitialization返回null,Spring仍然用原始的bean进行后续的处理,同样的逻辑在postProcessAfterInitialization也是一样。这就是为什么我们在BeanPostProcessor类的方法中返回null,原始bean实例还是存在的原因。

5.3 通过BeanPostProcessor实现Bean属性的动态修改

来看看是怎么拦截 bean 的初始化的

全部代码如下:

首先,我们定义一个Lion类:

public class Lion {
 private String name;
 public Lion() {
 this.name = "Default Lion";
 }
 public Lion(String name) {
 this.name = name;
 }
 public String getName() {
 return name;
 }
 public void setName(String name) {
 this.name = name;
 }
 @Override
 public String toString() {
 return "Lion{" + "name='" + name + '\'' + '}';
 }
}

接下来,我们定义一个BeanPostProcessor,我们称之为MyBeanPostProcessor :

package com.example.demo.processor;
import com.example.demo.bean.Lion;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanPostProcessor;
public class MyBeanPostProcessor implements BeanPostProcessor {
 @Override
 public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
 System.out.println("Bean的初始化之前:" + bean);
 return bean;
 }
 @Override
 public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
 System.out.println("Bean的初始化之后:" + bean);
 if (bean instanceof Lion) {
 ((Lion) bean).setName("Simba");
 }
 return bean;
 }
}

然后我们定义一个配置类,其中包含对Lion类的Bean定义和对MyBeanPostProcessor 类的Bean定义:

package com.example.demo.configuration;
import com.example.demo.bean.Lion;
import com.example.demo.processor.MyBeanPostProcessor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class AnimalConfig {
 @Bean
 public Lion lion() {
 return new Lion();
 }
 @Bean
 public MyBeanPostProcessor myBeanPostProcessor() {
 return new MyBeanPostProcessor();
 }
}

最后,我们在主程序中创建ApplicationContext对象,并获取Lion对象:

package com.example.demo;
import com.example.demo.bean.Lion;
import com.example.demo.configuration.AnimalConfig;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
public class DemoApplication {
 public static void main(String[] args) {
 ApplicationContext context = new AnnotationConfigApplicationContext(AnimalConfig.class);
 Lion lion = context.getBean("lion", Lion.class);
 System.out.println(lion);
 ((AnnotationConfigApplicationContext)context).close();
 }
}

运行结果:

上面代码在执行时,先创建一个Lion对象,然后在初始化过程中和初始化后调用postProcessBeforeInitialization和postProcessAfterInitialization方法,修改Lion的名字为"Simba",最后在主程序中输出Lion对象,显示其名字为"Simba"。

6. 集成外部系统与服务

Spring Boot 提供了多种 Starter POMs 来简化与外部系统的集成,如 Spring Cloud, Kafka, Redis 等。通过简单的配置,就可以将这些服务集成到你的应用中。

Starter POM是Spring Boot提供的一个模块化依赖管理策略,旨在简化项目依赖的引入过程。每个Starter POM都是一个预定义的依赖集合,专为解决某一类问题而设计,比如Web开发、安全、数据库连接等。通过引入一个Starter POM,开发者可以自动获取所有相关依赖,且无需关心版本兼容性问题。

使用Starter POM

以spring-boot-starter-web为例,只需在项目的pom.xml中加入一行代码,即可获得开发Web应用所需的所有依赖,包括Spring MVC、Tomcat服务器等:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
</dependency>


Starter POM的设计遵循“约定优于配置”原则,不仅降低了项目初始化的复杂度,也确保了项目的整洁性和易维护性。

外部化配置:灵活性与可维护性的平衡

7. 测试与自动化

Spring Boot 支持编写单元测试和集成测试。通过使用 @RunWith(SpringRunner.class) 和 @SpringBootTest 注解,可以方便地编写和运行测试用例。

在单元测试和集成测试中,我们可以使用一些数学模型来评估测试结果的可靠性。例如,我们可以使用以下公式来计算测试覆盖率:

这个公式可以帮助我们了解测试的覆盖程度,并确保应用的正确性和可靠性。

7.1 单元测试

单元测试是一种软件测试方法,它测试单个代码单元(如方法或函数)的功能和行为。单元测试的目的是确保代码的正确性和可靠性。在Spring Boot中,我们可以使用JUnit和Mockito等框架来编写单元测试。

单元测试的原理是通过创建一组预定义的输入,对代码单元进行测试,并检查预期输出与实际输出是否一致。这个过程可以通过以下步骤实现:

  1. 创建一个测试类,继承自JUnit的测试框架。
  2. 在测试类中,定义一个或多个测试方法,每个方法对应一个代码单元。
  3. 在测试方法中,使用Mockito等框架来模拟依赖对象,并设置预期输出。
  4. 调用代码单元的方法,并检查实际输出与预期输出是否一致。

Spring Boot使用JUnit和Spring TestContext Framework来进行单元测试。你可以通过@SpringBootTest注解启用Spring Boot的测试支持。

@RunWith(SpringRunner.class)
@SpringBootTest
public class MyServiceTests {
 
    @Autowired
    private MyService myService;
 
    @Test
    public void testMyService() {
        assertNotNull(myService);
        // 进行更多的测试
    }
}

7.2 集成测试

集成测试是一种软件测试方法,它测试多个代码单元之间的交互和协同工作。集成测试的目的是确保不同模块之间的接口正确地工作,以及整个系统的正常运行。在Spring Boot中,我们可以使用Spring Test和MockMvc等框架来编写集成测试。

集成测试的原理是通过模拟整个系统的环境,对多个代码单元之间的交互和协同工作进行测试。这个过程可以通过以下步骤实现:

  1. 创建一个测试类,继承自Spring Test的测试框架。
  2. 在测试类中,使用@SpringBootTest注解来启动Spring Boot应用。
  3. 在测试类中,定义一个或多个测试方法,每个方法对应一个测试场景。
  4. 在测试方法中,使用MockMvc等框架来模拟HTTP请求,并检查响应结果是否正确。

集成测试通常用于测试多个组件或服务之间的交互。你可以使用@SpringBootTest注解加上@AutoConfigureMockMvc或@AutoConfigureWebTestClient来进行集成测试。

@RunWith(SpringRunner.class)
@SpringBootTest
@AutoConfigureMockMvc
public class MyControllerTests {
 
    @Autowired
    private MockMvc mockMvc;
 
    @Test
    public void testMyController() throws Exception {
        mockMvc.perform(get("/api/data"))
                .andExpect(status().isOk())
                .andExpect(content().contentType(MediaType.APPLICATION_JSON_UTF8));
        // 进行更多的测试
    }
}

使用@AutoConfigureWebTestClient可以进行基于Spring WebFlux的测试。

@RunWith(SpringRunner.class)
@SpringBootTest
@AutoConfigureWebTestClient
public class MyWebFluxTests {
 
    @Autowired
    private WebTestClient webTestClient;
 
    @Test
    public void testMyWebFluxController() {
        webTestClient.get().uri("/api/data")
                .accept(MediaType.APPLICATION_JSON)
                .exchange()
                .expectStatus().isOk()
                .expectBody().jsonPath("$.key").isEqualTo("value");
        // 进行更多的测试
    }
}

7.3 联系

单元测试和集成测试是软件测试的两种不同方法,它们在不同阶段和层次上进行。单元测试主要关注代码的内部逻辑和功能,而集成测试关注多个代码单元之间的交互和协同工作。在Spring Boot中,我们可以使用不同的框架来编写单元测试和集成测试,以确保应用的正确性和可靠性。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

-无-为-

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值