Browse Source

feat: 初次提交

master
niushuai233 2 years ago
commit
1dcefee9dd
  1. 33
      .gitignore
  2. 71
      pom.xml
  3. 19
      src/main/java/cc/niushuai/projects/demo/aspectlock/AspectLockApplication.java
  4. 59
      src/main/java/cc/niushuai/projects/demo/aspectlock/common/anno/AopLock.java
  5. 25
      src/main/java/cc/niushuai/projects/demo/aspectlock/common/anno/Lock.java
  6. 196
      src/main/java/cc/niushuai/projects/demo/aspectlock/common/anno/LockAspect.java
  7. 32
      src/main/java/cc/niushuai/projects/demo/aspectlock/common/exception/GlobalExceptionHandler.java
  8. 29
      src/main/java/cc/niushuai/projects/demo/aspectlock/common/exception/LockException.java
  9. 129
      src/main/java/cc/niushuai/projects/demo/aspectlock/common/util/AopLockUtil.java
  10. 31
      src/main/java/cc/niushuai/projects/demo/aspectlock/student/controller/StudentController.java
  11. 18
      src/main/java/cc/niushuai/projects/demo/aspectlock/student/entity/Student.java
  12. 8
      src/main/resources/application.yml
  13. 13
      src/test/java/cc/niushuai/projects/demo/aspectlock/AspectLockApplicationTests.java
  14. 21
      src/test/java/cc/niushuai/projects/demo/aspectlock/OptionalTest.java

33
.gitignore vendored

@ -0,0 +1,33 @@ @@ -0,0 +1,33 @@
HELP.md
target/
!.mvn/wrapper/maven-wrapper.jar
!**/src/main/**/target/
!**/src/test/**/target/
### STS ###
.apt_generated
.classpath
.factorypath
.project
.settings
.springBeans
.sts4-cache
### IntelliJ IDEA ###
.idea
*.iws
*.iml
*.ipr
### NetBeans ###
/nbproject/private/
/nbbuild/
/dist/
/nbdist/
/.nb-gradle/
build/
!**/src/main/**/build/
!**/src/test/**/build/
### VS Code ###
.vscode/

71
pom.xml

@ -0,0 +1,71 @@ @@ -0,0 +1,71 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.6.2</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>cc.niushuai.projects.demo</groupId>
<artifactId>aspect-lock</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>aspect-lock</name>
<description>使用切面的方式来对$加锁</description>
<properties>
<java.version>1.8</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
<version>5.3.8</version>
</dependency>
<dependency>
<groupId>org.apache.poi</groupId>
<artifactId>poi-excelant</artifactId>
<version>4.1.1</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<excludes>
<exclude>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</exclude>
</excludes>
</configuration>
</plugin>
</plugins>
</build>
</project>

19
src/main/java/cc/niushuai/projects/demo/aspectlock/AspectLockApplication.java

@ -0,0 +1,19 @@ @@ -0,0 +1,19 @@
package cc.niushuai.projects.demo.aspectlock;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
/**
* 主应用 启动类
*
* @author niushuai
* @date: 2021/12/22 9:59
*/
@SpringBootApplication
public class AspectLockApplication {
public static void main(String[] args) {
SpringApplication.run(AspectLockApplication.class, args);
}
}

59
src/main/java/cc/niushuai/projects/demo/aspectlock/common/anno/AopLock.java

