Spring Boot整合全局异常处理器、junit、多环境、logback

一、SpringBoot整合全局异常处理器

1. 非前后端分离(了解)

适用于传统MVC应用,返回视图页面:

复制代码
@Component
public class GlobalExceptionResolver implements HandlerExceptionResolver {
    
    @Override
    public ModelAndView resolveException(HttpServletRequest request, 
                                       HttpServletResponse response, 
                                       Object handler, 
                                       Exception ex) {
        ModelAndView mv = new ModelAndView();
        // 设置错误信息
        mv.addObject("message", ex.getMessage());
        mv.addObject("exception", ex);
        // 跳转到错误页面
        mv.setViewName("error");
        return mv;
    }
}

2. 前后端分离(重点)

适用于RESTful API,返回JSON格式数据:

复制代码
@RestControllerAdvice
public class GlobalExceptionHandler {
    
    /**
     * 处理所有异常
     */
    @ExceptionHandler(Exception.class)
    public Result handleException(Exception e) {
        // 记录日志
        log.error("系统异常: ", e);
        
        // 返回统一格式
        return Result.error("系统异常,请联系管理员");
    }
    
    /**
     * 处理业务异常
     */
    @ExceptionHandler(BusinessException.class)
    public Result handleBusinessException(BusinessException e) {
        return Result.error(e.getMessage());
    }
    
    /**
     * 处理参数验证异常
     */
    @ExceptionHandler(MethodArgumentNotValidException.class)
    public Result handleValidException(MethodArgumentNotValidException e) {
        String message = e.getBindingResult().getFieldErrors().stream()
            .map(FieldError::getDefaultMessage)
            .collect(Collectors.joining(", "));
        return Result.error(message);
    }
}

// 统一返回结果类
@Data
public class Result<T> {
    private Integer code;
    private String message;
    private T data;
    
    public static <T> Result<T> success(T data) {
        Result<T> result = new Result<>();
        result.setCode(200);
        result.setMessage("操作成功");
        result.setData(data);
        return result;
    }
    
    public static <T> Result<T> error(String message) {
        Result<T> result = new Result<>();
        result.setCode(500);
        result.setMessage(message);
        return result;
    }
}

二、SpringBoot整合JUnit

1. 添加依赖

复制代码
<!-- pom.xml -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-test</artifactId>
    <scope>test</scope>
</dependency>

2. 编写测试类

复制代码
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;

/**
 * 测试演进历程:
 * 1. 传统方式:main方法启动Spring
 *    new ClassPathXmlApplicationContext("applicationContext.xml");
 * 
 * 2. Spring整合JUnit4:
 *    @RunWith(SpringJUnit4ClassRunner.class)
 *    @ContextConfiguration("applicationContext.xml")
 * 
 * 3. SpringBoot整合JUnit5(推荐):
 *    @SpringBootTest
 */
@SpringBootTest
class UserServiceTest {
    
    @Autowired
    private UserService userService;
    
    @Test
    void testGetUserById() {
        // 模拟测试
        User user = userService.getUserById(1L);
        assertNotNull(user);
        assertEquals("张三", user.getName());
    }
    
    @Test
    @Transactional
    @Rollback
    void testSaveUser() {
        User user = new User();
        user.setName("李四");
        user.setAge(25);
        
        User savedUser = userService.save(user);
        assertNotNull(savedUser.getId());
    }
    
    // Mock测试示例
    @Test
    void testWithMock() {
        // 使用Mockito模拟依赖
        UserRepository mockRepo = mock(UserRepository.class);
        when(mockRepo.findById(1L)).thenReturn(Optional.of(new User(1L, "王五")));
        
        UserService service = new UserService(mockRepo);
        User user = service.getUserById(1L);
        
        assertEquals("王五", user.getName());
    }
}

三、SpringBoot多环境配置

1. 多环境配置语法

  • 主配置文件:application.yml

  • 环境配置文件:application-{profile}.yml

2. 创建多套环境配置

application-dev.yml(开发环境)

复制代码
server:
  port: 8081
  servlet:
    context-path: /dev-api

spring:
  datasource:
    driver-class-name: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql://localhost:3306/dev_db?useSSL=false&serverTimezone=Asia/Shanghai
    username: dev_user
    password: dev123
    
logging:
  level:
    com.example: debug

application-test.yml(测试环境)

复制代码
server:
  port: 8082
  servlet:
    context-path: /test-api

spring:
  datasource:
    driver-class-name: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql://test-server:3306/test_db?useSSL=false&serverTimezone=Asia/Shanghai
    username: test_user
    password: test123
    
logging:
  level:
    com.example: info

application-prod.yml(生产环境)

复制代码
server:
  port: 8083
  servlet:
    context-path: /api

spring:
  datasource:
    driver-class-name: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql://prod-master:3306/prod_db?useSSL=false&serverTimezone=Asia/Shanghai
    username: ${DB_USERNAME}
    password: ${DB_PASSWORD}
    
logging:
  level:
    com.example: warn
  file:
    name: logs/application.log

3. 激活环境

方式一:在application.yml中指定

复制代码
# application.yml
spring:
  profiles:
    active: dev

方式二:启动参数指定

复制代码
# 命令行启动
java -jar myapp.jar --spring.profiles.active=prod

# 或使用系统属性
java -Dspring.profiles.active=test -jar myapp.jar

方式三:通过环境变量

复制代码
# 设置环境变量
export SPRING_PROFILES_ACTIVE=prod

方式四:使用分组(Spring Boot 2.4+)

