How to series How to ensure code thread safety in stand-alone and cluster environments


In multithreaded programming, thread safety is a key concept, which involves correctness and consistency when multiple threads access and modify shared resources. Whether in a stand-alone environment or a distributed cluster environment, ensuring thread safety is an issue that developers need to pay attention to.

img

What is thread safety

Thread safety is a term in programming design, which means that when a function or function library is called in a multi-threaded environment, it can correctly handle the common variables between multiple threads , so that the program function can be completed correctly .

The above is from Wikipedia

Give an example to illustrate the importance of thread safety. Suppose there is a movie theater playing a movie with a total of 10 seats. If there are no thread-safe protection measures, when multiple people rush to buy movie tickets at the same time, the remaining seats may be greater than 0, resulting in too many tickets being sold, which does not meet expectations .

Sample code :

public class MovieTicket {
    
    
    private int availableTickets;

    public MovieTicket(int totalTickets) {
    
    
        this.availableTickets = totalTickets;
    }

    public void sellTickets(int numTickets, String user) {
    
    
        if (numTickets > availableTickets) {
    
    
            System.out.println("抱歉," + user + ",剩余票数不足!");
            return;
        }

        // 模拟售票过程
        // 例如查库 写库 调用远程服务等
        try {
    
    
            Thread.sleep(100); // 假设售票过程需要一定的时间
        } catch (InterruptedException e) {
    
    
            e.printStackTrace();
        }

        availableTickets -= numTickets;
        System.out.println(user + "购买了" + numTickets + "张票,剩余票数:" + availableTickets);
    }

    public int getTicketsAvailable() {
    
    
        return availableTickets;
    }
}
  • MovieTicketThe class represents the ticketing system of a movie theater
  • sellTicket()The method is used to sell tickets, each call will reduce the number of remaining tickets, and then output the ticket information.
  • getTicketsAvailable()The method is used to get the remaining votes

Next, let's simulate 10 users buying movie tickets at the same time :

public class Test {
    
    
    public static void main(String[] args) {
    
    
        MovieTicket ticketCounter = new MovieTicket(10);
        for (int i = 0; i < 10; i++) {
    
    
            int finalI = i;
            Thread thread1 = new Thread(() -> ticketCounter.sellTickets(1, "User"+ finalI));
            thread1.start();
        }
    }
}

The execution results are as follows:

User7购买了1张票,剩余票数:3
User1购买了1张票,剩余票数:3
User8购买了1张票,剩余票数:3
User2购买了1张票,剩余票数:3
User9购买了1张票,剩余票数:3
User6购买了1张票,剩余票数:3
User4购买了1张票,剩余票数:3
User5购买了1张票,剩余票数:3
User0购买了1张票,剩余票数:3
User3购买了1张票,剩余票数:3

The expected result is as follows:

User0购买了1张票,剩余票数:9
User8购买了1张票,剩余票数:8
User7购买了1张票,剩余票数:7
User9购买了1张票,剩余票数:6
User6购买了1张票,剩余票数:5
User3购买了1张票,剩余票数:4
User5购买了1张票,剩余票数:3
User4购买了1张票,剩余票数:2
User2购买了1张票,剩余票数:1
User1购买了1张票,剩余票数:0

what will happen

When multiple threads read and write to a shared resource concurrently , data race conditions may arise, causing data to be polluted or produce indeterminate results.

A shared resource could be a counter variable , an array , a record in a database , or anything else .

Common operations are:

  • Check-then-act operation (initialization)
  • Read-modify-write operation (increment counter)

How to ensure thread safety

stand-alone environment

1. Stateless design

Design classes that are stateless, that is, classes that have no global variables or shared state. By avoiding competition for shared resources, conflicts and race conditions between threads are reduced, thereby ensuring thread safety. Here is an example:

Sample code :

public class ThreadSafeCalculator {
    
    
    // 没有任何全局变量或共享状态
    public int add(int a, int b) {
    
    
        return a + b;
    }

    public int subtract(int a, int b) {
    
    
        return a - b;
    }
    // 其他无状态的计算方法...
}

By designing the class to be stateless, each thread can create its own instance or share the same instance and call methods independently to perform computations. Since there is no shared state, there is no race or conflict between threads, thus ensuring thread safety.