@ -0,0 +1,59 @@ @@ -0,0 +1,59 @@
package cc.niushuai.projects.demo.aspectlock.common.anno;
import java.lang.annotation.*;
/**
* AopLock 注解
*
* @author niushuai
* @date 2021/12/22 10:01
*/
@Target(value = ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
public @interface AopLock {
/**
* 是否启用 默认false
* <pre>
* true 表示要利用反射来获取对象属性值作为key和value
* false 表示直接将key作为key, value作为value
* </pre>
*/
boolean enableReflect() default false;
/**
* key 多个数据会组合到一起
* <pre>
* 若为基本类型或其包装类型 直接 paramName 即可
* 若为对象请用 paramName.fieldName 的形式
* <pre/>
*/
String[] key();
/**
* value 多个数据会组合到一起
*/
String[] value() default "aopLock_default_value";
/**
* key前缀
*/
String prefix() default "";
/**
* key 数组element分隔符
*/
String keySeparator() default "";
/**
* value 数组element分隔符
*/
String valueSeparator() default "";
/**
* 有效期 默认3000ms
*/
long period() default 3000L;
}

25
src/main/java/cc/niushuai/projects/demo/aspectlock/common/anno/Lock.java

@ -0,0 +1,25 @@ @@ -0,0 +1,25 @@
package cc.niushuai.projects.demo.aspectlock.common.anno;
import lombok.Data;
import lombok.experimental.Accessors;
/**
* lock 参数实体类
*
* @author niushuai
* @date 2021/12/22 10:21
*/
@Data
@Accessors(chain = true)
public class Lock {
private String key;
private String value;
private long period;
private long expireTime;
public boolean isExpire() {
// 预计过期时间
return expireTime < System.currentTimeMillis();
}
}

196
src/main/java/cc/niushuai/projects/demo/aspectlock/common/anno/LockAspect.java

@ -0,0 +1,196 @@ @@ -0,0 +1,196 @@
package cc.niushuai.projects.demo.aspectlock.common.anno;
import cc.niushuai.projects.demo.aspectlock.common.exception.LockException;
import cc.niushuai.projects.demo.aspectlock.common.util.AopLockUtil;
import cn.hutool.core.util.ReflectUtil;
import cn.hutool.core.util.StrUtil;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.stereotype.Component;
import java.util.Arrays;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
/**
* LockAspect 锁切面
*
* @author niushuai
* @date 2021/12/22 10:17
*/
@Slf4j
@Aspect
@Component
public class LockAspect {
private static final String POINT = ".";
private static final String REGEX_POINT = "\\.";
private static final Class[] BASE_TYPE_CLASS =
new Class[]{
Byte.class,
Character.class,
Short.class, Integer.class, Long.class,
Float.class, Double.class,
String.class,
Boolean.class,
Date.class
};
@Pointcut(value = "@annotation(cc.niushuai.projects.demo.aspectlock.common.anno.AopLock)")
public void lockPointcut() {
}
@Around(value = "lockPointcut()")
public Object doAround(ProceedingJoinPoint joinPoint) throws Throwable {
Lock lock = parseLock(joinPoint);
try {
// 加锁
AopLockUtil.lock(lock);
// 执行方法
return joinPoint.proceed();
} finally {
// 解锁
AopLockUtil.unlock(lock);
}
}
/**
* 封装出一个lock对象
*
* @param joinPoint 切入点
* @author niushuai
* @date: 2021/12/22 17:42
* @return: {@link Lock} 封装过的lock对象
*/
private Lock parseLock(ProceedingJoinPoint joinPoint) {
Lock lock = new Lock();
StringBuffer key = new StringBuffer(), value = new StringBuffer();
// 实际参数值
Object[] args = joinPoint.getArgs();
MethodSignature method = (MethodSignature) joinPoint.getSignature();
// 方法签名上的参数类型
// Class[] parameterTypes = method.getParameterTypes();
// 方法签名上的参数名称
// String[] parameterNames = new String[]{};
// 确定key的顺序 便于后续反射取值
Map<String, Integer> paramOrderMap = transferParamOrderMap(method.getParameterNames());
Map<Integer, Class> classOrderMap = transferClassOrderMap(method.getParameterTypes());
// 方法上的注解 携带有具体的内容
AopLock annotation = method.getMethod().getAnnotation(AopLock.class);
String[] keyArr = annotation.key();
String[] valueArr = annotation.value();
// 根据是否启用反射取值来进行不同的处理
log.debug("启用反射取值 - {}", annotation.enableReflect());
if (annotation.enableReflect()) {
// key
reflectVal(key, args, paramOrderMap, classOrderMap, keyArr);
// value
reflectVal(value, args, paramOrderMap, classOrderMap, valueArr);
} else {
// 未启用反射 直接拼接key和value
key.append(StrUtil.concat(true, annotation.prefix(), StrUtil.join(annotation.keySeparator(), keyArr)));
value.append(StrUtil.join(annotation.valueSeparator(), valueArr));
}
log.debug("锁值 - key: {}, value: {}", key, value);
lock.setKey(key.toString()).setValue(value.toString()).setPeriod(annotation.period());
return lock;
}
private void reflectVal(StringBuffer key, Object[] args, Map<String, Integer> paramOrderMap, Map<Integer, Class> classOrderMap, String[] keyArr) {
for (String tmpKey : keyArr) {
// student.age
// 要先判断 . 是否存在
boolean baseType = false;
if (tmpKey.contains(POINT)) {
// 含有点
} else if (judgeBaseType(tmpKey, paramOrderMap, classOrderMap)) {
// 基础类型
baseType = true;
} else {
throw new LockException("取值 - 在启用反射获取值时请按照paramName.fieldName的方式来做 [" + tmpKey + "]");
}
if (baseType) {
// 基础类型不用反射 直接拼接即可
key.append(args[paramOrderMap.get(tmpKey)]);
continue;
}
// 获取paramName.fieldName的具体值
String[] split = tmpKey.split(REGEX_POINT);
String paramName = split[0];
String fieldName = split[1];
if (!paramOrderMap.containsKey(paramName)) {
throw new LockException("取值 - 不存在形参名为[" + paramName + "]的参数");
}
try {
// 非基础类型 通过反射取舍 像list map啊啥的 么想好怎么搞
// 感觉可以通过类型判断来对于这些特定类型进行按照特定的方式进行取值
// 最后才会使用到反射来取值 一般应用场景的话 形参对象居多 先不考虑吧
Object fieldValue = ReflectUtil.getFieldValue(args[paramOrderMap.get(paramName)], fieldName);
key.append(fieldValue);
} catch (Exception e) {
throw new LockException("取值 - 不存在形参名为[" + paramName + "], 且字段名称为[" + fieldName + "]的参数, 完整参数: [" + tmpKey + "]");
}
}
}
/**
* 判断既定key是否为基础类型
*
* @param tmpKey 既定key
* @param paramOrderMap 参数顺序
* @param classOrderMap clazz顺序
* @author niushuai
* @date: 2021/12/23 10:03
* @return: {@link boolean}
*/
private boolean judgeBaseType(String tmpKey, Map<String, Integer> paramOrderMap, Map<Integer, Class> classOrderMap) {
// 进入到此处时均为不带点的参数
if (!paramOrderMap.containsKey(tmpKey)) {
// 由于基础类型不需要. 因为tmpKey应于key保持一致 所以在paramOrderKey中含有则对 若不含有
throw new LockException("取值 - [" + tmpKey + "]在形参中不存在");
}
Class clazz = classOrderMap.get(paramOrderMap.get(tmpKey));
// 此处不可能为null 匹配既定的任一类型
return Arrays.stream(BASE_TYPE_CLASS).anyMatch(item -> item.isAssignableFrom(clazz));
}
private Map<String, Integer> transferParamOrderMap(String[] parameterNames) {
Map<String, Integer> map = new HashMap<>();
for (int i = 0; i < parameterNames.length; i++) {
map.put(parameterNames[i], i);
}
return map;
}
private Map<Integer, Class> transferClassOrderMap(Class[] clazz) {
Map<Integer, Class> map = new HashMap<>();
for (int i = 0; i < clazz.length; i++) {
map.put(i, clazz[i]);
}
return map;
}
}

32
src/main/java/cc/niushuai/projects/demo/aspectlock/common/exception/GlobalExceptionHandler.java

@ -0,0 +1,32 @@ @@ -0,0 +1,32 @@
package cc.niushuai.projects.demo.aspectlock.common.exception;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import org.springframework.web.servlet.mvc.method.annotation.ResponseEntityExceptionHandler;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.HashMap;
import java.util.Map;
/**
* TODO
*
* @author niushuai
* @date 2021/12/23 10:18
*/
@RestControllerAdvice
public class GlobalExceptionHandler extends ResponseEntityExceptionHandler {
@ExceptionHandler(value = LockException.class)
public Map<String, Object> lockExceptionHandler(HttpServletRequest request, final Exception e, HttpServletResponse response) {
Map<String, Object> map = new HashMap<>();
map.put("code", 500);
map.put("message", e.getMessage());
map.put("data", null);
return map;
}
}

29
src/main/java/cc/niushuai/projects/demo/aspectlock/common/exception/LockException.java

@ -0,0 +1,29 @@ @@ -0,0 +1,29 @@
package cc.niushuai.projects.demo.aspectlock.common.exception;
/**
* 锁异常
*
* @author niushuai
* @date 2021/12/22 10:42
*/
public class LockException extends RuntimeException {
public LockException() {
}
public LockException(String message) {
super(message);
}
public LockException(String message, Throwable cause) {
super(message, cause);
}
public LockException(Throwable cause) {
super(cause);
}
public LockException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) {
super(message, cause, enableSuppression, writableStackTrace);
}
}

