springboot自定义注解RateLimiter限流注解技术文档详解

本文主要是介绍springboot自定义注解RateLimiter限流注解技术文档详解,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

《springboot自定义注解RateLimiter限流注解技术文档详解》文章介绍了限流技术的概念、作用及实现方式,通过SpringAOP拦截方法、缓存存储计数器,结合注解、枚举、异常类等核心组件,...

什么是限流

限流是一种控制系统访问频率的技术手段,就像高速公路的收费站控制车流量一样。

生活场景类比:

  • 银行ATM机:每张卡每天最多取款5次
  • 手机验证码:每个手机号每分钟最多发送1条
  • 网站登录:每个IP每分钟最多尝试5次

技术价值:

  1. 防止恶意攻击:阻止暴力破解、恶意爬虫
  2. 保护系统稳定:避免瞬间大量请求压垮服务器
  3. 提升用户体验:确保正常用户的访问质量
  4. 节约成本:减少不必要的资源消耗

系统架构

┌─────────────────┐    ┌─────────────────┐    ┌─────────────────┐
│   用户请求      │───→│   限流切面      │───→│   业务接口      │
│   (HTTP API)   │    │  (AOP拦截)     │    │  (Controller)   │
└─────────────────┘    └─────────────────┘    └─────────────────┘
                              │
                              ▼
                    ┌─────────────────┐
                    │   限流服务      │
                    │ (核心逻辑处理)  │
                    └─────────────────┘
                              │
                              ▼
                    ┌─────────────────┐
                    │   缓存存储      │
                    │ (EhCache/Redis) │
                    └─────────────────┘

工作流程:

  1. 用户发起HTTP请求
  2. Spring AOP切面拦截带有@RateLimiter注解的方法
  3. 限流服务根据注解配置生成限流键
  4. 从缓存中获取当前访问次数
  5. 判断是否超过限制,决定放行或拒绝
  6. 更新缓存中的计数器

核心组件详解

1. 限流注解 (@RateLimiter)

这是系统的核心注解,定义了限流的各种参数:

package cn.jbolt.config.anno.rateLimiter;

import org.springframework.core.annotation.AliasFor;
import Java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE, ElementType.METHOD})
public @interface RateLimiter {

    /**
     * 缓存前缀 - 用于区分不同业务的限流数据
     */
    String prefix() default "jblimit:";

    /**
     * 时间窗口(秒) - 限流的时间范围
     */
    int time() default 60;

    /**
     * 允许访问次数 - 时间窗口内最大访问次数
     */
    @AliasFor(attribute = "count")
    int value() default 12;

    /**
     * 限制类型 - 决定按什么维度限流
     */
    RateLimitType limitType() default RateLimitType.DEFAULT;

    /**
     * 限制提示消息 - 触发限流时返回的错误信息
     */
    String msg() default "操作过于频繁,请稍后重试";

    /**
     * 允许访问次数 - 与value互为别名
     */
    @AliasFor(attribute = "value")
    int count() default 12;

    /**
     * 自定义键 - 当limitType为CUSTOM时使用
     */
    String customKey() default "";

    /**
     * 是否启用 - 可用于动态开关限流功能
     */
    boolean enabled() default true;

    /**
     * 额外的时间窗口限制(秒)
     * 实现双重限流:比如1秒最多1次 + 1分钟最多10次
     */
    int extraTime() default -1;

    /**
     * 额外时间窗口内的允许访问次数
     */
    int extraCount() default -1;

    /**
     * 额外限制的提示消息
     */
    String extraMsg() default "";
}

2. 限流类型枚举 (RateLimitType)

package cn.jbolt.config.anno.rateLimiter;

public enum RateLimitType {
    /**
     * 默认限制(全局)
     * 所有请求共享一个计数器
     */
    DEFAULT,
    
    /**
     * 基于IP地址限制
     * 每个IP独立计数
     */
    IP,
    
    /**
     * 基于用户ID限制
     * 每个登录用户独立计数
     */
    USER,
    
    /**
     * 基于自定义KEY限制
     * 根据业务逻辑自定义限流维度
     */
    CUSTOM
}

3. 限流异常类 (RateLimitException)