It should be noted that stateless design is not suitable for all scenarios. In some cases, shared state or global variables may indeed be required to implement specific functionality. In this case, appropriate synchronization mechanisms (such as using locks) need to be adopted to ensure the safety of multi-threaded access to shared resources.

2. Use the final keyword (immutable)

final variable is also thread safe in java because once some reference of one object is assigned, it cannot point to a reference of another object.

Code example :

public class ThreadSafeCounter {
    
    
    private final int limit = 100;
    private final Object lock = new Object();

    public void increment() {
    
    
    }

    public int getLimit() {
    
    
        return limit;
    }
}

It should be noted that finalthe keyword only guarantees that the variable reference will not be modified, but does not guarantee the immutability of the internal state of the referenced object. If the reference object itself is mutable, and multiple threads modify it, additional synchronization mechanisms are still required to ensure thread safety.

3. Use the synchronized keyword

Use the synchronized keyword to modify the access method or code block of the shared resource to ensure that only one thread can access the shared resource at the same time.

The synchronized keyword can prevent multiple threads from modifying shared resources at the same time, ensuring data consistency and correctness. When a thread acquires the lock, other threads will be blocked until the lock is released.

Sample code :

public synchronized void sellTickets(int numTickets, String user) {
    
    
    // 线程安全的代码块
    // ...
}

4. Use the volatile keyword

The volatile keyword is used to modify the shared variable to ensure that the latest value is read from the main memory every time the variable is accessed, instead of using the thread's local cache. It can ensure visibility between multiple threads, but it does not solve the problems of atomicity and ordering. Therefore, volatile is suitable for some simple variable state flags or switches .

Sample code :

private volatile boolean flag = false;

public void setFlag(boolean value) {
    
    
    flag = value;
}

public boolean getFlag() {
    
    
    return flag;
}

5. Use the atomic wrapper class in the java.util.concurrent.atomic package

Java provides a series of Atomic classes, such as AtomicInteger, AtomicLong, etc., which provide atomic operations, which can complete read and update operations in a single operation, thereby ensuring thread safety. The Atomic class uses the underlying CAS (Compare and Swap) operation to ensure atomicity and visibility.

Sample code :

private AtomicInteger availableTickets = new AtomicInteger(10);

public void sellTickets(int numTickets, String user) {
    
    
    int remainingTickets = availableTickets.getAndAdd(-numTickets);
    // 线程安全的代码块
    // ...
}

6. Use the locks in the java.util.concurrent.locks package

ReentrantLock is a reentrant lock provided by Java, which provides more flexibility and scalability. By explicitly acquiring and releasing locks, you can ensure that only one thread can access a shared resource. Compared with the synchronized keyword, ReentrantLock provides more advanced features, such as interruptible locks, fair locks, etc.

Sample code :

private ReentrantLock lock = new ReentrantLock();

public void sellTickets(int numTickets, String user) {
    
    
    lock.lock();
    try {
    
    
        // 线程安全的代码块
        // ...
    } finally {
    
    
        lock.unlock();
    }
}

7. Use thread-safe collection classes

Java provides many thread-safe data structures, such as ConcurrentHashMap, CopyOnWriteArrayList, etc. These data structures internally implement thread-safe access and modification mechanisms, and can be used directly in a multi-threaded environment without additional synchronization measures.

Sample code :

private Map<String, Integer> map = new ConcurrentHashMap<>();

public void updateMap(String key, int value) {
    
    
    map.put(key, value);
}

8. Use ThreadLocal

ThreadLocal is a thread closure mechanism provided by Java, which can provide each thread with an independent variable copy (space for time) . By storing shared variables in ThreadLocal, data sharing and competition between multiple threads can be avoided, thereby ensuring thread safety.

Sample code :

private ThreadLocal<Integer> threadLocalCount = ThreadLocal.withInitial(() -> 0);

public void incrementCount() {
    
    
    int count = threadLocalCount.get();
    threadLocalCount.set(count + 1);
}

cluster environment

In a cluster environment, more factors and challenges need to be considered to ensure thread safety. As clusters involve multiple servers and multiple processes/threads running concurrently, the maintenance of thread safety becomes more complicated. The following are some common scenarios for ensuring thread safety in a cluster environment

1. Distributed lock

  • Use distributed locks to coordinate access to shared resources among multiple nodes.
  • Common distributed lock implementations include database-based locks, cache-based locks (such as Redis locks), and ZooKeeper-based locks.
  • Before accessing shared resources, nodes need to acquire distributed locks to ensure that only one node can execute critical section code.