复制代码
# application.yml
spring:
  profiles:
    active: dev,audit
    group:
      dev: dev,debug
      prod: prod,audit,metrics

四、SpringBoot整合Logback日志

1. 添加依赖(已包含在starter中)

SpringBoot默认使用Logback,无需额外配置依赖。

2. Logback配置文件

创建logback-spring.xmlresources目录下:

复制代码
<?xml version="1.0" encoding="UTF-8"?>
<configuration>
    
    <!-- 定义日志输出格式 -->
    <property name="CONSOLE_LOG_PATTERN" value="%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n"/>
    <property name="FILE_LOG_PATTERN" value="%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n"/>
    
    <!-- 控制台输出 -->
    <appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
        <encoder>
            <pattern>${CONSOLE_LOG_PATTERN}</pattern>
            <charset>UTF-8</charset>
        </encoder>
    </appender>
    
    <!-- 文件输出 -->
    <appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <file>logs/app.log</file>
        <encoder>
            <pattern>${FILE_LOG_PATTERN}</pattern>
            <charset>UTF-8</charset>
        </encoder>
        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
            <!-- 按天归档,保存30天 -->
            <fileNamePattern>logs/app.%d{yyyy-MM-dd}.%i.log</fileNamePattern>
            <timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
                <maxFileSize>100MB</maxFileSize>
            </timeBasedFileNamingAndTriggeringPolicy>
            <maxHistory>30</maxHistory>
        </rollingPolicy>
    </appender>
    
    <!-- 错误日志单独输出 -->
    <appender name="ERROR_FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <file>logs/error.log</file>
        <encoder>
            <pattern>${FILE_LOG_PATTERN}</pattern>
            <charset>UTF-8</charset>
        </encoder>
        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
            <fileNamePattern>logs/error.%d{yyyy-MM-dd}.log</fileNamePattern>
            <maxHistory>30</maxHistory>
        </rollingPolicy>
        <filter class="ch.qos.logback.classic.filter.ThresholdFilter">
            <level>ERROR</level>
        </filter>
    </appender>
    
    <!-- 多环境配置 -->
    <springProfile name="dev">
        <root level="DEBUG">
            <appender-ref ref="CONSOLE"/>
        </root>
        <logger name="com.example" level="DEBUG" additivity="false">
            <appender-ref ref="CONSOLE"/>
        </logger>
    </springProfile>
    
    <springProfile name="test">
        <root level="INFO">
            <appender-ref ref="CONSOLE"/>
            <appender-ref ref="FILE"/>
        </root>
    </springProfile>
    
    <springProfile name="prod">
        <root level="WARN">
            <appender-ref ref="FILE"/>
            <appender-ref ref="ERROR_FILE"/>
        </root>
        <logger name="com.example" level="INFO" additivity="false">
            <appender-ref ref="FILE"/>
        </logger>
    </springProfile>
    
</configuration>

3. 在代码中使用日志

复制代码
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class UserController {
    
    private static final Logger logger = LoggerFactory.getLogger(UserController.class);
    
    public User getUser(Long id) {
        // 不同级别的日志
        logger.trace("trace级别日志");
        logger.debug("debug级别日志,用户ID: {}", id);
        logger.info("info级别日志");
        logger.warn("warn级别日志");
        logger.error("error级别日志");
        
        // 使用占位符,避免字符串拼接
        logger.info("用户查询: id={}, time={}", id, System.currentTimeMillis());
        
        return userService.getUserById(id);
    }
}

五、完整项目结构示例

复制代码
src/main/java/
├── com.example.demo
│   ├── config/
│   │   └── GlobalExceptionHandler.java
│   ├── controller/
│   │   └── UserController.java
│   ├── service/
│   │   └── UserService.java
│   └── DemoApplication.java
├── resources/
│   ├── application.yml
│   ├── application-dev.yml
│   ├── application-test.yml
│   ├── application-prod.yml
│   └── logback-spring.xml
└── test/java/
    └── com.example.demo
        └── UserServiceTest.java

总结要点

  1. 异常处理 :前后端分离项目使用@RestControllerAdvice+@ExceptionHandler

  2. 单元测试 :使用@SpringBootTest注解,支持依赖注入

  3. 多环境配置 :通过application-{profile}.ymlspring.profiles.active管理

  4. 日志配置 :使用logback-spring.xml,支持多环境不同日志级别

  5. 最佳实践:生产环境关闭调试日志,使用文件日志并配置滚动策略

相关推荐
tkevinjd4 小时前
Redis主从复制
数据库·redis·后端·缓存·面试
进击的女IT4 小时前
Java使用poi-tl实现word模版渲染文本/图片
java·数据库·word
庞轩px4 小时前
ThreadLocal 源码分析与内存泄漏问题
java·jvm·线程·threadlocal·内存泄露·key-value
小江的记录本4 小时前
【Logback】Logback 日志框架 与 SLF4J绑定、三层模块、MDC链路追踪、异步日志、滚动策略
java·spring boot·后端·spring·log4j·maven·logback
随风,奔跑4 小时前
Spring Boot笔记
java·spring boot·笔记·后端
studyForMokey4 小时前
【Android面试】Handler专题
android·java·面试
难忘经典4 小时前
springboot中配置logback-spring.xml
spring boot·spring·logback
huabiangaozhi4 小时前
SpringBoot + vue 管理系统
vue.js·spring boot·后端
ruiang4 小时前
Spring Boot 3.3.4 升级导致 Logback 之前回滚策略配置不兼容问题解决
java·spring boot·logback