package cn.jbolt.config.exception;

public class RateLimitException extends RuntimeException {
    
    private final String message;
    private final int retryAfter;
    
    public RateLimitException(String message) {
        this(message, 0);
    }
    
    public RateLimitException(String message, int retryAfter) {
        super(message);
        this.message = message;
        this.retryAfter = retryAfter;
    }
    
    @Override
    public String getMessage() {
        return message;
    }
    
    public int getRetryAfter() {
        return retryAfter;
    }
}

4. 全局异常处理器 (RateLimitExceptionHandler)

package cn.jbolt.config.handler;

import cn.jbolt.config.exception.RateLimitException;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;

import javax.servlet.http.HttpServletResponse;
import java.util.HashMap;
import java.util.Map;

@RestControllerAdvice
public class RateLimitExceptionHandler {
    
    @ExceptionHandler(RateLimitException.class)
    public ResponseEntity<Map<String, Object>> handleRateLimitException(
            RateLimitException e, HttpServletResponse response) {
        
        Map<String, Object> result = new HashMap<>();
        result.putphp("code", HttpStatus.TOO_MANY_REQUESTS.value());
        result.put("message", e.getMessage());
        result.put("data", null);
        
        // 设置HTTP响应头,告诉客户端多久后可以重试
        if (e.getRetryAfter() > 0) {
            response.setHeader("Retry-After", String.valueOf(e.getRetryAfter()));
        }
        response.setHeader("X-RateLimit-Window", "60");
   android     
        return ResponseEntity.status(HttpStatus.TOO_MANY_REQUESTS).body(result);
    }
}

5. IP工具类 (IpUtils)

package cn.jbolt.util;

import org.springframework.util.StringUtils;
import javax.servlet.http.HttpServletRequest;

public class IpUtils {
    
    private static final String[] IP_HEADER_NAMES = {
        "X-Forwarded-For",
        "X-Real-IP", 
        "Proxy-Client-IP",
        "WL-Proxy-Client-IP",
        "HTTP_CLIENT_IP",
        "HTTP_X_FORWARDED_FOR"
    };
    
    private static final String UNKNOWN = "unknown";
    private static final String LOCALHOST_IPV4 = "127.0.0.1";
    private static final String LOCALHOST_IPV6 = "0:0:0:0:0:0:0:1";
    
    /**
     * 获取客户端真实IP地址
     * 处理代理服务器、负载均衡器等场景
     */
    public static String getClientIp(HttpServletRequest request) {
        if (request == null) {
            return UNKNOWN;
        }
        
        String ip = null;
        
        // 依次检查各种可能的IP头
        for (String header : IP_HEADER_NAMES) {
            ip = request.getHeader(header);
            if (isValidIp(ip)) {
                break;
            }
        }
        
        // 如果头信息中没有找到,则使用getRemoteAddr
        if (!isValidIp(ip)) {
            ip = request.getRemoteAddr();
            if (LOCALHOST_IPV6.equals(ip)) {
                ip = LOCALHOST_IPV4;
            }
        }
        
        // 处理多个IP的情况(X-Forwarded-For可能包含多个IP)
        if (StringUtils.hasText(ip) && ip.contains(",")) {
            ip = ip.split(",")[0].trim();
        }
        
        return StringUtils.hasText(ip) ? ip : UNKNOWN;
    }
    
    /**
     * 检查IP是否有效
     */
    private static boolean isValidIp(String ip) {
        return StringUtils.hasText(ip) && !UNKNOWN.equalsIgnoreCase(ip);
    }
}

技术实现原理

1. AOP切面拦截

系统使用Spring AOP在方法执行前进行拦截,这是一个核心的限流切面类:

package cn.jbolt.config.ASPect;

import cn.jbolt.config.anno.rateLimiter.RateLimiter;
import cn.jbolt.config.anno.rateLimiter.RateLimitType;
import cn.jbolt.config.exception.RateLimitException;
import cn.jbolt.util.IpUtils;
import cn.jbolt.util.cache.RateLimiterCache;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import javax.servlet.http.HttpServletRequest;
import java.util.concurrent.TimeUnit;

@Aspect
@Component
public class RateLimiterAspect {
    
