欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

基于AOP+Redis的簡易滑動窗口限流

 更新時(shí)間:2025年07月30日 10:08:16   作者:TCChzp  
本文主要介紹了基于AOP+Redis的簡易滑動窗口限流,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧

在分布式系統(tǒng)設(shè)計(jì)中,限流是保障服務(wù)穩(wěn)定性的核心技術(shù)之一?;瑒哟翱谙蘖魉惴ㄒ云?strong>精確性和平滑性優(yōu)勢,成為解決傳統(tǒng)固定窗口限流臨界突變問題的理想方案。本文將深入解析滑動窗口算法原理,并通過AOP+Redis滑動窗口限流。

固定窗口與滑動窗口對比

固定窗口限流及其缺陷

固定窗口限流將時(shí)間劃分為固定區(qū)間(如每分鐘),統(tǒng)計(jì)每個區(qū)間內(nèi)的請求數(shù)量。這種方法雖然簡單,但存在嚴(yán)重缺陷:

當(dāng)大量請求集中在兩個窗口的交界處時(shí)(如00:59:59和01:00:00),系統(tǒng)會在極短時(shí)間內(nèi)接收雙倍于閾值的請求,導(dǎo)致服務(wù)過載?;瑒哟翱谙蘖魍ㄟ^動態(tài)時(shí)間區(qū)間解決了這個問題。

核心原理:為每個請求動態(tài)定義一個以當(dāng)前時(shí)間為終點(diǎn)、向前回溯固定時(shí)長T的時(shí)間區(qū)間(滑動窗口),統(tǒng)計(jì)該區(qū)間內(nèi)的請求數(shù):

  1. 動態(tài)窗口:每個請求到達(dá)時(shí)計(jì)算[當(dāng)前時(shí)間 - T, 當(dāng)前時(shí)間]區(qū)間
  2. 實(shí)時(shí)統(tǒng)計(jì):計(jì)算該區(qū)間內(nèi)的請求數(shù)量
  3. 決策執(zhí)行:請求數(shù) < 閾值 → 允許;否則拒絕
  4. 窗口滑動:過期請求自動移出統(tǒng)計(jì)范圍

Redis實(shí)現(xiàn)方案

Redis的有序集合(ZSET)是實(shí)現(xiàn)滑動窗口限流的理想數(shù)據(jù)結(jié)構(gòu):

ZSET結(jié)合了集合(Set)和哈希(Hash)的特性:

  • 唯一成員:每個成員(member)在集合中唯一
  • 分?jǐn)?shù)排序:每個成員關(guān)聯(lián)一個分?jǐn)?shù)(score),用于排序
  • 自動排序:成員按分?jǐn)?shù)值從小到大排序

之所以是滑動窗口限流的理想選擇,關(guān)鍵在于它完美解決了滑動窗口算法的三個核心需求:

  • 時(shí)間序列的天然支持:ZSET的分?jǐn)?shù)(score)機(jī)制為時(shí)間戳提供了原生支持。當(dāng)我們將請求時(shí)間戳作為score存儲時(shí),所有請求按時(shí)間順序自動排序,形成精確的時(shí)間序列。這使得界定時(shí)間窗口邊界變得簡單——只需計(jì)算當(dāng)前時(shí)間 - 窗口大小就能得到窗口起始點(diǎn),無需額外維護(hù)時(shí)間索引。這種設(shè)計(jì)讓滑動窗口的"滑動"機(jī)制得以自然實(shí)現(xiàn)。
  • 高效的范圍操作能力:滑動窗口的核心操作是清理過期請求,ZSET的ZREMRANGEBYSCORE命令正是為此而生。它能以O(log(N)+M)的復(fù)雜度高效刪除指定時(shí)間范圍外的歷史請求。這種高效的范圍刪除能力確保了窗口滑動時(shí)的實(shí)時(shí)性能。
  • 精確的實(shí)時(shí)統(tǒng)計(jì)特性:通過ZCARDZCOUNT命令,ZSET提供原子級的精確計(jì)數(shù)能力。在滑動窗口算法中,我們需要實(shí)時(shí)統(tǒng)計(jì)當(dāng)前窗口內(nèi)的請求數(shù)并與閾值比較,這些命令能以O(shè)(1)和O(log(N))復(fù)雜度瞬間完成統(tǒng)計(jì)。這種即時(shí)反饋機(jī)制對高并發(fā)場景至關(guān)重要,確保限流決策的及時(shí)性和準(zhǔn)確性。
