Spring Boot/Spring Cloud 应用日志书写详细指南

基于Spring Boot和Spring Cloud实现微服务架构学习(一)-Spring框架介绍 基于Spring Boot和Spring Cloud框架开发的微服务架构学习(一)-Spring框架介绍总结 看了几周Spring相关框架的书籍和官方demo,是时候开始总结下这中间的学习感悟。 首先,最想说的是,当你要学习一套最新的技术时,官网的英文文档是学习的最佳渠道。因为网上流传的多数资料是官网翻译而来,很多描述的重点也都偏向于作者自身碰到的问题,这样就很容易让你理解和操作出现偏差,最开始我就进入了这样误区。官网的技术导读真的描述的很详细,虽然对于我们看英文很费劲,但如果英文不是很差,请选 阅读详情

Spring Boot/Spring Cloud 应用日志书写详细指南

1. 日志框架选择与配置

1.1 推荐日志框架组合

<!-- Spring Boot 默认使用,无需额外引入 -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-logging</artifactId>
</dependency>

<!-- 如需使用 Log4j2 -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-log4j2</artifactId>
</dependency>

1.2 基础配置

# application.yml
logging:
  level:
    com.yourcompany: INFO
    org.springframework: WARN
    org.hibernate: ERROR
  pattern:
    console: "%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n"
    file: "%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n"
  file:
    name: logs/application.log
  logback:
    rollingpolicy:
      max-file-size: 10MB
      max-history: 30

2. 日志级别规范

2.1 级别定义与使用场景

@Component
public class UserService {
    private static final Logger logger = LoggerFactory.getLogger(UserService.class);
    
    public User createUser(UserDTO userDTO) {
        // TRACE: 详细的调试信息,通常在生产环境关闭
        logger.trace("开始创建用户,输入参数: {}", userDTO);
        
        // DEBUG: 调试信息,用于开发阶段问题定位
        logger.debug("用户数据验证通过,准备持久化");
        
        try {
            User user = userRepository.save(convertToEntity(userDTO));
            
            // INFO: 重要的业务流程节点
            logger.info("用户创建成功,用户ID: {}, 用户名: {}", user.getId(), user.getUsername());
            
            return user;
        } catch (DataIntegrityViolationException e) {
            // WARN: 非预期但可处理的情况
            logger.warn("用户数据重复,用户名: {}", userDTO.getUsername(), e);
            throw new BusinessException("用户已存在");
        } catch (Exception e) {
            // ERROR: 系统错误和异常情况
            logger.error("用户创建失败,用户名: {}", userDTO.getUsername(), e);
            throw new SystemException("系统内部错误");
        }
    }
}

3. 日志内容规范

3.1 结构化日志格式

@Service
@Slf4j // 推荐使用 Lombok @Slf4j 注解
public class OrderService {
    
    public Order createOrder(OrderRequest request) {
        // 关键业务操作开始日志
        log.info("创建订单开始 | 用户ID:{} | 商品数量:{} | 总金额:{}", 
                request.getUserId(), 
                request.getItems().size(),
                request.getTotalAmount());
        
        try {
            // 业务逻辑处理
            Order order = processOrder(request);
            
            // 成功日志包含关键业务ID
            log.info("订单创建成功 | 订单ID:{} | 状态:{} | 用户ID:{}", 
                    order.getId(), 
                    order.getStatus(),
                    order.getUserId());
            
            return order;
        } catch (InventoryException e) {
            // 异常日志包含上下文信息和异常
            log.error("库存不足创建订单失败 | 用户ID:{} | 错误码:{}", 
                     request.getUserId(), 
                     "INSUFFICIENT_INVENTORY", e);
            throw e;
        }
    }
}

3.2 JSON 结构化日志(推荐用于微服务)

@Component
public class StructuredLogger {
    
    public void logPaymentEvent(PaymentEvent event) {
        Map<String, Object> logData = new HashMap<>();
        logData.put("event", "payment_processed");
        logData.put("orderId", event.getOrderId());
        logData.put("amount", event.getAmount());
        logData.put("paymentMethod", event.getPaymentMethod());
        logData.put("timestamp", Instant.now().toString());
        logData.put("traceId", MDC.get("traceId"));
        
        log.info(JSON.toJSONString(logData));
    }
    
    public void logError(String errorCode, String message, String module, Throwable t) {
        Map<String, Object> errorLog = new HashMap<>();
        errorLog.put("level", "ERROR");
        errorLog.put("errorCode", errorCode);
        errorLog.put("message", message);
        errorLog.put("module", module);
        errorLog.put("timestamp", Instant.now().toString());
        errorLog.put("stackTrace", getStackTrace(t));
        errorLog.put("traceId", MDC.get("traceId"));
        
        log.error(JSON.toJSONString(errorLog));
    }
    
    private String getStackTrace(Throwable t) {
        StringWriter sw = new StringWriter();
        t.printStackTrace(new PrintWriter(sw));
        return sw.toString();
    }
}

4. 日志上下文信息

4.1 使用 MDC (Mapped Diagnostic Context)

@Aspect
@Component
@Slf4j
public class LoggingAspect {
    
    @Around("execution(* com.yourcompany..*Controller.*(..))")
    public Object logController(ProceedingJoinPoint joinPoint) throws Throwable {
        // 设置请求上下文
        MDC.put("traceId", generateTraceId());
        MDC.put("userId", getCurrentUserId());
        MDC.put("clientIp", getClientIp());
        
        String className = joinPoint.getTarget().getClass().getSimpleName();
        String methodName = joinPoint.getSignature().getName();
        
        log.info("请求开始 | 类:{} | 方法:{}", className, methodName);
        
        long startTime = System.currentTimeMillis();
        try {
            Object result = joinPoint.proceed();
            long executionTime = System.currentTimeMillis() - startTime;
            
            log.info("请求完成 | 类:{} | 方法:{} | 耗时:{}ms", 
                    className, methodName, executionTime);
            return result;
        } catch (Exception e) {
            long executionTime = System.currentTimeMillis() - startTime;
            log.error("请求异常 | 类:{} | 方法:{} | 耗时:{}ms | 异常:{}", 
                     className, methodName, executionTime, e.getMessage(), e);
            throw e;
        } finally {
            // 清理 MDC
            MDC.clear();
        }
    }
}

4.2 请求链路追踪

@Component
public class TraceFilter implements Filter {
    
