七、日志管理
7.1 日志配置
yaml
# application.yml
logging:
level:
root: INFO
com.example: DEBUG
org.springframework.web: INFO
org.hibernate: WARN
org.mybatis: DEBUG
file:
name: logs/application.log
path: logs/
max-size: 10MB
max-history: 30
pattern:
console: '%d{yyyy-MM-dd HH:mm:ss} - %msg%n'
file: '%d{yyyy-MM-dd HH:mm:ss} [%thread] %-5level %logger{36} - %msg%n'
dateformat: yyyy-MM-dd HH:mm:ss.SSS
xml
<!-- logback-spring.xml -->
<configuration>
<property name="LOG_PATH" value="${LOG_PATH:-${user.home}/logs}"/>
<property name="LOG_PATTERN"
value="%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n"/>
<!-- 控制台输出 -->
<appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>${LOG_PATTERN}</pattern>
</encoder>
</appender>
<!-- 文件输出 -->
<appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
<file>${LOG_PATH}/app.log</file>
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<fileNamePattern>${LOG_PATH}/app.%d{yyyy-MM-dd}.log.gz</fileNamePattern>
<maxHistory>30</maxHistory>
</rollingPolicy>
<encoder>
<pattern>${LOG_PATTERN}</pattern>
</encoder>
</appender>
<!-- 错误日志单独文件 -->
<appender name="ERROR_FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
<file>${LOG_PATH}/error.log</file>
<filter class="ch.qos.logback.classic.filter.ThresholdFilter">
<level>ERROR</level>
</filter>
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<fileNamePattern>${LOG_PATH}/error.%d{yyyy-MM-dd}.log.gz</fileNamePattern>
</rollingPolicy>
<encoder>
<pattern>${LOG_PATTERN}</pattern>
</encoder>
</appender>
<!-- 异步日志 -->
<appender name="ASYNC_FILE" class="ch.qos.logback.classic.AsyncAppender">
<queueSize>512</queueSize>
<discardingThreshold>0</discardingThreshold>
<appender-ref ref="FILE"/>
</appender>
<root level="INFO">
<appender-ref ref="CONSOLE"/>
<appender-ref ref="ASYNC_FILE"/>
<appender-ref ref="ERROR_FILE"/>
</root>
<logger name="com.example" level="DEBUG"/>
</configuration>
7.2 日志使用
java
@RestController
@Slf4j
public class UserController {
@GetMapping("/users/{id}")
public User getUser(@PathVariable Long id) {
log.debug("查询用户: id={}", id);
User user = userService.findById(id);
if (user == null) {
log.warn("用户不存在: id={}", id);
} else {
log.info("用户信息: {}", user);
}
return user;
}
@PostMapping("/users")
public User createUser(@RequestBody User user) {
log.info("创建用户: {}", user);
try {
User result = userService.create(user);
log.info("创建成功: id={}", result.getId());
return result;
} catch (Exception e) {
log.error("创建用户失败: {}", user, e);
throw e;
}
}
}
八、测试
8.1 单元测试
@SpringBootTest
@AutoConfigureMockMvc
@Transactional
@Rollback
public class UserControllerTest {
@Autowired
private MockMvc mockMvc;
@Autowired
private ObjectMapper objectMapper;
@Test
public void testGetUser() throws Exception {
mockMvc.perform(get("/api/users/1")
.contentType(MediaType.APPLICATION_JSON))
.andExpect(status().isOk())
.andExpect(jsonPath("$.code").value(200))
.andExpect(jsonPath("$.data.id").value(1))
.andDo(print());
}
@Test
public void testCreateUser() throws Exception {
User user = new User("张三", 25);
mockMvc.perform(post("/api/users")
.contentType(MediaType.APPLICATION_JSON)
.content(objectMapper.writeValueAsString(user)))
.andExpect(status().isCreated())
.andExpect(jsonPath("$.data.username").value("张三"));
}
@Test
public void testValidationError() throws Exception {
User user = new User("", -1);
mockMvc.perform(post("/api/users")
.contentType(MediaType.APPLICATION_JSON)
.content(objectMapper.writeValueAsString(user)))
.andExpect(status().isBadRequest())
.andExpect(jsonPath("$.code").value(400));
}
@Test
public void testPageQuery() throws Exception {
mockMvc.perform(get("/api/users")
.param("page", "1")
.param("size", "10")
.param("keyword", "张三"))
.andExpect(status().isOk())
.andExpect(jsonPath("$.data.records").isArray())
.andExpect(jsonPath("$.data.total").isNumber());
}
}
8.2 集成测试
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
@Testcontainers
public class IntegrationTest {
@LocalServerPort
private int port;
@Container
static MySQLContainer<?> mysql = new MySQLContainer<>("mysql:8.0")
.withDatabaseName("testdb")
.withUsername("test")
.withPassword("test");
@Container
static GenericContainer<?> redis = new GenericContainer<>("redis:7")
.withExposedPorts(6379);
@Test
public void testDatabaseConnection() {
assertTrue(mysql.isRunning());
assertTrue(redis.isRunning());
}
@Test
public void testApiCall() {
RestTemplate restTemplate = new RestTemplate();
String url = "http://localhost:" + port + "/api/health";
String response = restTemplate.getForObject(url, String.class);
assertNotNull(response);
}
}
九、生产就绪特性
9.1 Actuator 监控
yaml
# application.yml
management:
server:
port: 8081
address: 127.0.0.1
endpoints:
web:
exposure:
include: health,info,metrics,env,beans,mappings,threaddump,heapdump
base-path: /actuator
jmx:
exposure:
include: '*'
endpoint:
health:
show-details: always
show-components: always
info:
git:
mode: full
metrics:
enabled: true
prometheus:
enabled: true
metrics:
export:
prometheus:
enabled: true
tags:
application: ${spring.application.name}
java
// 自定义健康检查
@Component
public class CustomHealthIndicator implements HealthIndicator {
@Autowired
private DataSource dataSource;
@Override
public Health health() {
try {
dataSource.getConnection().isValid(5);
return Health.up()
.withDetail("database", "connected")
.withDetail("timestamp", System.currentTimeMillis())
.build();
} catch (Exception e) {
return Health.down()
.withDetail("error", e.getMessage())
.build();
}
}
}
// 自定义信息端点
@Component
public class CustomInfoContributor implements InfoContributor {
@Override
public void contribute(Builder builder) {
builder.withDetail("app", Map.of(
"name", "Spring Boot Demo",
"version", "1.0.0",
"description", "示例应用"
));
builder.withDetail("build", Map.of(
"time", Instant.now().toString(),
"user", System.getProperty("user.name")
));
}
}
// 自定义度量指标
@Component
public class CustomMetrics {
@Autowired
private MeterRegistry meterRegistry;
public void recordUserLogin(String username) {
meterRegistry.counter("user.login.total", "username", username).increment();
}
public void recordApiCall(String api, long duration) {
meterRegistry.timer("api.call.duration", "api", api)
.record(duration, TimeUnit.MILLISECONDS);
}
}
9.2 性能监控
@Aspect
@Component
public class PerformanceAspect {
private static final Logger log = LoggerFactory.getLogger(PerformanceAspect.class);
@Around("@annotation(com.example.annotation.Monitor)")
public Object monitor(ProceedingJoinPoint pjp) throws Throwable {
String className = pjp.getTarget().getClass().getSimpleName();
String methodName = pjp.getSignature().getName();
long start = System.currentTimeMillis();
try {
Object result = pjp.proceed();
long elapsed = System.currentTimeMillis() - start;
if (elapsed > 1000) {
log.warn("慢方法: {}.{} - 耗时: {}ms", className, methodName, elapsed);
} else {
log.debug("{}.{} - 耗时: {}ms", className, methodName, elapsed);
}
return result;
} catch (Exception e) {
long elapsed = System.currentTimeMillis() - start;
log.error("{}.{} 执行异常, 耗时: {}ms", className, methodName, elapsed, e);
throw e;
}
}
}
十、部署与运维
10.1 打包与部署
xml
<!-- Maven 打包配置 -->
<build>
<finalName>app</finalName>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<executable>true</executable>
<mainClass>com.example.Application</mainClass>
</configuration>
</plugin>
</plugins>
</build>
bash
# 打包
mvn clean package
# 启动
java -jar target/app.jar
# 指定环境
java -jar target/app.jar --spring.profiles.active=prod
# 指定内存
java -Xms512m -Xmx1024m -jar target/app.jar
# 后台运行
nohup java -jar target/app.jar > app.log 2>&1 &
# 使用 Systemd 管理
# /etc/systemd/system/app.service
[Unit]
Description=Spring Boot App
After=network.target
[Service]
Type=simple
User=appuser
WorkingDirectory=/opt/app
ExecStart=/usr/bin/java -jar /opt/app/app.jar
Restart=on-failure
RestartSec=10
[Install]
WantedBy=multi-user.target
10.2 Docker 部署
dockerfile
# Dockerfile
FROM openjdk:17-jdk-slim AS builder
WORKDIR /app
COPY . .
RUN ./mvnw clean package -DskipTests
FROM openjdk:17-jre-slim
WORKDIR /app
COPY --from=builder /app/target/app.jar app.jar
EXPOSE 8080
ENTRYPOINT ["java", "-jar", "app.jar"]
yaml
# docker-compose.yml
version: '3.8'
services:
app:
build: .
ports:
- "8080:8080"
environment:
- SPRING_PROFILES_ACTIVE=prod
- SPRING_DATASOURCE_URL=jdbc:mysql://mysql:3306/mydb
depends_on:
- mysql
- redis
networks:
- app-network
mysql:
image: mysql:8.0
environment:
MYSQL_ROOT_PASSWORD: 123456
MYSQL_DATABASE: mydb
volumes:
- mysql-data:/var/lib/mysql
ports:
- "3306:3306"
networks:
- app-network
redis:
image: redis:7-alpine
ports:
- "6379:6379"
networks:
- app-network
volumes:
mysql-data:
networks:
app-network:
driver: bridge
10.3 Kubernetes 部署
yaml
# deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: spring-boot-app
spec:
replicas: 3
selector:
matchLabels:
app: spring-boot-app
template:
metadata:
labels:
app: spring-boot-app
spec:
containers:
- name: app
image: myregistry/spring-boot-app:latest
ports:
- containerPort: 8080
env:
- name: SPRING_PROFILES_ACTIVE
value: "k8s"
- name: SPRING_DATASOURCE_URL
value: "jdbc:mysql://mysql-service:3306/mydb"
resources:
requests:
memory: "512Mi"
cpu: "250m"
limits:
memory: "1Gi"
cpu: "500m"
livenessProbe:
httpGet:
path: /actuator/health/liveness
port: 8080
initialDelaySeconds: 60
periodSeconds: 10
readinessProbe:
httpGet:
path: /actuator/health/readiness
port: 8080
initialDelaySeconds: 30
periodSeconds: 5
---
apiVersion: v1
kind: Service
metadata:
name: spring-boot-app-service
spec:
selector:
app: spring-boot-app
ports:
- port: 80
targetPort: 8080
type: LoadBalancer
---
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
name: spring-boot-app-hpa
spec:
scaleTargetRef:
apiVersion: apps/v1
kind: Deployment
name: spring-boot-app
minReplicas: 2
maxReplicas: 10
metrics:
- type: Resource
resource:
name: cpu
target:
type: Utilization
averageUtilization: 70
- type: Resource
resource:
name: memory
target:
type: Utilization
averageUtilization: 80
十一、Spring Boot 新特性
11.1 Spring Boot 3.x 新特性
// Jakarta EE 迁移(javax → jakarta)
import jakarta.persistence.Entity;
import jakarta.persistence.Table;
// GraalVM 原生镜像支持
@SpringBootApplication
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
// 构建原生镜像
// mvn -Pnative native:compile
// 问题详细(Problem Details)支持
@RestController
public class ProblemDetailsController {
@GetMapping("/error")
public ProblemDetail handleError() {
ProblemDetail problem = ProblemDetail.forStatus(HttpStatus.NOT_FOUND);
problem.setTitle("Resource Not Found");
problem.setDetail("The requested resource was not found");
problem.setProperty("timestamp", Instant.now());
return problem;
}
}
11.2 Spring Boot 3.2 新特性
// 虚拟线程支持
@Configuration
public class VirtualThreadConfig {
@Bean
public TomcatProtocolHandlerCustomizer<?> protocolHandlerCustomizer() {
return protocolHandler -> {
protocolHandler.setExecutor(Executors.newVirtualThreadPerTaskExecutor());
};
}
}
// 或通过配置
spring.threads.virtual.enabled=true
// 新的 RestClient
@Configuration
public class RestClientConfig {
@Bean
public RestClient restClient(RestClient.Builder builder) {
return builder
.baseUrl("https://api.example.com")
.defaultHeader("Accept", "application/json")
.build();
}
}
Spring Boot 作为 Java 生态中最受欢迎的框架,以其简洁的配置、强大的功能和出色的开发体验,极大地提升了开发效率。本文系统性地梳理了 Spring Boot 的核心知识点,从基础入门到高级特性,从数据访问到生产部署,帮助开发者建立完整的知识体系。
来源:
https://app-a6nw7st4g741.appmiaoda.com/