命令統(tǒng)計(jì)范圍時(shí)間復(fù)雜度典型使用場景
ZCARD key整個 ZSET 的總成員數(shù)O(1)清理過期數(shù)據(jù)后快速獲取當(dāng)前窗口請求總數(shù)
ZCOUNT key min max指定 score 范圍內(nèi)的成員數(shù)O(log(N))動態(tài)統(tǒng)計(jì)子窗口/特定時(shí)間段的請求量

代碼

自定義注解

首先自定義注解,定義限流維度、窗口大小、時(shí)間單位、窗口內(nèi)最大請求數(shù)量

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface SlidingWindowLimit {
    /**
     * 限流維度的 SpEL 表達(dá)式
     * 示例:
     * - 按郵箱: "#email"
     * - 按 IP: "#request.remoteAddr"
     * - 按用戶 ID + 郵箱: "#user.id + ':' + #user.email"
     */
    String keySpEL() default "#email";
    /**
     * 窗口大小
     */
    int windowSize() default 60;
    /**
     * 時(shí)間單位
     */
    TimeUnit timeUnit() default TimeUnit.SECONDS;
    /**
     * 窗口內(nèi)最大請求數(shù)
     */
    int maxRequests() default 10;

}

切面類

1. 切面配置與基礎(chǔ)結(jié)構(gòu)

首先創(chuàng)建切面類

@Aspect
@Component
@Order(Ordered.HIGHEST_PRECEDENCE + 20)
public class SlidingWindowLimitAspect {
    @Resource
    private RedissonClient redissonClient;
    private static final Logger log = LoggerFactory.getLogger(SlidingWindowLimitAspect.class);
    private static final String RATE_LIMIT_PREFIX = "rate_limit";
}
  • @Aspect:聲明該類為AOP切面
  • @Order:設(shè)置切面執(zhí)行優(yōu)先級(數(shù)字越小優(yōu)先級越高)
  • redissonClient:Redis客戶端操作接口
  • RATE_LIMIT_PREFIX:Redis鍵名前綴,用于區(qū)分限流數(shù)據(jù)

2. 切面入口方法 - around()

@Around("@annotation(slidingWindowLimit)")
public Object around(ProceedingJoinPoint joinPoint, SlidingWindowLimit slidingWindowLimit) throws Throwable {
    //獲取方法簽名
    MethodSignature signature = (MethodSignature) joinPoint.getSignature();
    Method method = signature.getMethod();
    String methodName = method.getName();
    //判斷是否為Http請求
    ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
    if(attributes == null){
        log.warn("方法 {} 不在 Web 請求上下文中,跳過限流檢查。", methodName);
        return joinPoint.proceed();
    }
    try {
        Object parseSpEL = parseSpEL(joinPoint, signature, slidingWindowLimit.keySpEL());
        String rateLimitKey = buildRateLimitKey(parseSpEL, slidingWindowLimit);
        if(isRateLimited(rateLimitKey, slidingWindowLimit.windowSize(), slidingWindowLimit.timeUnit(), slidingWindowLimit.maxRequests())){
            throw new RateLimitExceededException("Rate limit exceeded");
        }
        return joinPoint.proceed();
    }catch (RateLimitExceededException e){
        log.warn("方法 {} 觸發(fā)了限流,已拒絕訪問。", methodName);
        throw e;
    }catch (Exception e){
        log.error("方法 {} 觸發(fā)了異常,已拒絕訪問。", methodName, e);
        throw e;
    }
}