    @Override
    public void doFilter(ServletRequest request, ServletResponse response, 
                        FilterChain chain) throws IOException, ServletException {
        
        HttpServletRequest httpRequest = (HttpServletRequest) request;
        String traceId = httpRequest.getHeader("X-Trace-Id");
        if (traceId == null || traceId.isEmpty()) {
            traceId = generateTraceId();
        }
        
        MDC.put("traceId", traceId);
        MDC.put("requestUri", httpRequest.getRequestURI());
        MDC.put("userAgent", httpRequest.getHeader("User-Agent"));
        
        try {
            chain.doFilter(request, response);
        } finally {
            MDC.clear();
        }
    }
}

5. 性能优化建议

5.1 避免日志性能问题

@Service
@Slf4j
public class PerformanceSensitiveService {
    
    // 不好的写法:在调试级别进行字符串拼接
    public void badLogging(User user) {
        // 即使日志级别高于DEBUG,字符串拼接仍然会执行
        log.debug("用户信息: " + user.toString() + ", 时间: " + System.currentTimeMillis());
    }
    
    // 好的写法:使用参数化日志
    public void goodLogging(User user) {
        // 只有在DEBUG级别启用时才会进行字符串处理
        log.debug("用户信息: {}, 时间: {}", user, System.currentTimeMillis());
    }
    
    // 对于复杂的日志消息,使用条件判断
    public void conditionalLogging(List<User> users) {
        if (log.isDebugEnabled()) {
            log.debug("处理用户列表: {}", formatUserList(users)); // formatUserList 比较耗时
        }
    }
}

6. 异常日志处理

6.1 统一的异常日志处理

@RestControllerAdvice
@Slf4j
public class GlobalExceptionHandler {
    
    @ExceptionHandler(BusinessException.class)
    public ResponseEntity<ErrorResponse> handleBusinessException(BusinessException e) {
        // WARN 级别记录业务异常
        log.warn("业务异常 | 错误码:{} | 错误信息:{} | 请求路径:{}", 
                e.getErrorCode(), e.getMessage(), getCurrentRequestPath());
        return ResponseEntity.status(HttpStatus.BAD_REQUEST)
                .body(new ErrorResponse(e.getErrorCode(), e.getMessage()));
    }
    
    @ExceptionHandler(Exception.class)
    public ResponseEntity<ErrorResponse> handleSystemException(Exception e) {
        // ERROR 级别记录系统异常
        log.error("系统异常 | 异常类型:{} | 请求路径:{}", 
                 e.getClass().getSimpleName(), getCurrentRequestPath(), e);
        return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR)
                .body(new ErrorResponse("SYSTEM_ERROR", "系统内部错误"));
    }
}

7. 日志配置文件示例

7.1 Logback 配置 (logback-spring.xml)

<?xml version="1.0" encoding="UTF-8"?>
<configuration>
    <property name="LOG_PATTERN" value="%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} [%X{traceId}] - %msg%n"/>
    <property name="LOG_PATH" value="logs"/>
    
    <!-- 控制台输出 -->
    <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}/application.log</file>
        <encoder>
            <pattern>${LOG_PATTERN}</pattern>
        </encoder>
        <rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
            <fileNamePattern>${LOG_PATH}/application.%d{yyyy-MM-dd}.%i.log</fileNamePattern>
            <maxFileSize>10MB</maxFileSize>
            <maxHistory>30</maxHistory>
            <totalSizeCap>1GB</totalSizeCap>
        </rollingPolicy>
    </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>
        <encoder>
            <pattern>${LOG_PATTERN}</pattern>
        </encoder>
        <rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
            <fileNamePattern>${LOG_PATH}/error.%d{yyyy-MM-dd}.%i.log</fileNamePattern>
            <maxFileSize>10MB</maxFileSize>
            <maxHistory>30</maxHistory>
        </rollingPolicy>
    </appender>
    
    <root level="INFO">
        <appender-ref ref="CONSOLE"/>
        <appender-ref ref="FILE"/>
        <appender-ref ref="ERROR_FILE"/>
    </root>
    
    <!-- 特定包日志级别 -->
    <logger name="com.yourcompany" level="DEBUG"/>
    <logger name="org.springframework" level="WARN"/>
    <logger name="org.hibernate" level="WARN"/>
</configuration>

8. 最佳实践总结

8.1 必须遵循的原则

  1. 适当的日志级别:生产环境使用 INFO,开发环境使用 DEBUG
  2. 有意义的日志消息:包含足够的上下文信息
  3. 统一格式:团队使用统一的日志格式
  4. 性能考虑:避免在日志中执行耗时操作
  5. 安全考虑:不要记录敏感信息(密码、token等)

8.2 推荐的日志场景

// 1. 方法入口和出口
log.info("方法开始: {},参数: {}", methodName, parameters);
log.info("方法结束: {},结果: {}", methodName, result);

// 2. 业务流程关键节点
log.info("订单状态变更 | 订单ID:{} | 原状态:{} | 新状态:{}", orderId, oldStatus, newStatus);

// 3. 外部调用
log.info("调用外部服务开始 | 服务名:{} | 请求参数:{}", serviceName, request);
log.info("调用外部服务结束 | 服务名:{} | 响应结果:{}", serviceName, response);

// 4. 异常情况
log.error("数据库操作失败 | SQL:{} | 参数:{}", sql, params, e);

// 5. 定时任务
log.info("定时任务开始 | 任务名:{} | 执行时间:{}", taskName, startTime);
log.info("定时任务结束 | 任务名:{} | 执行结果:{} | 耗时:{}ms", taskName, result, costTime);

通过遵循这些指南,可以建立统一、可维护、高效的日志系统,大大提高系统的可观测性和问题排查效率。

Spring Boot/Cloud 全场景日志书写示例

1. 控制器层日志

1.1 REST Controller 完整示例

@RestController
@RequestMapping("/api/v1/users")
@Slf4j
public class UserController {
    
    @GetMapping("/{userId}")
    public ResponseEntity<UserVO> getUser(@PathVariable Long userId,
                                         @RequestHeader("User-Agent") String userAgent) {
        // 请求入口日志
        log.info("查询用户开始 | 用户ID:{} | 客户端:{} | 线程:{}", 
                userId, userAgent, Thread.currentThread().getName());
        
        long startTime = System.currentTimeMillis();
        try {
            UserVO user = userService.getUserById(userId);
            
            // 成功响应日志
            log.info("查询用户成功 | 用户ID:{} | 用户名:{} | 耗时:{}ms", 
                    userId, user.getUsername(), System.currentTimeMillis() - startTime);
            
            return ResponseEntity.ok(user);
        } catch (UserNotFoundException e) {
            // 业务异常日志
            log.warn("用户不存在 | 用户ID:{} | 耗时:{}ms", 
                    userId, System.currentTimeMillis() - startTime, e);
            return ResponseEntity.notFound().build();
        } catch (Exception e) {
            // 系统异常日志
            log.error("查询用户异常 | 用户ID:{} | 异常类型:{} | 耗时:{}ms", 
                     userId, e.getClass().getSimpleName(), 
                     System.currentTimeMillis() - startTime, e);
            return ResponseEntity.internalServerError().build();
        }
    }
    