    private static final Logger logger = LoggerFactory.getLogger(RateLimiterAspect.class);
    
    @Around("@annotation(rateLimiter)")
    public Object around(ProceedingJoinPoint point, RateLimiter rateLimiter) throws Throwable {
        
        // 检查是否启用限流
        if (!rateLimiter.enabled(http://www.chinasem.cn)) {
            return point.proceed();
        }
        
        // 获取HTTP请求对象
        HttpServletRequest request = getCurrentRequest();
        if (request == null) {
            logger.warn("无法获取HttpServletRequest,跳过限流检查");
            return point.proceed();
        }
        
        // 生成限流键
        String limitKey = generateLimitKey(point, rateLimiter, request);
        
        // 执行主要限流检查
        checkRateLimit(limitKey, rateLimiter.time(), rateLimiter.count(), rateLimiter.msg());
        
        // 执行额外限流检查(如果配置了)
        if (rateLimiter.extraTime() > 0 && rateLimiter.extraCount() > 0) {
            String extraLimitKey = limitKey + ":extra";
            String extraMsg = rateLimiter.extraMsg().isEmpty() ? rateLimiter.msg() : rateLimiter.extraMsg();
            checkRateLimit(extraLimitKey, rateLimiter.extraTime(), rateLimiter.extraCount(), extraMsg);
        }
        
        // 所有限流检查通过,继续执行业务方法
        return point.proceed();
    }
    
    /**
     * 执行限流检查
     */
    private void checkRateLimit(String key, int timeWindow, int maxCount, String message) {
        try {
            // 增加计数器并获取当前访问次数
            int currentCount = RateLimiterCache.incrementAndGet(key, timeWindow, TimeUnit.SECONDS);
            
            logger.debug("限流检查: key={}, 当前次数={}, 限制次数={}", key, currentCount, maxCount);
            
            // 检查是否超过限制
            if (currentCount > maxCount) {
                long ttl = RateLimiterCache.getTtl(key);
                logger.warn("触发限流: key={}, 当前次数={}, 限制次数={}, 剩余时间={}秒", 
                          key, currentCount, maxCount, ttl);
                throw new RateLimitException(message, (int) ttl);
            }
            
        } catch (RateLimitException e) {
            throw e;
        } catch (Exception e) {
            logger.error("限流检查异常: key={}", key, e);
            // 限流服务异常时,选择放行而不是阻塞
        }
    }
    
    /**
     * 生成限流键
     */
    private String generateLimitKey(ProceedingJoinPoint point, RateLimiter rateLimiter, HttpServletRequest request) {
        StringBuilder keyBuilder = new StringBuilder();
        keyBuilder.append(rateLimiter.prefix());
        
        // 添加方法签名
        String methodSignature = point.getSignature().toShortString();
        keyBuilder.append(methodSignature);
        
        // 根据限流类型添加不同的标识
        switch (rateLimiter.limitType()) {
            case IP:
                keyBuilder.append(":ip:").append(IpUtils.getClientIp(request));
                break;
            case USER:
                String userId = getCurrentUserId(request);
                keyBuilder.append(":user:").append(userId != null ? userId : "anonymous");
                break;
            case CUSTOM:
                keyBuilder.append(":custom:").append(rateLimiter.customKey());
                break;
            case DEFAULT:
            default:
                keyBuilder.append(":default:global");
                break;
        }
        
        // 添加时间窗口,确保不同时间窗口的限流独立
        keyBuilder.append(":").append(rateLimiter.time());
        
        String finalKey = keyBuilder.toString();
        logger.debug("生成限流键: {}", finalKey);
        return finalKey;
    }
    
    /**
     * 获取当前HTTP请求
     */
    private HttpServletRequest getCurrentRequest() {
        try {
            ServletRequestAttributes attrs = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
            return attrs != null ? attrs.getRequest() : null;
        } catch (Exception e) {
            logger.warn("获取HttpServletRequest失败", e);
            return null;
        }
    }
    
    /**
     * 获取当前用户ID
     * 这里需要根据实际的用户认证体系来实现
     */
    private String getCurrentUserId(HttpServletRequest request) {
        // 方案1:从Session中获取
        Object userId = request.getSession().getAttribute("userId");
        if (userId != null) {
            return userId.toString();
        }
        
        // 方案2:从JWT Token中获取
        String token = request.getHeader("Authorization");
        if (token != null && token.startsWith("Bearer ")) {
            // 解析JWT获取用户ID
            // return JwtUtils.getUserIdFromToken(token);
        }
        
        // 方案3:从请求参数中获取
        String userIdParam = request.getParameter("userId");
        if (userIdParam != null) {
            return userIdParam;
        }
        
        return null;
    }
}

2. 缓存数据结构

系统使用一个包装类来存储缓存数据:

package cn.jbolt.util.cache;

import java.io.Serializable;
import java.util.concurrent.TimeUnit;

public class CacheWrapper implements Serializable {
    