Example pseudocode :

// 加锁
if (acquireLock(key)) {
    
    
    try {
    
    
        // 执行操作
    } finally {
    
    
        // 释放锁
        releaseLock(key);
    }
}

reference

2. Data sharding, splitting and isolating data

  • Divide the shared data into pieces and assign each piece to a different node for processing.
  • Each node is only responsible for the data fragments allocated by itself, preventing multiple nodes from accessing the same data at the same time.
  • Appropriate data fragmentation strategies can be selected according to data characteristics and load conditions, such as hash-based, consistent hash, range, etc.

Example pseudocode :

// 获取数据分片的节点
Node node = getShardNode(key);
// 在指定节点上执行操作
result = node.processData(key, data);

3. Serialization avoids concurrency

  • Use the message queue as the middleware for data exchange, and convert the operation of shared resources into the form of asynchronous messages.
  • Each node receives messages from the message queue and processes them, ensuring that only one node processes each message.
  • Message queues can provide a reliable message delivery mechanism and ensure data consistency through the order of message consumption.
  • Avoid concurrency through some strategy and business design.
// 发送消息到消息队列
queue.send(key,message);

// 在节点上异步消费消息
queue.consume(key,message -> {
    
    
    // 处理消息
});

4. Distributed atomic operations

Redis provides some atomic commands that can implement some common distributed atomic operations in a cluster environment. Here are some commonly used Redis atomic commands and examples:

1.SETNX(Set if Not eXists)

If the specified key does not exist, sets the value of the key to the given value, the operation is atomic.

// 设置键名为 "key" 的值为 "value",仅当该键不存在时
jedis.setnx("key", "value");

2. Atomic counters

Redis' INCR and DECR commands can perform atomic operations on integer values ​​stored in Redis.

// 自增计数器
Long incrementedValue = jedis.incr("counter_key");

// 自减计数器
Long decrementedValue = jedis.decr("counter_key");

3. Transaction atomic operation combination

Redis provides a combination of MULTI/EXEC/WATCH commands, which can realize the atomic execution of multiple operations.

// 监视键
jedis.watch("key");

// 开启事务
Transaction transaction = jedis.multi();

// 执行多个操作
transaction.set("key1", "value1");
transaction.set("key2", "value2");

// 提交事务
List<Object> results = transaction.exec();

4. lua script

Redis provides Lua script support, which can be used to implement more complex atomic operations. By combining multiple Redis commands into a Lua script, when executing the script, Redis will execute the entire script as an atomic operation, ensuring that it will not be interrupted by other commands during execution.

You can use Redis Lua scripts to ensure thread safety and avoid overselling problems.

-- Lua 脚本代码
local key = KEYS[1]  -- 键名
local quantity = ARGV[1]  -- 购买数量

local remaining = tonumber(redis.call('GET', key))  -- 获取当前剩余票数

if remaining and remaining >= tonumber(quantity) then
    redis.call('DECRBY', key, quantity)  -- 减少票数
    return 1  -- 返回成功标志
else
    return 0  -- 返回失败标志
end

In this Lua script, we first get the current remaining votes for the specified key, and then make a judgment based on the purchased quantity. If the remaining votes are sufficient, use the Redis DECRBYcommand to atomically reduce the number of votes and return a success flag. Otherwise, return the failure flag directly.

In Java, we can use Redis clients such as Jedis or Lettuce to execute Lua scripts. The following is a sample code for executing Lua scripts using Jedis:

Jedis jedis = new Jedis("localhost", 6379);
String script = "local key = KEYS[1]\n" +
                "local quantity = ARGV[1]\n" +
                "local remaining = tonumber(redis.call('GET', key))\n" +
                "if remaining and remaining >= tonumber(quantity) then\n" +
                "    redis.call('DECRBY', key, quantity)\n" +
                "    return 1\n" +
                "else\n" +
                "    return 0\n" +
                "end";
String key = "ticket";
String quantity = "2";

// 执行 Lua 脚本
Long result = (Long) jedis.eval(script, Collections.singletonList(key),Collections.singletonList(quantity));

if (result == 1) {
    
    
    // 购票成功
    System.out.println("购票成功");
} else {
    
    
    // 购票失败
    System.out.println("购票失败");
}