    @PostMapping
    public ResponseEntity<CreateUserResponse> createUser(@Valid @RequestBody CreateUserRequest request,
                                                        HttpServletRequest httpRequest) {
        String clientIp = httpRequest.getRemoteAddr();
        log.info("创建用户开始 | 用户名:{} | 邮箱:{} | 客户端IP:{}", 
                request.getUsername(), request.getEmail(), clientIp);
        
        // 敏感信息脱敏
        log.debug("创建用户详细参数 | 用户名:{} | 角色:{} | 部门:{}", 
                 request.getUsername(), request.getRole(), request.getDepartment());
        
        long startTime = System.currentTimeMillis();
        try {
            UserVO user = userService.createUser(request);
            
            log.info("创建用户成功 | 用户ID:{} | 用户名:{} | 耗时:{}ms", 
                    user.getId(), user.getUsername(), System.currentTimeMillis() - startTime);
            
            return ResponseEntity.status(HttpStatus.CREATED)
                    .body(new CreateUserResponse(user.getId(), "创建成功"));
        } catch (DuplicateUserException e) {
            log.warn("创建用户失败-用户已存在 | 用户名:{} | 邮箱:{}", 
                    request.getUsername(), request.getEmail(), e);
            return ResponseEntity.badRequest()
                    .body(new CreateUserResponse(null, "用户已存在"));
        }
    }
    
    @PutMapping("/{userId}/status")
    public ResponseEntity<Void> updateUserStatus(@PathVariable Long userId,
                                                @RequestBody UpdateStatusRequest request) {
        log.info("更新用户状态开始 | 用户ID:{} | 原状态:{} | 新状态:{}", 
                userId, getCurrentStatus(userId), request.getStatus());
        
        userService.updateStatus(userId, request.getStatus());
        
        log.info("更新用户状态成功 | 用户ID:{} | 新状态:{}", userId, request.getStatus());
        return ResponseEntity.ok().build();
    }
}

2. 服务层日志

2.1 业务服务完整示例

@Service
@Slf4j
@Transactional
public class OrderService {
    
    public Order createOrder(CreateOrderRequest request) {
        log.info("创建订单开始 | 用户ID:{} | 商品数量:{} | 总金额:{}", 
                request.getUserId(), request.getItems().size(), request.getTotalAmount());
        
        // 参数验证日志
        if (log.isDebugEnabled()) {
            log.debug("订单详细参数 | 商品列表:{} | 收货地址:{}", 
                     request.getItems(), maskSensitiveInfo(request.getAddress()));
        }
        
        try {
            // 1. 验证库存
            log.debug("开始验证库存 | 商品数量:{}", request.getItems().size());
            inventoryService.validateStock(request.getItems());
            log.debug("库存验证通过");
            
            // 2. 计算价格
            log.debug("开始计算订单价格");
            BigDecimal finalAmount = calculateFinalAmount(request);
            log.debug("价格计算完成 | 最终金额:{}", finalAmount);
            
            // 3. 创建订单
            Order order = buildOrder(request, finalAmount);
            order = orderRepository.save(order);
            log.info("订单创建完成 | 订单ID:{} | 订单号:{} | 状态:{}", 
                    order.getId(), order.getOrderNo(), order.getStatus());
            
            // 4. 扣减库存
            log.debug("开始扣减库存 | 订单ID:{}", order.getId());
            inventoryService.deductStock(order.getId(), request.getItems());
            log.debug("库存扣减完成");
            
            // 5. 发送创建事件
            eventPublisher.publishEvent(new OrderCreatedEvent(order));
            log.debug("订单创建事件已发布");
            
            log.info("订单流程全部完成 | 订单ID:{} | 总耗时:{}ms", 
                    order.getId(), System.currentTimeMillis() - startTime);
            
            return order;
        } catch (InventoryException e) {
            log.error("创建订单失败-库存不足 | 用户ID:{} | 错误详情:{}", 
                     request.getUserId(), e.getMessage(), e);
            throw new BusinessException("库存不足,创建订单失败");
        } catch (PaymentException e) {
            log.error("创建订单失败-支付异常 | 用户ID:{} | 错误码:{}", 
                     request.getUserId(), e.getErrorCode(), e);
            throw new BusinessException("支付处理失败");
        } catch (Exception e) {
            log.error("创建订单失败-系统异常 | 用户ID:{} | 异常类型:{}", 
                     request.getUserId(), e.getClass().getSimpleName(), e);
            throw new SystemException("系统繁忙,请稍后重试");
        }
    }
    
    public void processExpiredOrders() {
        log.info("开始处理过期订单 | 线程:{}", Thread.currentThread().getName());
        
        int processedCount = 0;
        int successCount = 0;
        int failureCount = 0;
        long startTime = System.currentTimeMillis();
        
        try {
            List<Order> expiredOrders = orderRepository.findExpiredOrders();
            log.info("找到过期订单数量:{}", expiredOrders.size());
            
            for (Order order : expiredOrders) {
                processedCount++;
                try {
                    log.debug("处理过期订单 | 订单ID:{} | 订单号:{}", order.getId(), order.getOrderNo());
                    cancelOrder(order.getId(), "系统自动取消-超时未支付");
                    successCount++;
                    log.debug("过期订单处理成功 | 订单ID:{}", order.getId());
                } catch (Exception e) {
                    failureCount++;
                    log.error("处理过期订单失败 | 订单ID:{} | 异常:{}", order.getId(), e.getMessage(), e);
                }
            }
            
            log.info("过期订单处理完成 | 总数:{} | 成功:{} | 失败:{} | 总耗时:{}ms", 
                    processedCount, successCount, failureCount, 
                    System.currentTimeMillis() - startTime);
        } catch (Exception e) {
            log.error("处理过期订单任务异常 | 异常类型:{}", e.getClass().getSimpleName(), e);
            throw e;
        }
    }
}

3. 数据访问层日志