執(zhí)行過程:

3. SpEL解析方法 - parseSpEL()

private Object parseSpEL(ProceedingJoinPoint joinPoint,MethodSignature signature, String keySpEL){
    StandardEvaluationContext context = new StandardEvaluationContext();

    Object[] args = joinPoint.getArgs();
    String[] parameterNames = signature.getParameterNames();

    for (int i = 0; i < args.length; i++) {
        if(parameterNames!=null && i< parameterNames.length){
            context.setVariable(parameterNames[i], args[i]);
        }else {
            context.setVariable("arg" + i, args[i]);
        }
    }

    ExpressionParser parser = new SpelExpressionParser();
    return parser.parseExpression(keySpEL).getValue(context);
}

功能說明

  • 動態(tài)解析注解中的SpEL表達(dá)式(如#email
  • 將方法參數(shù)注入表達(dá)式上下文
  • 支持靈活的限流鍵生成策略

4.限流鍵構(gòu)建方法 - buildRateLimitKey()

private String buildRateLimitKey(Object keyValue, SlidingWindowLimit slidingWindowLimit){
    if(keyValue == null){
        throw new IllegalArgumentException("限流參數(shù)不能為空");
    }
    return String.format("%s:%s:%s",RATE_LIMIT_PREFIX,slidingWindowLimit.keySpEL(), keyValue);
}

鍵格式說明

rate_limit:SpEL表達(dá)式:參數(shù)值
↓          ↓         ↓
rate_limit:#email:user@example.com

5. 限流核心邏輯 - isRateLimited()

private boolean isRateLimited(String key, int windowSize, TimeUnit timeUnit, int maxRequests){
    //獲取當(dāng)前時(shí)間數(shù)
    long currentTime = System.currentTimeMillis();
    long windowStartTime = currentTime - convertToMillis(windowSize, timeUnit);
    // 獲取 Redisson 的 ZSet 操作對象
    RScoredSortedSet<Long> scoredSortedSet = redissonClient.getScoredSortedSet(key);

    // 1. 刪除窗口外的過期請求
    scoredSortedSet.removeRangeByScore(0, true, windowStartTime, true); // [0, windowStartTime]

    // 2. 添加當(dāng)前請求的時(shí)間戳到 ZSet
    scoredSortedSet.add(currentTime, currentTime); // score 和 value 均為時(shí)間戳

    // 3. 統(tǒng)計(jì)窗口內(nèi)請求數(shù)量
    int count = scoredSortedSet.size();
    return count > maxRequests;
}

Redis操作序列

  1. ZREMRANGEBYSCORE key 0 windowStart:刪除過期請求
  2. ZADD key currentTime currentTime:添加當(dāng)前請求
  3. ZCARD key:獲取當(dāng)前請求數(shù)

6. 時(shí)間單位轉(zhuǎn)換 - convertToMillis()

private long convertToMillis(int windowSize, TimeUnit timeUnit) {
    return switch (timeUnit) {
        case SECONDS -> timeUnit.toMillis(windowSize);
        case MINUTES -> timeUnit.toMillis(windowSize);
        case HOURS -> timeUnit.toMillis(windowSize);
        case DAYS -> timeUnit.toMillis(windowSize);
        case MILLISECONDS -> windowSize;
        default -> throw new IllegalArgumentException("不支持的時(shí)間單位: " + timeUnit);
    };
}

完整代碼

@Aspect
@Component
@Order(Ordered.HIGHEST_PRECEDENCE + 20)
public class SlidingWindowLimitAspect {
    @Resource
    private RedissonClient redissonClient;
    private static final Logger log = LoggerFactory.getLogger(SlidingWindowLimitAspect.class);

    private static final String RATE_LIMIT_PREFIX = "rate_limit";

    @Around("@annotation(slidingWindowLimit)")
    public Object around(ProceedingJoinPoint joinPoint, SlidingWindowLimit slidingWindowLimit) throws Throwable {
        //獲取方法簽名
        MethodSignature signature = (MethodSignature) joinPoint.getSignature();
        Method method = signature.getMethod();
        String methodName = method.getName();
        //判斷是否為Http請求
        ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        if(attributes == null){
            log.warn("方法 {} 不在 Web 請求上下文中,跳過限流檢查。", methodName);
            return joinPoint.proceed();
        }
        try {
            Object parseSpEL = parseSpEL(joinPoint, signature, slidingWindowLimit.keySpEL());
            String rateLimitKey = buildRateLimitKey(parseSpEL, slidingWindowLimit);
            if(isRateLimited(rateLimitKey, slidingWindowLimit.windowSize(), slidingWindowLimit.timeUnit(), slidingWindowLimit.maxRequests())){
                throw new RateLimitExceededException("Rate limit exceeded");
            }
            return joinPoint.proceed();
        }catch (RateLimitExceededException e){
            log.warn("方法 {} 觸發(fā)了限流,已拒絕訪問。", methodName);
            throw e;
        }catch (Exception e){
            log.error("方法 {} 觸發(fā)了異常,已拒絕訪問。", methodName, e);
            throw e;
        }
    }

    private Object parseSpEL(ProceedingJoinPoint joinPoint,MethodSignature signature, String keySpEL){
        StandardEvaluationContext context = new StandardEvaluationContext();

        Object[] args = joinPoint.getArgs();
        String[] parameterNames = signature.getParameterNames();

        for (int i = 0; i < args.length; i++) {
            if(parameterNames!=null && i< parameterNames.length){
                context.setVariable(parameterNames[i], args[i]);
            }else {
                context.setVariable("arg" + i, args[i]);
            }
        }

        ExpressionParser parser = new SpelExpressionParser();
        return parser.parseExpression(keySpEL).getValue(context);
    }

    private String buildRateLimitKey(Object keyValue, SlidingWindowLimit slidingWindowLimit){
        if(keyValue == null){
            throw new IllegalArgumentException("限流參數(shù)不能為空");
        }
        return String.format("%s:%s:%s",RATE_LIMIT_PREFIX,slidingWindowLimit.keySpEL(), keyValue);
    }

    private boolean isRateLimited(String key, int windowSize, TimeUnit timeUnit, int maxRequests){
        //獲取當(dāng)前時(shí)間數(shù)
        long currentTime = System.currentTimeMillis();
        long windowStartTime = currentTime - convertToMillis(windowSize, timeUnit);
        // 獲取 Redisson 的 ZSet 操作對象
        RScoredSortedSet<Long> scoredSortedSet = redissonClient.getScoredSortedSet(key);

        // 1. 刪除窗口外的過期請求
        scoredSortedSet.removeRangeByScore(0, true, windowStartTime, true); // [0, windowStartTime]

        // 2. 添加當(dāng)前請求的時(shí)間戳到 ZSet
        scoredSortedSet.add(currentTime, currentTime); // score 和 value 均為時(shí)間戳

        // 3. 統(tǒng)計(jì)窗口內(nèi)請求數(shù)量
        int count = scoredSortedSet.size();
        return count > maxRequests;
    }

    /**
     * 時(shí)間單位轉(zhuǎn)換,將時(shí)間單位轉(zhuǎn)換為毫秒數(shù)
     * @param windowSize 窗口大小
     * @param timeUnit 時(shí)間單位
     * @return
     */
    private long convertToMillis(int windowSize, TimeUnit timeUnit){
        return switch (timeUnit){
            case NANOSECONDS, SECONDS, MICROSECONDS, MINUTES, HOURS, DAYS -> timeUnit.toMillis(windowSize);
            case MILLISECONDS -> windowSize;
            default -> throw new IllegalArgumentException("不支持的時(shí)間單位: " + timeUnit);
        };
    }
}

注解使用

比如說我們現(xiàn)在定義發(fā)送驗(yàn)證碼的方法60秒內(nèi)只能發(fā)送三次

@PostMapping("/send/code")
@SlidingWindowLimit(keySpEL = "#email.email",windowSize = 60, maxRequests = 3)
public Result sendVerificationCode(@RequestBody EmailSendDTO email) {
    userService.sendVerificationCode(email.getEmail());
    return Result.success();
}

每一次訪問時(shí),Redis都會記錄下時(shí)間戳,如果第四次訪問時(shí)的時(shí)間戳與第一次訪問的時(shí)間戳之間少于60秒,則返回

{
    "code": 429,
    "message": "請求過于頻繁,請稍后再試",
    "data": null
}

優(yōu)化

private boolean isRateLimited(String key, int windowSize, TimeUnit timeUnit, int maxRequests){
    //獲取當(dāng)前時(shí)間數(shù)
    long currentTime = System.currentTimeMillis();
    long windowStartTime = currentTime - convertToMillis(windowSize, timeUnit);
    // 獲取 Redisson 的 ZSet 操作對象
    RScoredSortedSet<Long> scoredSortedSet = redissonClient.getScoredSortedSet(key);

    // 1. 刪除窗口外的過期請求
    scoredSortedSet.removeRangeByScore(0, true, windowStartTime, true); // [0, windowStartTime]

    // 2. 添加當(dāng)前請求的時(shí)間戳到 ZSet
    scoredSortedSet.add(currentTime, currentTime); // score 和 value 均為時(shí)間戳

    // 3. 統(tǒng)計(jì)窗口內(nèi)請求數(shù)量
    int count = scoredSortedSet.size();
    return count > maxRequests;
}

在這個方法中,存在幾個問題:

代碼中直接將時(shí)間戳作為ZSET的成員(member)和分?jǐn)?shù)(score),當(dāng)同一毫秒內(nèi)有多個請求時(shí),后寫入的請求會覆蓋先前的請求(ZSET成員唯一),導(dǎo)致計(jì)數(shù)不準(zhǔn)確。