129
src/main/java/cc/niushuai/projects/demo/aspectlock/common/util/AopLockUtil.java

@ -0,0 +1,129 @@ @@ -0,0 +1,129 @@
package cc.niushuai.projects.demo.aspectlock.common.util;
import cc.niushuai.projects.demo.aspectlock.common.anno.Lock;
import cc.niushuai.projects.demo.aspectlock.common.exception.LockException;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
import java.util.HashMap;
import java.util.Map;
/**
* 公共工具类
*
* @author niushuai
* @date 2021/12/22 10:19
*/
public class AopLockUtil {
/**
* 锁柜
*/
private static final Map<String, Lock> LOCK_MAP = new HashMap<>(16);
private AopLockUtil() {
}
/**
* 从锁柜中根据钥匙获取到相应的锁
*
* @param key 钥匙
* @author niushuai
* @date: 2021/12/22 11:24
* @return: {@link Lock}
*/
private static Lock getLock(String key) {
return LOCK_MAP.get(key);
}
/**
* 加锁
*
* @param lock
* @author niushuai
* @date: 2021/12/22 11:25
* @return: void
*/
public static void lock(Lock lock) {
// 加锁时 钥匙不能为空
validate(lock.getKey());
// 要求锁不存在
if (isLocked(lock.getKey())) {
throw new LockException("加锁失败 - 锁[" + lock.getKey() + "]已存在");
}
// 重置key过期时间
lock.setExpireTime(getExpireTime(lock.getPeriod()));
// 放入锁柜
LOCK_MAP.put(lock.getKey(), lock);
}
/**
* 解锁
*
* @param lock
* @author niushuai
* @date: 2021/12/22 11:29
* @return: void
*/
public static void unlock(Lock lock) {
// 要求锁存在
if (ObjectUtil.isNull(getLock(lock.getKey()))) {
throw new LockException("解锁失败 - 锁[" + lock.getKey() + "]不存在");
}
// 无需关心过期问题 直接移除锁柜
LOCK_MAP.remove(lock.getKey());
}
/**
* 是否可锁
* 为空 可锁
* 锁已过期 可锁
*
* @param key 钥匙
* @author niushuai
* @date: 2021/12/22 11:06
* @return: {@link boolean}
*/
public static boolean lockable(String key) {
Lock lock = getLock(key);
return ObjectUtil.isEmpty(lock) || lock.isExpire();
}
/**
* 非空 且未过期 即为true
* 非空 已过期 为false
* 为false
*
* @param key 钥匙
* @author niushuai
* @date: 2021/12/22 11:04
* @return: {@link boolean} 是否可锁
*/
public static boolean isLocked(String key) {
return !lockable(key);
}
private static void validate(String key) {
if (StrUtil.isEmpty(key)) {
throw new LockException("锁[" + key + "]不能为空");
}
}
/**
* 根据有效时间 获取基于当前时间的有效期
*
* @param period
* @author niushuai
* @date: 2021/12/22 10:40
* @return: {@link long}
*/
private static long getExpireTime(long period) {
return System.currentTimeMillis() + period;
}
}