3.1 Repository 日志示例

@Repository
@Slf4j
public class UserRepository {
    
    public User findByUsername(String username) {
        log.debug("根据用户名查询用户 | 用户名:{}", username);
        
        long startTime = System.currentTimeMillis();
        try {
            User user = userMapper.selectByUsername(username);
            
            if (user == null) {
                log.debug("用户不存在 | 用户名:{} | 耗时:{}ms", 
                         username, System.currentTimeMillis() - startTime);
            } else {
                log.debug("用户查询成功 | 用户ID:{} | 用户名:{} | 耗时:{}ms", 
                         user.getId(), username, System.currentTimeMillis() - startTime);
            }
            
            return user;
        } catch (DataAccessException e) {
            log.error("数据库查询异常 | SQL:查询用户 | 参数:{} | 异常类型:{}", 
                     username, e.getClass().getSimpleName(), e);
            throw new RepositoryException("用户查询失败", e);
        }
    }
    
    @Transactional
    public void updateUserStatus(Long userId, UserStatus newStatus) {
        UserStatus oldStatus = getUserStatus(userId);
        log.info("更新用户状态 | 用户ID:{} | 原状态:{} | 新状态:{}", userId, oldStatus, newStatus);
        
        try {
            int affectedRows = userMapper.updateStatus(userId, newStatus);
            
            if (affectedRows == 0) {
                log.warn("更新用户状态失败-记录不存在 | 用户ID:{}", userId);
                throw new EntityNotFoundException("用户不存在");
            }
            
            log.info("用户状态更新成功 | 用户ID:{} | 影响行数:{}", userId, affectedRows);
        } catch (DataIntegrityViolationException e) {
            log.error("更新用户状态失败-数据完整性异常 | 用户ID:{} | 新状态:{}", userId, newStatus, e);
            throw new RepositoryException("数据完整性约束冲突", e);
        }
    }
    
    public Page<User> findUsersByCondition(UserQuery query, Pageable pageable) {
        if (log.isDebugEnabled()) {
            log.debug("分页查询用户 | 查询条件:{} | 分页参数:{}", 
                     query.toString(), pageable.toString());
        }
        
        long startTime = System.currentTimeMillis();
        try {
            Page<User> result = userMapper.selectByCondition(query, pageable);
            
            log.info("用户分页查询完成 | 总数:{} | 当前页:{} | 耗时:{}ms", 
                    result.getTotalElements(), result.getNumber(), 
                    System.currentTimeMillis() - startTime);
            
            return result;
        } catch (DataAccessException e) {
            log.error("用户分页查询异常 | 查询条件:{} | 异常类型:{}", 
                     query, e.getClass().getSimpleName(), e);
            throw new RepositoryException("用户查询失败", e);
        }
    }
}

4. 外部服务调用日志

4.1 HTTP 客户端调用

@Component
@Slf4j
public class PaymentServiceClient {
    
    public PaymentResponse createPayment(PaymentRequest request) {
        String requestId = generateRequestId();
        log.info("调用支付服务开始 | 请求ID:{} | 订单号:{} | 金额:{} | 端点:{}", 
                requestId, request.getOrderNo(), request.getAmount(), "/api/payments");
        
        long startTime = System.currentTimeMillis();
        try {
            // 记录请求详情(敏感信息脱敏)
            if (log.isDebugEnabled()) {
                log.debug("支付请求详情 | 请求ID:{} | 支付方式:{} | 银行编码:{}", 
                         requestId, request.getPaymentMethod(), 
                         maskBankCode(request.getBankCode()));
            }
            
            PaymentResponse response = restTemplate.postForObject(
                paymentServiceUrl + "/api/payments", request, PaymentResponse.class);
            
            log.info("支付服务调用成功 | 请求ID:{} | 支付状态:{} | 耗时:{}ms", 
                    requestId, response.getStatus(), System.currentTimeMillis() - startTime);
            
            return response;
        } catch (HttpClientErrorException e) {
            log.warn("支付服务调用失败-客户端错误 | 请求ID:{} | 状态码:{} | 响应:{}", 
                    requestId, e.getStatusCode(), e.getResponseBodyAsString(), e);
            throw new PaymentException("支付请求参数错误");
        } catch (HttpServerErrorException e) {
            log.error("支付服务调用失败-服务端错误 | 请求ID:{} | 状态码:{} | 异常:{}", 
                     requestId, e.getStatusCode(), e.getMessage(), e);
            throw new PaymentException("支付服务暂时不可用");
        } catch (ResourceAccessException e) {
            log.error("支付服务调用失败-网络异常 | 请求ID:{} | 异常类型:{}", 
                     requestId, e.getClass().getSimpleName(), e);
            throw new PaymentException("网络连接超时");
        } catch (Exception e) {
            log.error("支付服务调用失败-未知异常 | 请求ID:{} | 异常类型:{}", 
                     requestId, e.getClass().getSimpleName(), e);
            throw new PaymentException("支付处理失败");
        }
    }
}

4.2 Feign Client 调用

@FeignClient(name = "inventory-service")
@Slf4j
public class InventoryServiceClient {
    
    @PostMapping("/api/inventory/deduct")
    ResponseEntity<InventoryResponse> deductStock(@RequestBody DeductRequest request);
    
    // Feign 配置类中的日志
    @Component
    @Slf4j
    public class FeignLogger extends Logger {
        @Override
        protected void log(String configKey, String format, Object... args) {
            if (log.isInfoEnabled()) {
                log.info(String.format(methodTag(configKey) + format, args));
            }
        }
    }
}

5. 消息队列处理日志

5.1 RabbitMQ 消费者

@Component
@Slf4j
public class OrderMessageConsumer {
    
    @RabbitListener(queues = "order.create.queue")
    public void handleOrderCreate(OrderCreateMessage message, Channel channel, 
                                @Header(AmqpHeaders.DELIVERY_TAG) long deliveryTag) {
        log.info("收到订单创建消息 | 消息ID:{} | 订单号:{} | 路由键:{}", 
                message.getMessageId(), message.getOrderNo(), "order.create");
        
        long startTime = System.currentTimeMillis();
        try {
            // 业务处理
            orderService.processOrderCreate(message);
            
            // 手动确认消息
            channel.basicAck(deliveryTag, false);
            log.info("订单消息处理成功 | 消息ID:{} | 订单号:{} | 耗时:{}ms", 
                    message.getMessageId(), message.getOrderNo(), 
                    System.currentTimeMillis() - startTime);
        } catch (BusinessException e) {
            log.warn("订单消息处理失败-业务异常 | 消息ID:{} | 订单号:{} | 错误:{}", 
                    message.getMessageId(), message.getOrderNo(), e.getMessage());
            
            // 业务异常,拒绝消息并不再重试
            channel.basicReject(deliveryTag, false);
        } catch (Exception e) {
            log.error("订单消息处理失败-系统异常 | 消息ID:{} | 订单号:{} | 异常类型:{}", 
                     message.getMessageId(), message.getOrderNo(), 
                     e.getClass().getSimpleName(), e);
            
            // 系统异常,拒绝消息并重新入队
            channel.basicReject(deliveryTag, true);
        }
    }
}