當(dāng)前操作序列:

在并發(fā)場景下,多個請求可能同時(shí)通過計(jì)數(shù)檢查,導(dǎo)致實(shí)際請求量超過閾值

  • 刪除過期請求
  • 添加當(dāng)前請求
  • 獲取當(dāng)前計(jì)數(shù)

當(dāng)某個限流鍵長時(shí)間無請求時(shí),對應(yīng)的空ZSET會永久占用內(nèi)存

那么優(yōu)化時(shí),可以利用UUID作為member 這樣不會出現(xiàn)覆蓋的情況,使用Lua腳本進(jìn)行執(zhí)行避免多個請求同時(shí)通過計(jì)數(shù)檢查的情況,針對問題三可以通過設(shè)置過期時(shí)間來解決,優(yōu)化后的代碼如下:

private boolean isRateLimited(String key, int windowSize, TimeUnit timeUnit, int maxRequests) {
    // 1. 計(jì)算窗口大?。ê撩耄?
    long windowMillis = convertToMillis(windowSize, timeUnit);
    // 2. 獲取當(dāng)前時(shí)間和窗口起始時(shí)間
    long currentTime = System.currentTimeMillis();
    long windowStartTime = currentTime - windowMillis;
    // 3. 生成唯一請求ID
    String requestId = UUID.randomUUID().toString();
    // 4. 計(jì)算過期時(shí)間(秒)
    long expireSeconds = calculateExpireSeconds(windowMillis);
    // 5. Lua腳本(使用分?jǐn)?shù)范圍精確統(tǒng)計(jì))
    String luaScript =
            "redis.call('ZREMRANGEBYSCORE', KEYS[1], '-inf', ARGV[2])\n" +  // 清理過期數(shù)據(jù)
                    "local count = redis.call('ZCOUNT', KEYS[1], ARGV[2], ARGV[1])\n" +  // 精確統(tǒng)計(jì)窗口內(nèi)請求
                    "if count >= tonumber(ARGV[4]) then\n" +
                    "    return 1\n" +  // 觸發(fā)限流
                    "end\n" +
                    "redis.call('ZADD', KEYS[1], ARGV[1], ARGV[3])\n" +  // 添加當(dāng)前請求
                    "redis.call('EXPIRE', KEYS[1], ARGV[5])\n" +  // 設(shè)置過期時(shí)間
                    "return 0";  // 允許通過

    try {
        RScript script = redissonClient.getScript();
        Long result = script.eval(
                RScript.Mode.READ_WRITE,
                luaScript,
                RScript.ReturnType.INTEGER,
                Collections.singletonList(key),
                currentTime, windowStartTime, requestId, maxRequests, expireSeconds
        );
        return result != null && result == 1;
    } catch (Exception e) {
        log.error("限流服務(wù)異常,降級放行", e);
        return false; // Redis故障時(shí)允許請求
    }
}
private long calculateExpireSeconds(long windowMillis) {
    // 過期時(shí)間 = 2 * 窗口大?。耄?,向上取整
    double expireSec = (windowMillis * 2.0) / 1000;
    long result = (long) Math.ceil(expireSec);
    return Math.max(1, result); // 至少1秒
}