    private static final long serialVersionUID = 1L;
    
    private Object value;
    private long timestamp;
    private long durationMillis;
    
    public CacheWrapper() {
    }
    
    public CacheWrapper(Object value, long duration, TimeUnit unit) {
        this.value = value;
        this.timestamp = System.currentTimeMillis();
        this.durationMillis = unit.toMillis(duration);
    }
    
    /**
     * 检查是否已过期
     */
    public boolean isExpired() {
        return System.currentTimeMillis() - timestamp > durationMillis;
    }
    
    /**
     * 获取剩余过期时间(毫秒)
     */
    public long getRemainingTime() {
        long elapsed = System.currentTimeMillis() - timestamp;
        return Math.max(0, durationMillis - elapsed);
    }
    
    // getter和setter方法
    public Object getValue() {
        return value;
    }
    
    public void setValue(Object value) {
        this.value = value;
    }
    
    public long getTimestamp() {
        return timestamp;
    }
    
    public void setTimestamp(long timestamp) {
        this.timestamp = timestamp;
    }
    
    public long getDurationMillis() {
        return durationMillis;
    }
    
    public void setDurationMillis(long durationMillis) {
        this.durationMillis = durationMillis;
    }
}

完整代码示例

1. 控制器示例

package cn.jbolt.controller;

import cn.jbolt.config.anno.rateLimiter.RateLimiter;
import cn.jbolt.config.anno.rateLimiter.RateLimitType;
import org.springframework.web.bind.annotation.*;

@RestController
@RequestMapping("/api")
public class DemoController {
    
    /**
     * 登录接口 - 防止暴力破解
     * 每个IP每分钟最多尝试5次
     */
    @PostMapping("/login")
    @RateLimiter(
        limitType = RateLimitType.IP,
        time = 60, 
        count = 5,
        msg = "登录尝试过于频繁,请1分钟后重试"
    )
    public Result login(@RequestBody LoginRequest request) {
        // 登录逻辑
        if (isValidUser(request.getUsername(), request.getPassword())) {
            return Result.success("登录成功");
        } else {
            return Result.error("用户名或密码错误");
        }
    }
    
    /**
     * 发送验证码 - 防止恶意发送
     * 每个IP每分钟最多3次
     */
    @PostMapping("/sms/send")
    @RateLimiter(
        limitType = RateLimitType.IP,
        time = 60, 
        count = 3,
        msg = "验证码发送过于频繁,请稍后重试"
    )
    public Result sendSms(@RequestBody SmsRequest request) {
        // 发送短信逻辑
        boolean success = smsService.sendCode(request.getPhone());
        return success ? Result.success("发送成功") : Result.error("发送失败");
    }
    
    /**
     * 查询接口 - 防止爬虫
     * 每个IP每分钟最多100次
     */
    @GetMapping("/products")
    @RateLimiter(
        limitType = RateLimitType.IP,
        time = 60, 
        count = 100,
        msg = "查询过于频繁,请稍后重试"
    )
    public Result getProducts(@RequestParam(defaultValue = "1") int page) {
        // 查询商品逻辑
        List<Product> products = productService.getProducts(page);
        return Result.success(products);
    }
   js 
    /**
     * 用户操作 - 防止频繁操作
     * 每个用户每分钟最多30次
     */
    @PostMapping("/user/update")
    @RateLimiter(
        limitType = RateLimitType.USER,
        time = 60, 
        count = 30,
        msg = "操作过于频繁,请稍后重试"
    )
    public Result updateUser(@RequestBody UserUpdateRequest request) {
        // 更新用户信息逻辑
        boolean success = userService.updateUser(request);
        return success ? Result.success("更新成功") : Result.error("更新失败");
    }
    