By executing this Lua script, we can ensure thread safety in a distributed environment and avoid the problem of overselling movie tickets. When multiple threads or nodes execute the script at the same time, Redis will guarantee the atomicity of the Lua script, thus ensuring the correctness and consistency of the ticket purchase operation.

There are many kinds of atomic operations in the database, the following are a few common examples in development :

Atomic Counter: Perform atomic operations on counters in the database, usually by increasing or decreasing the value of the counter.

Example: Incrementing a views counter in a table of articles.

UPDATE articles SET view_count = view_count + 1 WHERE id = 456;

5. Atomic operation CAS + Retry/Failfast (general solution - token limit protection)

To ensure thread safety in a cluster environment, combining atomic operations and token protection is an effective solution. The scheme ensures coordination and mutual exclusion among multiple threads or nodes by using atomic operations and a token mechanism. Here is a detailed explanation and sample pseudocode for the scheme:

  1. Atomic operations: Use atomic operations provided by databases or distributed storage systems to ensure data consistency. These atomic operations include atomic addition, atomic update, atomic deletion, etc., and you can choose the appropriate atomic operation according to specific business requirements.
  2. Token Protection Mechanism: Before performing a set of unsafe operations, introduce a token fetch operation. The number of tokens is tied to a resource or operational capability in the cluster. Before each thread or node can perform an unsafe operation, it needs to obtain a token from the token pool. The process of obtaining tokens needs to be thread-safe, which can be achieved using atomic operations.
  3. Retry/Failfast: If the thread or node cannot obtain the token, that is, it cannot enter the key operation stage, you can choose to retry or give up the operation. A retry mechanism allows a thread to wait and try to acquire a token again until it succeeds. The Failfast mechanism gives up the operation immediately to avoid wasting resources.

The following is an example pseudocode that demonstrates a cluster thread-safe scheme for atomic operations and token protection:

int maxRetries = 3;
int retryInterval = 100; // milliseconds
int currentRetry = 0;
boolean success = false;

while (!success && currentRetry < maxRetries) {
    
    
        // 尝试获取令牌
        if (threadSafeAcquireToken()) {
    
    
            try {
    
    
                // 执行一组不安全的操作
                executeUnsafeOperations();
                success = true;
            } finally {
    
    
                // 释放令牌
                releaseToken();
            }
        } else {
    
    
            // 没有获取到令牌,选择重试或者放弃操作
            currentRetry++;
            handleRetryOrFail();
               // 拿不到令牌,等待一段时间后重试
            Thread.sleep(retryInterval);
        }
}

Take the example of selling movie tickets above as an example. This token can be a general token or a business token. For example, the token limit here is actually a limit of 10 people per movie, that is, 10 tokens.

Create a Lua script acquire_token.luato get the token:

local key = KEYS[1]  -- 令牌池键名
local tokenCount = tonumber(ARGV[1])  -- 需要获取的令牌数量

local currentCount = tonumber(redis.call('GET', key))  -- 获取当前令牌数量

if currentCount and currentCount >= tokenCount then
    redis.call('DECRBY', key, tokenCount)  -- 减少令牌数量
    return 1  -- 获取令牌成功
else
    return 0  -- 获取令牌失败
end

Create a Lua script release_token.luafor releasing tokens

local key = KEYS[1]  -- 令牌池键名
local tokenCount = tonumber(ARGV[1])  -- 需要释放的令牌数量

redis.call('INCRBY', key, tokenCount)  -- 增加令牌数量

Execute Lua scripts using Jedis in Java:

int maxRetries = 3;  // 最大重试次数
int retryDelayMillis = 100;  // 重试延迟时间

int retryCount = 0;
boolean acquiredToken = false;

// 获取令牌
while (!acquiredToken && retryCount < maxRetries) {
    
    
    Long acquireResult = (Long) jedis.eval(acquireScript, Collections.singletonList(电影id), Collections.singletonList(String.valueOf(tokenCount)));

    if (acquireResult == 1) {
    
    
        acquiredToken = true;
    } else {
    
    
        retryCount++;
        try {
    
    
            Thread.sleep(retryDelayMillis);
        } catch (InterruptedException e) {
    
    
        }
    }
}
// 处理业务
if (acquiredToken) {
    
    
    try {
    
    
        // 执行线程安全的操作 重点 重点 重点,这里是一大堆操作需要保证线程安全的
        // 远程调用
        // 写库
        // ...
    } finally {
    
     // 释放令牌
        jedis.eval(releaseScript, Collections.singletonList(电影id), Collections.singletonList(String.valueOf(tokenCount)));
    }
} else {
    
    
    // 重试次数超过阈值,执行其他处理逻辑或抛出异常
    // ...
    throw 
}