6. 定时任务日志

6.1 分布式定时任务

@Component
@Slf4j
public class ScheduledTasks {
    
    @Scheduled(cron = "0 0 2 * * ?") // 每天凌晨2点执行
    @SchedulerLock(name = "dataCleanupTask", lockAtMostFor = "30m")
    public void dataCleanupTask() {
        String taskId = UUID.randomUUID().toString();
        log.info("数据清理任务开始 | 任务ID:{} | 执行时间:{}", 
                taskId, LocalDateTime.now().format(DateTimeFormatter.ISO_LOCAL_DATE_TIME));
        
        long startTime = System.currentTimeMillis();
        int successCount = 0;
        int failureCount = 0;
        
        try {
            // 清理过期订单
            log.debug("开始清理过期订单 | 任务ID:{}", taskId);
            int orderCount = orderService.cleanExpiredOrders();
            log.debug("过期订单清理完成 | 清理数量:{}", orderCount);
            successCount++;
            
            // 清理日志文件
            log.debug("开始清理日志文件 | 任务ID:{}", taskId);
            int logCount = logService.cleanOldLogs();
            log.debug("日志文件清理完成 | 清理数量:{}", logCount);
            successCount++;
            
            log.info("数据清理任务完成 | 任务ID:{} | 成功子任务:{} | 失败子任务:{} | 总耗时:{}ms", 
                    taskId, successCount, failureCount, 
                    System.currentTimeMillis() - startTime);
        } catch (Exception e) {
            log.error("数据清理任务异常 | 任务ID:{} | 异常类型:{}", 
                     taskId, e.getClass().getSimpleName(), e);
            throw e;
        }
    }
}

7. 安全认证日志

7.1 JWT 认证日志

@Component
@Slf4j
public class JwtTokenProvider {
    
    public String generateToken(UserDetails userDetails) {
        log.debug("生成JWT Token开始 | 用户名:{} | 角色:{}", 
                 userDetails.getUsername(), userDetails.getAuthorities());
        
        String token = doGenerateToken(userDetails);
        
        log.info("JWT Token生成成功 | 用户名:{} | Token前缀:{}", 
                userDetails.getUsername(), token.substring(0, 20));
        return token;
    }
    
    public boolean validateToken(String token) {
        log.debug("验证JWT Token | Token长度:{}", token.length());
        
        try {
            boolean isValid = doValidateToken(token);
            
            if (isValid) {
                log.debug("JWT Token验证通过");
            } else {
                log.warn("JWT Token验证失败");
            }
            
            return isValid;
        } catch (ExpiredJwtException e) {
            log.warn("JWT Token已过期 | 过期时间:{}", e.getClaims().getExpiration());
            return false;
        } catch (MalformedJwtException e) {
            log.warn("JWT Token格式错误");
            return false;
        } catch (Exception e) {
            log.error("JWT Token验证异常 | 异常类型:{}", e.getClass().getSimpleName(), e);
            return false;
        }
    }
}

8. 文件操作日志

8.1 文件上传下载

@Service
@Slf4j
public class FileService {
    
    public FileUploadResponse uploadFile(MultipartFile file, String businessType) {
        log.info("文件上传开始 | 文件名:{} | 文件大小:{} | 业务类型:{}", 
                file.getOriginalFilename(), file.getSize(), businessType);
        
        long startTime = System.currentTimeMillis();
        try {
            // 文件验证
            validateFile(file);
            log.debug("文件验证通过 | 文件名:{}", file.getOriginalFilename());
            
            // 上传处理
            String filePath = saveFile(file);
            log.debug("文件保存完成 | 存储路径:{}", filePath);
            
            FileUploadResponse response = new FileUploadResponse(filePath, "上传成功");
            
            log.info("文件上传成功 | 文件名:{} | 文件路径:{} | 耗时:{}ms", 
                    file.getOriginalFilename(), filePath, 
                    System.currentTimeMillis() - startTime);
            
            return response;
        } catch (FileSizeException e) {
            log.warn("文件上传失败-文件大小超限 | 文件名:{} | 实际大小:{} | 限制大小:{}", 
                    file.getOriginalFilename(), file.getSize(), e.getLimitSize());
            throw new BusinessException("文件大小超过限制");
        } catch (FileTypeException e) {
            log.warn("文件上传失败-文件类型不支持 | 文件名:{} | 文件类型:{}", 
                    file.getOriginalFilename(), e.getFileType());
            throw new BusinessException("不支持的文件类型");
        } catch (IOException e) {
            log.error("文件上传失败-IO异常 | 文件名:{} | 异常类型:{}", 
                     file.getOriginalFilename(), e.getClass().getSimpleName(), e);
            throw new SystemException("文件上传失败");
        }
    }
}

9. 缓存操作日志

9.1 Redis 缓存操作

@Service
@Slf4j
public class CacheService {
    
    public <T> T getFromCache(String key, Class<T> clazz) {
        log.debug("查询缓存开始 | 缓存键:{} | 类型:{}", key, clazz.getSimpleName());
        
        long startTime = System.currentTimeMillis();
        try {
            T value = redisTemplate.opsForValue().get(key);
            
            if (value == null) {
                log.debug("缓存未命中 | 缓存键:{} | 耗时:{}ms", 
                         key, System.currentTimeMillis() - startTime);
            } else {
                log.debug("缓存命中 | 缓存键:{} | 耗时:{}ms", 
                         key, System.currentTimeMillis() - startTime);
            }
            
            return value;
        } catch (RedisConnectionFailureException e) {
            log.error("缓存查询失败-连接异常 | 缓存键:{}", key, e);
            throw new CacheException("缓存服务不可用");
        } catch (Exception e) {
            log.error("缓存查询失败-未知异常 | 缓存键:{} | 异常类型:{}", 
                     key, e.getClass().getSimpleName(), e);
            throw new CacheException("缓存查询失败");
        }
    }
    