    /**
     * 关键操作 - 严格限流
     * 1秒最多1次 + 1分钟最多5次
     */
    @PostMapping("/transfer")
    @RateLimiter(
        limitType = RateLimitType.USER,
        time = 1, count = 1, msg = "操作过于频繁,请稍后再试",
        extraTime = 60, extraCount = 5, extraMsg = "您在1分钟内的操作次数已达上限"
    )
    public Result transfer(@RequestBody TransferRequest request) {
        // 转账逻辑
        boolean success = transferService.transfer(request);
        return success ? Result.success("转账成功") : Result.error("转账失败");
    }
    
    /**
     * 自定义限流 - 按商品限制
     * 每个商品每分钟最多下单20次
     */
    @PostMapping("/order/{productId}")
    @RateLimiter(
        limitType = RateLimitType.CUSTOM,
        customKey = "product_order",
        time = 60, 
        count = 20,
        msg = "该商品下单过于频繁,请稍后重试"
    )
    public Result createOrder(@PathVariable String productId, @RequestBody OrderRequest request) {
        // 创建订单逻辑
        Order order = orderService.createOrder(productId, request);
        return Result.success(order);
    }
    
    // 辅助方法
    private boolean isValidUser(String username, String password) {
        // 实际的用户验证逻辑
        return "admin".equals(username) && "123456".equals(password);
    }
}

2. 统一返回对象

package cn.jbolt.common;

public class Result {
    private int code;
    private String message;
    private Object data;
    
    public static Result success(Object data) {
        Result result = new Result();
        result.code = 200;
        result.message = "success";
        result.data = data;
        return result;
    }
    
    public static Result error(String message) {
        Result result = new Result();
        result.code = 500;
        result.message = message;
        result.data = null;
        return result;
    }
    
    // getter和setter方法
    public int getCode() {
        return code;
    }
    
    public void setCode(int code) {
        this.code = code;
    }
    
    public String getMessage() {
        return message;
    }
    
    public void setMessage(String message) {
        this.message = message;
    }
    
    public Object getData() {
        return data;
    }
    