Lua 腳本執(zhí)行邏輯:

  • redis.call('ZREMRANGEBYSCORE', KEYS[1], '-inf', ARGV[2]):清理過期數(shù)據(jù),將 ZSET 中分?jǐn)?shù)小于等于窗口起始時(shí)間的成員刪除。
  • local count = redis.call('ZCOUNT', KEYS[1], ARGV[2], ARGV[1]):精確統(tǒng)計(jì)窗口內(nèi)請求數(shù)量,即分?jǐn)?shù)在窗口起始時(shí)間和當(dāng)前時(shí)間之間的成員數(shù)量。
  • if count >= tonumber(ARGV[4]) then return 1 end:如果統(tǒng)計(jì)的請求數(shù)量大于等于閾值,則返回 1,表示觸發(fā)限流。
  • redis.call('ZADD', KEYS[1], ARGV[1], ARGV[3]):添加當(dāng)前請求,將當(dāng)前請求的唯一 ID 作為成員,當(dāng)前時(shí)間作為分?jǐn)?shù)添加到 ZSET 中。
  • redis.call('EXPIRE', KEYS[1], ARGV[5]):設(shè)置 ZSET 的過期時(shí)間,避免長時(shí)間無請求時(shí)空 ZSET 占用內(nèi)存。
  • return 0:如果未觸發(fā)限流,則返回 0,表示允許請求通過。