Summarize

Through the above sorting and analysis, we create a pseudocode based on Redis - based general token restriction protection strategy .

public class RedisTokenProtection {
    
    
    private final Jedis jedis;
    private final String tokenPoolKey;
    private final int maxRetries;
    private final long retryInterval;

    /**
     * 构造函数
     *
     * @param jedisSupplier 提供 Jedis 实例的供应商
     * @param tokenPoolKey  令牌池的键名
     * @param maxRetries    最大重试次数
     * @param retryInterval 重试间隔时间(毫秒)
     */
    public RedisTokenProtection(Supplier<Jedis> jedisSupplier, String tokenPoolKey, int maxRetries, long retryInterval) {
    
    
        this.jedis = jedisSupplier.get();
        this.tokenPoolKey = tokenPoolKey;
        this.maxRetries = maxRetries;
        this.retryInterval = retryInterval;
    }

    /**
     * 执行带有令牌保护的业务逻辑
     *
     * @param limitTokenCount   限制令牌数
     * @param requestTokenKey   请求令牌的键名
     * @param requestTokenCount 请求令牌的数量
     * @param totalTimeout      总的执行超时时间(毫秒)
     * @param supplier          提供业务逻辑的供应商
     * @param <T>               返回值的类型
     * @return 业务逻辑的返回值
     * @throws TokenAcquisitionException 令牌获取异常
     */
    public <T> T executeWithTokenProtection(int limitTokenCount, String requestTokenKey, int requestTokenCount, long totalTimeout, Supplier<T> supplier) throws TokenAcquisitionException {
    
    
        long startTime = System.currentTimeMillis();
        try {
    
    
            // 尝试获取令牌
            boolean acquiredToken = acquireToken(limitTokenCount, requestTokenKey, requestTokenCount);
            if (acquiredToken) {
    
    
                // 成功获取令牌后执行业务逻辑
                return supplier.get();
            }
            throw new TokenAcquisitionException("Failed to acquire tokens.");
        } catch (TokenAcquisitionException ex) {
    
    
            throw ex;
        } catch (Exception ex) {
    
    
            long elapsedTime = System.currentTimeMillis() - startTime;
            int retries = 0;
            while (retries < maxRetries && elapsedTime < totalTimeout) {
    
    
                try {
    
    
                    // 等待重试间隔
                    Thread.sleep(retryInterval);
                    boolean acquiredToken = acquireToken(limitTokenCount, requestTokenKey, requestTokenCount);
                    if (acquiredToken) {
    
    
                        // 成功获取令牌后执行业务逻辑
                        return supplier.get();
                    }
                    retries++;
                    elapsedTime = System.currentTimeMillis() - startTime;
                } catch (InterruptedException e) {
    
    
                    Thread.currentThread().interrupt();
                    break;
                }
            }
            throw new TokenAcquisitionException("Failed to acquire tokens after retrying " + maxRetries + " times.");
        } finally {
    
    
            releaseToken(requestTokenKey, requestTokenCount);
        }
    }

    // 获取令牌的逻辑,实现方法根据具体需求自行编写
    // 必须是原子的
    private boolean acquireToken(int limitTokenCount, String requestTokenKey, int requestTokenCount) {
    
    
         	String acquireTokenScript = 
                "local availableTokens = tonumber(redis.call('get', KEYS[1])) or 0\n" +
                "if availableTokens >= tonumber(ARGV[1]) then\n" +
                "    redis.call('decrby', KEYS[1], ARGV[1])\n" +
                "    return true\n" +
                "else\n" +
                "    return false\n" +
                "end";

            Object result = jedis.eval(acquireTokenScript, Collections.singletonList(requestTokenKey),
                Collections.singletonList(String.valueOf(requestTokenCount)));

            return (Boolean) result;
    }

    // 释放令牌的逻辑,实现方法根据具体需求自行编写
    // 必须是原子的
    private void releaseToken(String requestTokenKey, int requestTokenCount) {
    
    
     	String releaseTokenScript =
            "redis.call('incrby', KEYS[1], ARGV[1])";

        jedis.eval(releaseTokenScript, Collections.singletonList(requestTokenKey),
            Collections.singletonList(String.valueOf(requestTokenCount)));
    }