    public void setToCache(String key, Object value, Duration ttl) {
        log.debug("设置缓存开始 | 缓存键:{} | TTL:{}", key, ttl);
        
        try {
            redisTemplate.opsForValue().set(key, value, ttl);
            log.debug("缓存设置成功 | 缓存键:{}", key);
        } catch (Exception e) {
            log.error("缓存设置失败 | 缓存键:{} | 异常类型:{}", 
                     key, e.getClass().getSimpleName(), e);
            // 缓存设置失败不应该影响主流程
        }
    }
}

10. 全局异常处理日志

10.1 统一异常处理器

@RestControllerAdvice
@Slf4j
public class GlobalExceptionHandler {
    
    @ExceptionHandler(BusinessException.class)
    public ResponseEntity<ErrorResponse> handleBusinessException(BusinessException e, 
                                                               HttpServletRequest request) {
        String errorId = generateErrorId();
        log.warn("业务异常处理 | 错误ID:{} | 错误码:{} | 错误信息:{} | 请求路径:{} | 客户端IP:{}", 
                errorId, e.getErrorCode(), e.getMessage(), 
                request.getRequestURI(), getClientIp(request));
        
        return ResponseEntity.status(HttpStatus.BAD_REQUEST)
                .body(new ErrorResponse(errorId, e.getErrorCode(), e.getMessage()));
    }
    
    @ExceptionHandler(AuthenticationException.class)
    public ResponseEntity<ErrorResponse> handleAuthException(AuthenticationException e,
                                                           HttpServletRequest request) {
        String errorId = generateErrorId();
        log.warn("认证异常处理 | 错误ID:{} | 错误信息:{} | 请求路径:{} | 客户端IP:{}", 
                errorId, e.getMessage(), request.getRequestURI(), getClientIp(request));
        
        return ResponseEntity.status(HttpStatus.UNAUTHORIZED)
                .body(new ErrorResponse(errorId, "UNAUTHORIZED", "认证失败"));
    }
    
    @ExceptionHandler(Exception.class)
    public ResponseEntity<ErrorResponse> handleSystemException(Exception e,
                                                             HttpServletRequest request) {
        String errorId = generateErrorId();
        log.error("系统异常处理 | 错误ID:{} | 异常类型:{} | 请求路径:{} | 客户端IP:{} | 堆栈跟踪:{}", 
                 errorId, e.getClass().getSimpleName(), request.getRequestURI(), 
                 getClientIp(request), getStackTrace(e));
        
        // 生产环境隐藏详细错误信息
        String message = isProduction() ? "系统内部错误" : e.getMessage();
        
        return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR)
                .body(new ErrorResponse(errorId, "SYSTEM_ERROR", message));
    }
}

11. 配置类日志

11.1 应用启动配置

@SpringBootApplication
@Slf4j
public class Application {
    
    public static void main(String[] args) {
        SpringApplication app = new SpringApplication(Application.class);
        
        // 启动前日志
        log.info("应用启动开始 | 环境:{} | 版本:{} | 启动时间:{}", 
                getActiveProfile(), getAppVersion(), LocalDateTime.now());
        
        try {
            ConfigurableApplicationContext context = app.run(args);
            
            // 启动成功日志
            Environment env = context.getEnvironment();
            String port = env.getProperty("server.port");
            String contextPath = env.getProperty("server.servlet.context-path", "");
            
            log.info("应用启动成功 | 环境:{} | 端口:{} | 上下文路径:{} | 启动耗时:{}ms", 
                    getActiveProfile(), port, contextPath, 
                    System.currentTimeMillis() - startTime);
            
            // 记录重要的配置信息
            log.info("数据源配置 | URL:{} | 用户名:{}", 
                    maskDatabaseUrl(env.getProperty("spring.datasource.url")),
                    env.getProperty("spring.datasource.username"));
                    
        } catch (Exception e) {
            log.error("应用启动失败 | 异常类型:{}", e.getClass().getSimpleName(), e);
            System.exit(1);
        }
    }
}

这些示例覆盖了 Spring Boot/Cloud 应用中绝大多数常见的日志场景,提供了完整的、生产可用的日志记录模式。在实际开发中,可以根据具体业务需求进行调整和扩展。

Spring Cloud填坑日志(3)——Spring Boot Actuator的/pause端点使用 背景 &amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;最近在学习周立大神的《Spring Cloud与Docker微服务架构实战》,在翻阅Eureka的健康检查这一部分的时候,发现有一个小备注: 当eureka.client.healthcheck.enabled=true时,/pause端点(该端点由Spring Boot Actuator提供,用于暂停应用)无法正常工作,经笔者测试,发现当... 阅读详情

相关推荐

Java Spring、Spring MVC、Spring Boot 和 Spring Cloud 的关系与区别

本文详细介绍了 Java 开发中常用的 Spring 系列框架及其关系。Spring 是核心框架,提供控制反转(IoC)和面向切面编程(AOP)等基础功能。Spring MVC 是 Spring 的模块,专注于 Web 开发,采用 MVC 设计模式。Spring Boot 简化了 Spring 应用的初始搭建和开发,提供默认配置和嵌入式服务器。Spring Cloud 基于 Spring Boot,扩展了分布式系统开发的能力,提供服务发现、熔断器等功能。这些框架共同构成了强大的 Java 开发生态系统,满足

C_V_Better的博客 1306

SpringCloud使用logback日志框架教程

本文介绍了如何使用SpringCloud使用Logback日志框架,文章总结提供了使用Logback框架的实现步骤,以及可能出现的一些问题,以及一些常用的配置项,包括根据不同的环境配置不同日志输出,控制日志文件大小等保留策略,以便读者了解如何构建可靠的微服务应用程序。

五道口 4874

Spring boot与Spring cloud 之间的关系

1、Spring boot 是 Spring (AOP和IOC)的一套快速配置脚手架,可以基于spring boot 快速开发单个微服务; Spring Cloud是一个基于Spring Boot实现的云应用开发工具;Spring Cloud是多个Spring boot微服务的集合,当然不是简单的集合,那么就涉及到微服务的七大原则:1)、围绕业务概念建模经验表明,围绕业务的限界上下文定义的接口,比围绕技术概念定义的接口更加稳定。针对系统如何工作这个领域进行建模,不仅可以帮助我们形成更稳定的接口,也

CaseyWei 5884

Spring Cloud 全链路日志traceId

