Springboot-Starter automatic lock component for making wheels (lock-starter)

foreword

Some people may have doubts, why there are better components out there, and why do you have to repeatedly build wheels? It can only be said that others will always belong to others. So yes. (Actually, just for volume)

In the process of daily business development, we often encounter scenarios with high concurrency. At this time, we will choose to use it redisto implement a lock to prevent concurrency.

But many times, we may need to release the lock after the business is completed for the next thread to use, but if we forget to release the lock, there may be a deadlock problem. (For those who are not very proficient in using locks, this situation often happens. Although many times, our locks have an expiration time, but if we forget to release them, there will still be a big loss during this expiration time).

Another point is that when we use redis to implement a lock, we need to import redisClient, set the key, set the expiration time, set whether to lock or not, and some repeated operations. Many of the previous steps are repetitive, so we can think of a way to abstract the repetitive things into a unified process, and at the same time provide a setting entry for the changed values.

We can also encapsulate the extracted things into a spring-boot-stater, so that we only need to write one copy and use it in different projects. Just do it, let's use redisson to complete one 自动锁的starter.

accomplish

First, let's analyze which things we need to merge and which ones need to be provided to the user. get some questions below

  • We need to combine the process of locking and releasing the lock
  • Lock key, lock time...these need to be injected into the user
  • How to generate the key of the lock (in many cases, we need to construct a key according to the business field, such as user:{userId}), so how to obtain the userId?

From the above problems that need to be solved, we think about how to achieve it. We need to encapsulate some public logic and provide some configuration storage. In this case, we can try a method, use, and 注解+AOPcomplete locking and unlocking through annotations. (Many times, if you need to extract some public methods, you will use them 注解+AOPto implement them)

Definition Annotation

AutoLock Notes

The information required for a lock includes key, lock time, time unit, whether to try to lock, lock waiting time, and so on. (If you have other business needs, you can add an extended content and parse it yourself) Then you can know what the attributes of this annotation are

/**
 * 锁的基本信息
 */
@Target({ElementType.METHOD})
@Documented
@Retention(RetentionPolicy.RUNTIME)
public @interface AutoLock {

    /**
     * 锁前缀
     */
    String prefix() default "anoxia:lock";

    /**
     * 加锁时间
     */
    long lockTime() default 30;

    /**
     * 是否尝试加锁
     */
    boolean tryLock() default true;

    /**
     * 等待时间,-1 不等待
     */
    long waitTime() default -1;

    /**
     * 锁时间类型
     */
    TimeUnit timeUnit() default TimeUnit.MILLISECONDS;

}
复制代码

LockField annotation

This annotation is added to the parameter attribute to solve the above-mentioned problem of obtaining different business parameter content to construct the key. So we need to provide which fields to get to construct this key configuration, here we need to consider two issues:

  • 1. The parameter is a basic type
  • 2. The parameter is a reference type - this type needs to get the attribute value of the object from the object
/**
 * 构建锁的业务数据
 * @author huangle
 * @date 2023/5/5 15:01
 */
@Target({ElementType.PARAMETER})
@Documented
@Retention(RetentionPolicy.RUNTIME)
public @interface LockField {

    String[] fieldNames() default {};

}
复制代码

define aspect

The key point is in this aspect, where we need to complete key synthesis, lock acquisition and release. The whole process can be divided into the following steps

  • Get the basic information of the lock and build the key
  • lock, perform business
  • The business is completed, release the lock
/**
 * 自动锁切面
 * 处理加锁解锁逻辑
 *
 * @author huangle
 * @date 2023/5/5 14:50
 */
@Aspect
@Component
public class AutoLockAspect {

    private final static Logger LOGGER = LoggerFactory.getLogger(AutoLockAspect.class);

    @Resource
    private RedissonClient redissonClient;

    private static final String REDIS_LOCK_PREFIX = "anoxiaLock";

    private static final String SEPARATOR = ":";


    /**
     * 定义切点
     */
    @Pointcut("@annotation(cn.anoxia.lock.annotation.AutoLock)")
    public void lockPoincut() {
    }