31
src/main/java/cc/niushuai/projects/demo/aspectlock/student/controller/StudentController.java

@ -0,0 +1,31 @@ @@ -0,0 +1,31 @@
package cc.niushuai.projects.demo.aspectlock.student.controller;
import cc.niushuai.projects.demo.aspectlock.common.anno.AopLock;
import cc.niushuai.projects.demo.aspectlock.student.entity.Student;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
/**
* TODO
*
* @author niushuai
* @date 2021/12/22 11:44
*/
@RestController
@RequestMapping("/students")
public class StudentController {
@AopLock(enableReflect = true, key = {"student.namex", "student.age"}, keySeparator = "_", value = "age")
@GetMapping("/query/{age}")
public Student query(@PathVariable Long age, Student student) {
return new Student();
}
@GetMapping("/query2/{age}")
public Student query2(@PathVariable Long age, Student student) {
return new Student();
}
}

18
src/main/java/cc/niushuai/projects/demo/aspectlock/student/entity/Student.java

@ -0,0 +1,18 @@ @@ -0,0 +1,18 @@
package cc.niushuai.projects.demo.aspectlock.student.entity;
import lombok.Data;
/**
* TODO
*
* @author niushuai
* @date 2021/12/22 11:44
*/
@Data
public class Student {
private String id;
private String stuNo;
private String name;
private Long age;
}

8
src/main/resources/application.yml

@ -0,0 +1,8 @@ @@ -0,0 +1,8 @@
server:
port: 8301
servlet:
context-path: /
logging:
level:
cc.niushuai.projects.demo.aspectlock: debug

13
src/test/java/cc/niushuai/projects/demo/aspectlock/AspectLockApplicationTests.java

@ -0,0 +1,13 @@ @@ -0,0 +1,13 @@
package cc.niushuai.projects.demo.aspectlock;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;
@SpringBootTest
class AspectLockApplicationTests {
@Test
void contextLoads() {
}
}

21
src/test/java/cc/niushuai/projects/demo/aspectlock/OptionalTest.java

@ -0,0 +1,21 @@ @@ -0,0 +1,21 @@
package cc.niushuai.projects.demo.aspectlock;
import java.util.Arrays;
/**
* TODO
*
* @author niushuai
* @date 2021/12/22 10:50
*/
public class OptionalTest {
public static void main(String[] args) throws InterruptedException {
String[] split = "student.name".split("\\.");
System.out.println(Arrays.toString(split));
}
}
Loading…
Cancel
Save