    public void setData(Object data) {
        this.data = data;
    }
}

使用指南

1. 基本使用

// 最简单的用法 - 使用默认配置
@RateLimiter(limitType = RateLimitType.IP)
public String simpleApi() {
    return "success";
}

// 自定义时间窗口和次数
@RateLimiter(
    limitType = RateLimitType.IP,
    time = 60,    // 60秒
    count = 100   // 最多100次
)
public String customApi() {
    return "success";
}

2. 不同场景的配置建议

// 登录接口 - 严格限制
@RateLimiter(
    limitType = RateLimitType.IP,
    time = 60, count = 5,
    msg = "登录尝试过于频繁,请1分钟后重试"
)

// 查询接口 - 适中限制
@RateLimiter(
    limitType = RateLimitType.IP,
    time = 60, count = 100,
    msg = "查询过于频繁,请稍后重试"
)

// 用户操作 - 按用户限制
@RateLimiter(
    limitType = RateLimitType.USER,
    time = 60, count = 30,
    msg = "操作过于频繁,请稍后重试"
)

// 全局保护 - 系统级限制
@RateLimiter(
    limitType = RateLimitType.DEFAULT,
    time = 60, count = 200,
    msg = "系统繁忙,请稍后重试"
)

3. 双重限流配置

// 严格的双重限流:秒级 + 分钟级
@RateLimiter(
    limitType = RateLimitType.IP,
    time = 1, count = 1, msg = "请求过于频繁,请稍后再试",
    extraTime = 60, extraCount = 10, extraMsg = "您在1分钟内的请求次数已达上限"
)

// 适中的双重限流:分钟级 + 小时级
@RateLimiter(
    limitType = RateLimitType.IP,
    time = 60, count = 100, msg = "1分钟内请求过多",
    extraTime = 3600, extraCount = 1000, extraMsg = "1小时内请求过多"
)

总结

以上为个人经验,希望能给大家一个参考,也希望大家多多支持China编程(wwlbbhqw.chinasem.cn)。

这篇关于springboot自定义注解RateLimiter限流注解技术文档详解的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/1155495

相关文章

Java Thread中join方法使用举例详解

《JavaThread中join方法使用举例详解》JavaThread中join()方法主要是让调用改方法的thread完成run方法里面的东西后,在执行join()方法后面的代码,这篇文章主要介绍... 目录前言1.join()方法的定义和作用2.join()方法的三个重载版本3.join()方法的工作原

Spring AI使用tool Calling和MCP的示例详解

《SpringAI使用toolCalling和MCP的示例详解》SpringAI1.0.0.M6引入ToolCalling与MCP协议,提升AI与工具交互的扩展性与标准化,支持信息检索、行动执行等... 目录深入探索 Spring AI聊天接口示例Function CallingMCPSTDIOSSE结束语

Java获取当前时间String类型和Date类型方式

《Java获取当前时间String类型和Date类型方式》:本文主要介绍Java获取当前时间String类型和Date类型方式,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,... 目录Java获取当前时间String和Date类型String类型和Date类型输出结果总结Java获取

C语言进阶(预处理命令详解)

《C语言进阶(预处理命令详解)》文章讲解了宏定义规范、头文件包含方式及条件编译应用,强调带参宏需加括号避免计算错误,头文件应声明函数原型以便主函数调用,条件编译通过宏定义控制代码编译,适用于测试与模块... 目录1.宏定义1.1不带参宏1.2带参宏2.头文件的包含2.1头文件中的内容2.2工程结构3.条件编

Spring Boot Actuator应用监控与管理的详细步骤

《SpringBootActuator应用监控与管理的详细步骤》SpringBootActuator是SpringBoot的监控工具,提供健康检查、性能指标、日志管理等核心功能,支持自定义和扩展端... 目录一、 Spring Boot Actuator 概述二、 集成 Spring Boot Actuat

OpenCV在Java中的完整集成指南分享

《OpenCV在Java中的完整集成指南分享》本文详解了在Java中集成OpenCV的方法,涵盖jar包导入、dll配置、JNI路径设置及跨平台兼容性处理,提供了图像处理、特征检测、实时视频分析等应用... 目录1. OpenCV简介与应用领域1.1 OpenCV的诞生与发展1.2 OpenCV的应用领域2

在Java中使用OpenCV实践

《在Java中使用OpenCV实践》用户分享了在Java项目中集成OpenCV4.10.0的实践经验,涵盖库简介、Windows安装、依赖配置及灰度图测试,强调其在图像处理领域的多功能性,并计划后续探... 目录前言一 、OpenCV1.简介2.下载与安装3.目录说明二、在Java项目中使用三 、测试1.测

PyTorch中的词嵌入层(nn.Embedding)详解与实战应用示例

《PyTorch中的词嵌入层(nn.Embedding)详解与实战应用示例》词嵌入解决NLP维度灾难,捕捉语义关系,PyTorch的nn.Embedding模块提供灵活实现,支持参数配置、预训练及变长... 目录一、词嵌入(Word Embedding)简介为什么需要词嵌入?二、PyTorch中的nn.Em

Python Web框架Flask、Streamlit、FastAPI示例详解

《PythonWeb框架Flask、Streamlit、FastAPI示例详解》本文对比分析了Flask、Streamlit和FastAPI三大PythonWeb框架:Flask轻量灵活适合传统应用... 目录概述Flask详解Flask简介安装和基础配置核心概念路由和视图模板系统数据库集成实际示例Stre

Spring Bean初始化及@PostConstruc执行顺序示例详解

《SpringBean初始化及@PostConstruc执行顺序示例详解》本文给大家介绍SpringBean初始化及@PostConstruc执行顺序,本文通过实例代码给大家介绍的非常详细,对大家的... 目录1. Bean初始化执行顺序2. 成员变量初始化顺序2.1 普通Java类(非Spring环境)(