    /**
     * 定义拦截处理方式
     *
     * @return
     */
    @Around("lockPoincut()")
    public Object doLock(ProceedingJoinPoint joinPoint) throws Throwable {
        // 获取需要加锁的方法
        MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
        Method method = methodSignature.getMethod();
        // 获取锁注解
        AutoLock autoLock = method.getAnnotation(AutoLock.class);
        // 获取锁前缀
        String prefix = autoLock.prefix();
        // 获取方法参数
        Parameter[] parameters = method.getParameters();
        StringBuilder lockKeyStr = new StringBuilder(prefix);
        Object[] args = joinPoint.getArgs();
        // 遍历参数
        int index = -1;
        LockField lockField;
        // 构建key
        for (Parameter parameter : parameters) {
            Object arg = args[++index];
            lockField = parameter.getAnnotation(LockField.class);
            if (lockField == null) {
                continue;
            }
            String[] fieldNames = lockField.fieldNames();
            if (fieldNames == null || fieldNames.length == 0) {
                lockKeyStr.append(SEPARATOR).append(arg);
            } else {
                List<Object> filedValues = ReflectionUtil.getFiledValues(parameter.getType(), arg, fieldNames);
                for (Object value : filedValues) {
                    lockKeyStr.append(SEPARATOR).append(value);
                }
            }
        }
        String lockKey = REDIS_LOCK_PREFIX + SEPARATOR + lockKeyStr;
        RLock lock = redissonClient.getLock(lockKey);
        // 加锁标志位
        boolean lockFlag = false;
        try {
            long lockTime = autoLock.lockTime();
            long waitTime = autoLock.waitTime();
            TimeUnit timeUnit = autoLock.timeUnit();
            boolean tryLock = autoLock.tryLock();
            try {
                if (tryLock) {
                    lockFlag = lock.tryLock(waitTime, lockTime, timeUnit);
                } else {
                    lock.lock(lockTime, timeUnit);
                    lockFlag = true;
                }
            }catch (Exception e){
                LOGGER.error("加锁失败!,错误信息", e);
                throw new RuntimeException("加锁失败!");
            }
            if (!lockFlag) {
                throw new RuntimeException("加锁失败!");
            }
            // 执行业务
            return joinPoint.proceed();
        } finally {
            // 释放锁
            if (lockFlag) {
                lock.unlock();
                LOGGER.info("释放锁完成,key:{}",lockKey);
            }
        }
    }


}
复制代码

Get business attributes

This is a tool class for obtaining fields in objects. It is also implemented in some commonly used tool classes. You can use it directly or implement one yourself.

/**
 * @author huangle
 * @date 2023/5/5 15:17
 */
public class ReflectionUtil {

    public static List<Object> getFiledValues(Class<?> type, Object target, String[] fieldNames) throws IllegalAccessException {
        List<Field> fields = getFields(type, fieldNames);
        List<Object> valueList = new ArrayList();
        Iterator fieldIterator = fields.iterator();

        while(fieldIterator.hasNext()) {
            Field field = (Field)fieldIterator.next();
            if (!field.isAccessible()) {
                field.setAccessible(true);
            }

            Object value = field.get(target);
            valueList.add(value);
        }

        return valueList;
    }


    public static List<Field> getFields(Class<?> claszz, String[] fieldNames) {
        if (fieldNames != null && fieldNames.length != 0) {
            List<String> needFieldList = Arrays.asList(fieldNames);
            List<Field> matchFieldList = new ArrayList();
            List<Field> fields = getAllField(claszz);
            Iterator fieldIterator = fields.iterator();

            while(fieldIterator.hasNext()) {
                Field field = (Field)fieldIterator.next();
                if (needFieldList.contains(field.getName())) {
                    matchFieldList.add(field);
                }
            }

            return matchFieldList;
        } else {
            return Collections.EMPTY_LIST;
        }
    }

    public static List<Field> getAllField(Class<?> claszz) {
        if (claszz == null) {
            return Collections.EMPTY_LIST;
        } else {
            List<Field> list = new ArrayList();

            do {
                Field[] array = claszz.getDeclaredFields();
                list.addAll(Arrays.asList(array));
                claszz = claszz.getSuperclass();
            } while(claszz != null && claszz != Object.class);

            return list;
        }
    }
}
复制代码

Configure automatic injection

When we use the starter, we use this method to tell spring to complete the initialization of the bean when loading. This process is basically fixed. It is to write the configuration class, if the injection is completed through springBoot EnableAutoConfiguration. After injection, we can directly use the encapsulated lock.

/**
 * @author huangle
 * @date 2023/5/5 14:50
 */
@Configuration
public class LockAutoConfig {
    
    @Bean
    public AutoLockAspect autoLockAspect(){
        return new AutoLockAspect();
    }

}

// spring.factories 中内容
org.springframework.boot.autoconfigure.EnableAutoConfiguration=cn.anoxia.lock.config.LockAutoConfig
复制代码

test

Let's package this sarter first, and then import it into a project (I won't talk about the process of packaging and importing, just go and see for yourself) Directly go to the test class, and after executing the following, you can see that the lock has been released. If the business throws an exception and causes interruption, there is no need to worry about the lock not being released, because we release the lock in finally

/**
 * @author huangle
 * @date 2023/5/5 14:28
 */
@RestController
@RequestMapping("/v1/user")
public class UserController {

    @AutoLock(lockTime = 3, timeUnit = TimeUnit.MINUTES)
    @GetMapping("/getUser")
    public String getUser(@RequestParam @LockField String name) {
        return "hello:"+name;
    }


    @PostMapping("/userInfo")
    @AutoLock(lockTime = 1, timeUnit = TimeUnit.MINUTES)
    public String userInfo(@RequestBody @LockField(fieldNames = {"id", "name"}) UserDto userDto){
        return userDto.getId()+":"+userDto.getName();
    }

}
复制代码

Summarize

In many cases, some public business logic can be abstracted and exist as an independent component. We can constantly think and find out which ones can be abstracted and which ones can be simplified during the daily development process. Then try to abstract a component. In this way, you can not only exercise your ability, but also get some useful tools. Of course, there may be problems with the components you extract, but after you exercise slowly, it will always become better and better. the better. How should I put it, try to do it, let’s talk about whether it can be done well, and do it again and again if you can’t do it well.

 

Guess you like

Origin blog.csdn.net/ww2651071028/article/details/130551910