    public class TokenAcquisitionException extends Exception {
    
    
        public TokenAcquisitionException(String message) {
    
    
            super(message);
        }
    }
}

Call example :

public class Main {
    
    
    public static void main(String[] args) {
    
    
        // 创建 Jedis 实例的供应商
        Supplier<Jedis> jedisSupplier = () -> {
    
    
            // 这里创建和配置 Jedis 实例,例如连接到 Redis 服务器
            return new Jedis("localhost");
        };

        // 创建 RedisTokenProtection 实例
        RedisTokenProtection tokenProtection = new RedisTokenProtection(jedisSupplier, "token_pool:", 3, 1000);

        try {
    
    
            // 执行带有令牌保护的业务逻辑
            String movieId = "亮剑";
            boolean result = tokenProtection.executeWithTokenProtection(10, movieId, 1, 10000, () -> {
    
    
                // 这里编写需要保护的线程不安全的业务逻辑
                System.out.println("执行业务逻辑...");
                // 假设这里有一段需要保护的代码
                // ...

                // 返回业务逻辑执行的结果
                return true;
            });
            if (result) {
    
    
                System.out.println("业务逻辑执行成功!");
            } else {
    
    
                System.out.println("业务逻辑执行失败!");
            }
        } catch (RedisTokenProtection.TokenAcquisitionException ex) {
    
    
            System.out.println("获取令牌失败:" + ex.getMessage());
        }
    }
}

When using the combination of Spring AOP and custom annotations, the function of token protection can be realized more conveniently. Here is a sample code showing how to implement token protection using Spring AOP and custom annotations:

First, define a custom annotation TokenProtectedto mark the method that needs token protection:

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface TokenProtected {
    
    
    int limitTokenCount() default 1;
    String requestTokenKey();
    int requestTokenCount() default 1;
    long totalTimeout() default 0;
}

Then, create an aspect class TokenProtectionAspectto implement token protection logic using Spring AOP

@Aspect
@Component
public class TokenProtectionAspect {
    
    
    private final RedisTokenProtection tokenProtection;

    @Autowired
    public TokenProtectionAspect(RedisTokenProtection tokenProtection) {
    
    
        this.tokenProtection = tokenProtection;
    }

    @Pointcut("@annotation(com.example.TokenProtected)")
    public void tokenProtectedMethod() {
    
    
    }

    @Around("tokenProtectedMethod() && @annotation(tokenProtected)")
    public Object protectWithToken(ProceedingJoinPoint joinPoint, TokenProtected tokenProtected) throws Throwable {
    
    
        int limitTokenCount = tokenProtected.limitTokenCount();
        String requestTokenKey = tokenProtected.requestTokenKey();
        int requestTokenCount = tokenProtected.requestTokenCount();
        long totalTimeout = tokenProtected.totalTimeout();

        Supplier<Object> supplier = () -> {
    
    
            try {
    
    
                return joinPoint.proceed();
            } catch (Throwable throwable) {
    
    
                throw new RuntimeException(throwable);
            }
        };

        return tokenProtection.executeWithTokenProtection(limitTokenCount, requestTokenKey, requestTokenCount, totalTimeout, supplier);
    
    }
}

In this aspect class, we define a pointcut tokenProtectedMethod()to match the TokenProtectedannotated method. In protectWithTokenthe method, we get TokenProtectedthe parameters of the annotation, and create an RedisTokenProtectioninstance to execute the logic of token protection.

Finally, when using it, you only need to add annotations to the methods that need token protection @TokenProtected, and configure the corresponding parameters:

@Service
public class MyService {
    
    
    @TokenProtected(limitTokenCount = 100, requestTokenKey = "myTokenKey", requestTokenCount = 1, totalTimeout = 5000)
    public void protectedMethod() {
    
    
        // 令牌保护的业务逻辑
    }
}

In the above example, protectedMethodthe method is marked as requiring token protection and the relevant token parameters are provided.

Through the above steps, you can use Spring AOP and custom annotations to implement a convenient and easy-to-use token protection mechanism. The aspect class intercepts @TokenProtectedthe annotated method, and acquires and releases tokens before and after execution.

Guess you like

Origin blog.csdn.net/abu935009066/article/details/131366487