​ 随着业务量的增加,线上出现越来越多的bug,但是由于使用的是Spring Cloud,微服务之间调用,输出的日志没有固定上下文管理,定位具体问题存在诸多不便,因此相当有必要引入全链路日志traceId。MDC(Mapped Diagnostic Context,映射调试上下文)是 log4j 、logback及log4j2 提供的一种方便在多线程条件下记录日志的功能。借助MDC可以非常方便的将traceId自动输出到日志中,无需手动拼写traceId,代码简单,风格统一。.........

AndCo的博客 7830

Spring Cloud与Spring Boot版本匹配指南

微服务架构中,Spring Cloud作为基于Spring Boot的扩展框架,其版本兼容性直接影响系统稳定性。通过BOM(物料清单)管理机制,Spring Cloud与Spring Boot形成严格的版本对应关系,这种设计既保证了自动配置等核心功能的可靠性,也避免了依赖冲突。开发实践中常见的ClassNotFoundException、Bean创建失败等问题,往往源于版本不匹配。在金融、电商等企业级应用中,正确的版本组合能确保服务注册发现、配置中心等微服务组件稳定运行。本文结合Spring Cloud 2

weixin_34393428的博客 372

Spring Cloud与Spring Boot版本匹配关系解析

微服务架构中,Spring Cloud作为Spring Boot的增强套件,其版本兼容性直接影响系统稳定性。版本匹配遵循严格的地基与上层结构关系,如2023年推荐的Spring Cloud 2022.0.x与Spring Boot 3.0.x组合。理解版本号语义化规则(主版本、次版本、修订号)及官方版本对照表是避免依赖冲突的关键。企业级项目中,通过Maven或Gradle的BOM(Bill of Materials)管理依赖版本,结合CI/CD管道中的版本校验脚本,可有效预防版本错配问题。本文以Spring

weixin_33721344的博客 595

在Spring Cloud架构下使用Spring Boot的Logback日志处理方案

或者使用Lombok @Slf4j 注解 参考:https://blog.youkuaiyun.com/qq_20299791/article/details/89852458。定义了日志输出的方式,包括控制台输出和文件输出。其中,文件输出使用了滚动策略,即以时间为基准,每天生成一个。定义了日志输出的格式,包括时间、线程、日志级别、类名、日志信息等。其中,LOG_HOME为日志文件输出路径,可以根据需要进行修改。

weixin_43465090的博客 2737

Spring、Spring MVC、Spring Boot 与 Spring Cloud 的关系解析!

专门解决 “多栋楼协同管理”(微服务架构中多个独立服务协同工作)的问题 —— 相当于小区的 “物业系统”:当小区盖了 “住户楼、商铺楼、停车场楼”(多个微服务)后,需要 “水电管网连接、安保巡逻、住户登记”(服务注册、负载均衡、熔断降级),这些都靠 Spring Cloud 实现。微服务是 “小区架构方案”(多栋楼),Spring Cloud 是 “实现该方案的运维工具”—— 方案≠工具,比如 “盖小区” 是方案,“物业系统” 是工具。,没有 Spring 就没有后两者。、Spring MVC 的。

qq_53040109的博客 1039

Spring Cloud 日志与监控 日志收集

日志与监控是 Spring Cloud 微服务中不可或缺的一部分。通过 ELK Stack、Prometheus、Spring Cloud Sleuth 等工具,开发者可以实现全面的日志收集和监控能力。结合最佳实践,构建稳定、高效的日志系统,为微服务应用的运行与优化提供有力支持。

Flying_Fish_roe的博客 1824

【云原生技术】 Servlet、Spring Framework、Spring Boot 和 Spring Cloud 及其关系的详细解释

Servlet是 Java Web 应用的一部分,用于处理 HTTP 请求和响应。是一个全面的 Java 开发框架,提供强大的功能支持。和是 Spring Framework 的扩展,分别用于快速应用开发和微服务架构。CRUD 操作是数据管理的基本操作,适用于几乎所有数据库交互场景。

weixin_46453070的博客 949

VINS-Fusion实战:从零搭建无人机视觉惯性导航系统(附ROS配置指南)

本文提供了一份详尽的VINS-Fusion实战指南,涵盖从硬件选型、传感器标定到ROS节点配置与参数调优的全过程。重点解析了如何将VINS-Fusion视觉惯性里程计系统与PX4飞控深度集成,并实现室外GPS融合,旨在帮助开发者从零搭建稳定、高精度的无人机视觉惯性导航系统。

lll78的博客 863

spring cloud 日志配置

Spring Cloud日志配置可以使用Log4j或者logback,但是官方推荐使用logback。logback是log4j作者出的,用来替代log4j的。我们也使用logback来处理日志 在spring boot项目里面 需要配置logback是非常简单的。 第一步确认依赖包...

chicha2708的博客 3075

从零开始:小鼠Bulk RNA-seq全流程实战指南

本文提供了一份详尽的小鼠Bulk RNA-seq全流程实战指南,涵盖从软件环境搭建、原始数据质控与清洗、序列比对、基因定量到下游差异表达分析与功能富集。内容面向生物信息学新手,通过清晰的代码示例和步骤解析,帮助读者系统掌握Bulk RNA-seq数据分析的核心技能,并产出可靠的生物学洞见。

lake5的博客 712

规范化的日志书写、grep 排查容器日志完整指南

本文介绍了Spring Boot/Spring Cloud应用开发中的日志规范实践。主要内容包括:1)推荐使用SLF4J+Logback日志框架组合;2)规范日志级别使用场景(ERROR必须带堆栈,生产环境禁用DEBUG);3)建议结构化日志格式,包含traceId、业务ID等关键字段;4)MDC上下文传递最佳实践及线程安全注意事项;5)日志内容书写准则(使用占位符、脱敏处理等);6)提供Logback配置示例,推荐JSON格式输出;7)与Spring Cloud Sleuth集成实现链路追踪;8)强调安全

python15397的博客 1209

NDVI空间分布变化Slope趋势分析与F检验(自用)

1.NDVI数据预处理2.一元线性回归分析3.Slope趋势分析与F检验

什么都学亿点点的博客 9017

PID详解

1、什么是PID及其作用 设定一个输出目标,反馈系统传回输出值,如与目标不一致,则存在一个误差,PID 根据此误差调整输入值,直至输出达到设定值. 为什么需要PID呢,比如控制温度,我们需要设置一个要达到的目标温度值,当将温度稳定在目标温度值 例如,我们希望水的目标温度值为70°,其希望无非就是可以快速而且无抖动的达到70°,并稳定在70° 如果我们使用普通的方式加热,当温度到达70°后就以及停止加热,岂不是简单快速,当然用在要求不高的场合也可以,但是如果要求更高的场合呢,会出现温度到达70°后......