我們從命令行可以看到,每一次請求后,都會在ZSET中多一條記錄,并且每次都會重置過期時(shí)間,當(dāng)觸發(fā)限流后,不再允許訪問。

127.0.0.1:6379> ZRANGE rate_limit:#email.email:6888@example.com 0 -1
1) "fbd525dd-0e1e-4abf-a578-8c1207e8f6f0"
127.0.0.1:6379> TTL rate_limit:#email.email:6888@example.com
(integer) 355
127.0.0.1:6379> ZRANGE rate_limit:#email.email:6888@example.com 0 -1
1) "fbd525dd-0e1e-4abf-a578-8c1207e8f6f0"
2) "a7c8d5c2-f4da-46ce-9f98-472ad702e1ad"
127.0.0.1:6379> TTL rate_limit:#email.email:6888@example.com
(integer) 355
127.0.0.1:6379> ZRANGE rate_limit:#email.email:6888@example.com 0 -1
1) "fbd525dd-0e1e-4abf-a578-8c1207e8f6f0"
2) "a7c8d5c2-f4da-46ce-9f98-472ad702e1ad"
3) "12ed502c-6225-45bd-b85e-1d3ed9e46ef6"
127.0.0.1:6379> TTL rate_limit:#email.email:6888@example.com
(integer) 355

到此這篇關(guān)于基于AOP+Redis的簡易滑動窗口限流的文章就介紹到這了,更多相關(guān)AOP+Redis滑動窗口限流內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家! 