weixin_48435215的博客 5万+

Spring Boot/Spring Cloud 整合 ELK(Elasticsearch、Logstash、Kibana)详细避坑指南

Spring Boot/Spring Cloud 整合 ELK(Elasticsearch、Logstash、Kibana)详细避坑指南

qq_42552574的博客 1829

本地AI代码助手:智能解析大型代码库,实现自动化重构与理解

在软件开发中,大型代码库的理解与维护是常见挑战。传统方法依赖人工阅读和有限上下文工具,效率低下。通过结合检索增强生成(RAG)和多智能体(Multi-Agent)架构,现代AI工具能够智能解析代码结构,理解模块关系,并精准定位相关代码片段。这种技术不仅能提升代码探索效率,还能自动化执行重构、文档生成等任务,在遗留系统维护、快速上手新项目等场景中发挥关键作用。本文以blackboxaicode/cli为例,展示了如何利用其代码感知和Workflow Automation能力,高效处理复杂代码库。

weixin_30240349的博客 362

Spring boot ,Spring cloud PROPERTIES 详细说明

=================================================================== COMMON SPRING BOOT PROPERTIES # This sample file is provided as a guideline. Do NOT copy it in its entirety to your own ap...

cvvface的博客 1510

从0到1:提示工程架构师搭建Agentic AI系统的全流程实战手册

Agentic AI(自主智能体)是具备自主决策能力的大模型应用感知(Perceive):获取外部环境信息(比如代码仓库、用户输入、数据库数据);记忆(Memory):存储和检索历史信息(比如过去的审计记录、用户偏好);规划(Plan):将复杂任务拆解为可执行的步骤(比如“代码审计”拆成3步);行动(Act):调用工具或API完成具体操作(比如调用Git克隆仓库、调用Safety查漏洞);反馈(Feedback):根据结果调整策略(比如用户说“报告不够详细”,下次增加逻辑漏洞的分析深度)。

AI Agent Harness Engineering 1039

C#上位机+TensorFlow Lite 轻量化AI模型部署实战(工业级完整版,低算力工控机首选)

工业自动化轻量化AI部署指南:C# + TensorFlow Lite实战 摘要:本文针对工业自动化领域机器视觉部署的痛点,提出基于C#和TensorFlow Lite(TFLite)的轻量化解决方案。该方案完美适配低算力工控环境(J1900/2G内存),具有以下核心优势:1)极致轻量化,模型体积仅KB-MB级;2)CPU深度优化,单帧推理≤15ms;3)零环境依赖,纯C#部署;4)工业级稳定性,7×24小时运行。教程涵盖从环境搭建、模型转换到C#封装的完整流程,特别提供YOLOv8等工业常用模型的TFLi

这里只有干货:从 Modbus 通信到 MES 对接,从 YOLO 训练到工控机部署,带你搞定工业软件开发全流程。 1030

Spring/Spring MVC、Spring Boot/Spring Cloud

一、 为什么要使用 spring? spring 提供 ioc 技术,容器会帮你管理依赖的对象,从而不需要自己创建和管理依赖对象了,更轻松的实现了程序的解耦。 spring 提供了事务支持,使得事务操作变的更加方便。 spring 提供了面向切片编程,这样可以更方便的处理某一类的问题。 更方便的框架集成,spring 可以很方便的集成其他框架,比如 MyBatis、hibernate 等。 二、解释一下什么是 aop? aop 是面向切面编程,通过预编译方式和运行期动态代理实现程序功能的统一维护的一种技术。

书香水墨 2272

jdk-8u144-linux-x64.tar.gz.

该资源为搭建Linux系统Java环境用,资源的后缀为tar.gz格式的压缩包

Spring、Spring Boot 和 Spring Cloud 的区别详解

在软件架构设计中,**Spring**、**Spring Boot** 和 **Spring Cloud** 是 Java 开发人员经常使用的核心框架,它们各自有着不同的功能定位和设计理念。本文将从框架特性、应用场景、优缺点等角度深入探讨它们的区别,并通过具体的开发场景加以说明。

开源AI 专属 UI——papyrai-ui。《千界明彻录》(故事形式构建元思维)——胡说小说。更多思辨内容在公众号。 3348

jre9下载 jdk9

(Java SE Runtime Environment 9.0) 9.0u175 官方最新版 【x86|x64】

文档:亲测可用解决vnc灰屏,黑屏,鼠标大黑叉情况

主要解决vnc-viewer灰屏,黑屏,鼠标大黑叉等现象,详细见文档。

Spring Boot、Spring Cloud实现原理

Spring、Spring Boot、Spring Cloud实现原理

tszc95的博客 550

计算机控制技术及应用

《计算机控制技术及应用》通过总结作者的科研成果和吸收国内外的先进理论、方法和技术,以计算机控制系统的网络化、开放化、智能化和集成化发展趋势为主线索,主要介绍计算机控制系统的基本概念和计算机控制系统的典型形式,计算机控制系统的数学描述与基本分析方法,以及数字PID控制器、最少拍控制系统、达林算法、状态变量设计法等设计方法及其各种改进算法。重点突出计算机控制系统输入输出通道接口的设计方法和典型控制系统的设计案例与实现方法等。

【Spring/Spring Boot/Spring Cloud指南】核心技术+实操步骤(含表格对比+代码示例)

本文系统梳理了Spring生态三大核心技术框架:Spring、Spring Boot和Spring Cloud的核心定位、差异对比和学习路径。Spring作为基础框架提供IOC/AOP等核心功能;Spring Boot通过自动配置简化开发;Spring Cloud则专注于微服务架构。文章详细对比了三者在配置方式、依赖管理、部署方式等方面的差异,并提供了Spring核心功能的代码示例,包括IOC容器和依赖注入的实现。最后给出从Spring基础到微服务架构的渐进式学习建议,帮助开发者全面掌握Spring生态。

独立思考,明辨是非 1493
上一篇: PostgreSQL 字段类型选择最佳实践指南(Java 后端开发视角)
下一篇: 规范化的日志书写、grep 排查容器日志完整指南
龙茶清欢
博客等级 码龄7年 3720粉丝 375原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

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

打赏作者

龙茶清欢

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

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

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

打赏作者

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

抵扣说明:

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

余额充值