相關(guān)文章

  • java8 實(shí)現(xiàn)map以value值排序操作

    java8 實(shí)現(xiàn)map以value值排序操作

    這篇文章主要介紹了java8 實(shí)現(xiàn)map以value值排序操作,具有很好的參考價(jià)值,希望對大家有所幫助。一起跟隨小編過來看看吧
    2020-12-12
  • Java項(xiàng)目中如何訪問WEB-INF下jsp頁面

    Java項(xiàng)目中如何訪問WEB-INF下jsp頁面

    這篇文章主要介紹了Java項(xiàng)目中如何訪問WEB-INF下jsp頁面,文章通過示例代碼和圖文解析介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2020-08-08
  • java(jdk)環(huán)境變量配置(XP、win7、win8)圖文教程詳解

    java(jdk)環(huán)境變量配置(XP、win7、win8)圖文教程詳解

    對于初學(xué)java的同學(xué)來說,第一件事不是寫hello world,而是搭建好java開發(fā)環(huán)境,下載jdk,安裝,配置環(huán)境變量。這些操作在xp、win7、win8不同的操作系統(tǒng)里面配置不太一樣,下面通過本文給大家介紹如何在上面不同操作系統(tǒng)下配置
    2017-03-03
  • QQ好友列表樹形列表java代碼實(shí)現(xiàn)代碼

    QQ好友列表樹形列表java代碼實(shí)現(xiàn)代碼

    這篇文章主要為大家詳細(xì)介紹了QQ好友列表樹形列表簡單實(shí)現(xiàn)方式,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2016-08-08
  • Java中文件讀寫操作與常用技巧分享

    Java中文件讀寫操作與常用技巧分享

    在 Java I/O 體系中,F(xiàn)ile 類是唯一代表磁盤文件本身的對象,而File 類定義了一些與平臺無關(guān)的方法來操作文件,所以下面我們一起來看看 File 類有哪些操作方法吧
    2023-06-06
  • Spring Boot Jar 包部署腳本的實(shí)例講解

    Spring Boot Jar 包部署腳本的實(shí)例講解

    在本篇文章里小編給大家整理的是一篇關(guān)于Spring Boot Jar 包部署腳本的實(shí)例講解內(nèi)容,對此有興趣的朋友們可以跟著學(xué)習(xí)下。
    2021-12-12
  • Java日志組件間關(guān)系詳解

    Java日志組件間關(guān)系詳解

    在本文里我們給大家整理了關(guān)于Java日志組件間關(guān)系相關(guān)基礎(chǔ)知識,需要的朋友們跟著學(xué)習(xí)下。
    2019-02-02
  • 使用Java WebSocket獲取客戶端IP地址的示例代碼

    使用Java WebSocket獲取客戶端IP地址的示例代碼

    在開發(fā)Web應(yīng)用程序時(shí),我們通常需要獲取客戶端的 IP 地址用于日志記錄、身份驗(yàn)證、限制訪問等操作,本文將介紹如何使用Java WebSocket API獲取客戶端IP地址,以及如何在常見的WebSocket框架中獲得客戶端 IP地址,需要的朋友可以參考下
    2023-11-11
  • Java并發(fā)編程之synchronized底層實(shí)現(xiàn)原理分析

    Java并發(fā)編程之synchronized底層實(shí)現(xiàn)原理分析

    這篇文章主要介紹了Java并發(fā)編程之synchronized底層實(shí)現(xiàn)原理,具有很好的參考價(jià)值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教
    2024-02-02
  • 如何使用Spring+redis實(shí)現(xiàn)對session的分布式管理

    如何使用Spring+redis實(shí)現(xiàn)對session的分布式管理

    本篇文章主要介紹了如何使用Spring+redis實(shí)現(xiàn)對session的分布式管理,本文主要是在Spring中實(shí)現(xiàn)分布式session,采用redis對session進(jìn)行持久化管理,感興趣的小伙伴們可以參考一下
    2018-06-06

最新評論