From 27a931ae779abb505eebda7ed44cccf8a218177a Mon Sep 17 00:00:00 2001 From: "binbin.hou" Date: Wed, 7 Dec 2022 18:39:00 +0800 Subject: [PATCH] [Feature] add for new --- .../lock/api/core/ILockSupportContext.java | 2 +- .../com/github/houbb/lock/core/bs/LockBs.java | 10 +- .../core/support/lock/LockSupportContext.java | 4 +- .../core/support/lock/RedisLockSupport.java | 4 +- lock-spring/pom.xml | 30 ++++ .../lock/spring/annotation/EnableLock.java | 43 ++++++ .../houbb/lock/spring/annotation/Lock.java | 28 ++++ .../houbb/lock/spring/aop/LockAspect.java | 141 ++++++++++++++++++ .../lock/spring/config/LockAopConfig.java | 69 +++++++++ .../lock/spring/config/LockBeanConfig.java | 46 ++++++ .../houbb/lock/spring/package-info.java | 1 + lock-test/pom.xml | 9 ++ .../houbb/lock/test/config/SpringConfig.java | 16 ++ .../github/houbb/lock/test/model/User.java | 33 ++++ .../houbb/lock/test/service/UserService.java | 23 +++ .../lock/test/spring/SpringServiceTest.java | 38 +++++ pom.xml | 27 ++++ 17 files changed, 514 insertions(+), 10 deletions(-) create mode 100644 lock-spring/pom.xml create mode 100644 lock-spring/src/main/java/com/github/houbb/lock/spring/annotation/EnableLock.java create mode 100644 lock-spring/src/main/java/com/github/houbb/lock/spring/annotation/Lock.java create mode 100644 lock-spring/src/main/java/com/github/houbb/lock/spring/aop/LockAspect.java create mode 100644 lock-spring/src/main/java/com/github/houbb/lock/spring/config/LockAopConfig.java create mode 100644 lock-spring/src/main/java/com/github/houbb/lock/spring/config/LockBeanConfig.java create mode 100644 lock-spring/src/main/java/com/github/houbb/lock/spring/package-info.java create mode 100644 lock-test/src/main/java/com/github/houbb/lock/test/config/SpringConfig.java create mode 100644 lock-test/src/main/java/com/github/houbb/lock/test/model/User.java create mode 100644 lock-test/src/main/java/com/github/houbb/lock/test/service/UserService.java create mode 100644 lock-test/src/test/java/com/github/houbb/lock/test/spring/SpringServiceTest.java diff --git a/lock-api/src/main/java/com/github/houbb/lock/api/core/ILockSupportContext.java b/lock-api/src/main/java/com/github/houbb/lock/api/core/ILockSupportContext.java index 9ded289..7cff82f 100644 --- a/lock-api/src/main/java/com/github/houbb/lock/api/core/ILockSupportContext.java +++ b/lock-api/src/main/java/com/github/houbb/lock/api/core/ILockSupportContext.java @@ -20,7 +20,7 @@ public interface ILockSupportContext { * @return 缓存策略 * @since 0.0.4 */ - ICommonCacheService commonCacheService(); + ICommonCacheService cache(); /** * 锁的过期时间 diff --git a/lock-core/src/main/java/com/github/houbb/lock/core/bs/LockBs.java b/lock-core/src/main/java/com/github/houbb/lock/core/bs/LockBs.java index f174ca3..862aec6 100644 --- a/lock-core/src/main/java/com/github/houbb/lock/core/bs/LockBs.java +++ b/lock-core/src/main/java/com/github/houbb/lock/core/bs/LockBs.java @@ -44,7 +44,7 @@ public final class LockBs implements ILock{ * 缓存策略 * @since 0.0.4 */ - private ICommonCacheService commonCacheService = JedisRedisServiceFactory.simple("127.0.0.1", 6379); + private ICommonCacheService cache = JedisRedisServiceFactory.pooled("127.0.0.1", 6379); /** * 锁支持策略 @@ -70,10 +70,10 @@ public final class LockBs implements ILock{ return this; } - public LockBs commonCacheService(ICommonCacheService commonCacheService) { - ArgUtil.notNull(commonCacheService, "commonCacheService"); + public LockBs cache(ICommonCacheService cache) { + ArgUtil.notNull(cache, "cache"); - this.commonCacheService = commonCacheService; + this.cache = cache; return this; } @@ -91,7 +91,7 @@ public final class LockBs implements ILock{ public LockBs init() { this.lockSupportContext = LockSupportContext.newInstance() .id(id) - .commonCacheService(commonCacheService) + .cache(cache) .lockExpireMills(lockExpireMills); return this; diff --git a/lock-core/src/main/java/com/github/houbb/lock/core/support/lock/LockSupportContext.java b/lock-core/src/main/java/com/github/houbb/lock/core/support/lock/LockSupportContext.java index c953e31..75145d3 100644 --- a/lock-core/src/main/java/com/github/houbb/lock/core/support/lock/LockSupportContext.java +++ b/lock-core/src/main/java/com/github/houbb/lock/core/support/lock/LockSupportContext.java @@ -44,11 +44,11 @@ public class LockSupportContext implements ILockSupportContext { } @Override - public ICommonCacheService commonCacheService() { + public ICommonCacheService cache() { return commonCacheService; } - public LockSupportContext commonCacheService(ICommonCacheService commonCacheService) { + public LockSupportContext cache(ICommonCacheService commonCacheService) { this.commonCacheService = commonCacheService; return this; } diff --git a/lock-core/src/main/java/com/github/houbb/lock/core/support/lock/RedisLockSupport.java b/lock-core/src/main/java/com/github/houbb/lock/core/support/lock/RedisLockSupport.java index 5b6f0ac..89c4db8 100644 --- a/lock-core/src/main/java/com/github/houbb/lock/core/support/lock/RedisLockSupport.java +++ b/lock-core/src/main/java/com/github/houbb/lock/core/support/lock/RedisLockSupport.java @@ -65,7 +65,7 @@ public class RedisLockSupport implements ILockSupport { IdThreadLocalHelper.put(requestId); log.info("开始尝试获取锁 requestId: {}", requestId); - final ICommonCacheService commonCacheService = context.commonCacheService(); + final ICommonCacheService commonCacheService = context.cache(); final int lockExpireMills = context.lockExpireMills(); @@ -79,7 +79,7 @@ public class RedisLockSupport implements ILockSupport { String requestId = IdThreadLocalHelper.get(); log.info("开始尝试释放锁 requestId: {}", requestId); - final ICommonCacheService commonCacheService = context.commonCacheService(); + final ICommonCacheService commonCacheService = context.cache(); String script = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end"; Object result = commonCacheService.eval(script, Collections.singletonList(key), Collections.singletonList(requestId)); return JedisConst.RELEASE_SUCCESS.equals(result); diff --git a/lock-spring/pom.xml b/lock-spring/pom.xml new file mode 100644 index 0000000..b21886e --- /dev/null +++ b/lock-spring/pom.xml @@ -0,0 +1,30 @@ + + + + lock + com.github.houbb + 1.0.0 + + 4.0.0 + + lock-spring + + + + com.github.houbb + lock-core + + + + com.github.houbb + aop-core + + + com.github.houbb + aop-spring + + + + \ No newline at end of file diff --git a/lock-spring/src/main/java/com/github/houbb/lock/spring/annotation/EnableLock.java b/lock-spring/src/main/java/com/github/houbb/lock/spring/annotation/EnableLock.java new file mode 100644 index 0000000..82d8b3c --- /dev/null +++ b/lock-spring/src/main/java/com/github/houbb/lock/spring/annotation/EnableLock.java @@ -0,0 +1,43 @@ +package com.github.houbb.lock.spring.annotation; + +import com.github.houbb.lock.spring.config.LockAopConfig; +import org.springframework.context.annotation.EnableAspectJAutoProxy; +import org.springframework.context.annotation.Import; + +import java.lang.annotation.*; + +/** + * 启用注解 + * @author binbin.hou + * @since 0.0.2 + */ +@Target(ElementType.TYPE) +@Retention(RetentionPolicy.RUNTIME) +@Documented +@Import(LockAopConfig.class) +@EnableAspectJAutoProxy +public @interface EnableLock { + + /** + * 加锁过期时间 + * @return 时间 + * @since 1.1.0 + */ + int lockExpireMills() default 60 * 1000; + + /** + * 唯一标识生成策略 + * @return 结果 + * @since 1.1.0 + */ + String id() default "lockId"; + + /** + * 缓存实现策略 bean 名称 + * @return 实现 + * @since 1.1.0 + */ + String cache() default "lockCache"; + + +} diff --git a/lock-spring/src/main/java/com/github/houbb/lock/spring/annotation/Lock.java b/lock-spring/src/main/java/com/github/houbb/lock/spring/annotation/Lock.java new file mode 100644 index 0000000..ac00652 --- /dev/null +++ b/lock-spring/src/main/java/com/github/houbb/lock/spring/annotation/Lock.java @@ -0,0 +1,28 @@ +package com.github.houbb.lock.spring.annotation; + +import java.lang.annotation.*; + +/** + * 分布式加锁注解 + * @author binbin.hou + * @since 0.0.2 + */ +@Target(ElementType.METHOD) +@Retention(RetentionPolicy.RUNTIME) +@Documented +@Inherited +public @interface Lock { + + /** + * 缓存的 key 策略,支持 SpEL + * @return 结果 + */ + String value() default ""; + + /** + * 尝试获取锁等待时间 + * @return 结果 + */ + long tryLockMills() default 10 * 1000; + +} diff --git a/lock-spring/src/main/java/com/github/houbb/lock/spring/aop/LockAspect.java b/lock-spring/src/main/java/com/github/houbb/lock/spring/aop/LockAspect.java new file mode 100644 index 0000000..0ca7da6 --- /dev/null +++ b/lock-spring/src/main/java/com/github/houbb/lock/spring/aop/LockAspect.java @@ -0,0 +1,141 @@ +package com.github.houbb.lock.spring.aop; + +import com.github.houbb.aop.spring.util.SpringAopUtil; +import com.github.houbb.heaven.util.lang.ObjectUtil; +import com.github.houbb.heaven.util.lang.StringUtil; +import com.github.houbb.heaven.util.lang.reflect.ReflectMethodUtil; +import com.github.houbb.heaven.util.util.ArrayUtil; +import com.github.houbb.lock.api.exception.LockException; +import com.github.houbb.lock.core.bs.LockBs; +import com.github.houbb.lock.spring.annotation.Lock; +import com.github.houbb.log.integration.core.Log; +import com.github.houbb.log.integration.core.LogFactory; +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.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.expression.EvaluationContext; +import org.springframework.expression.ExpressionParser; +import org.springframework.expression.spel.standard.SpelExpressionParser; +import org.springframework.expression.spel.support.StandardEvaluationContext; +import org.springframework.stereotype.Component; + +import java.lang.reflect.Method; +import java.util.Arrays; +import java.util.List; +import java.util.concurrent.TimeUnit; + +/** + * @author binbin.hou + * @since 1.1.0 + */ +@Aspect +@Component +public class LockAspect { + + private final Log log = LogFactory.getLog(LockAspect.class); + + @Autowired + @Qualifier("lockBs") + private LockBs lockBs; + + @Pointcut("@annotation(com.github.houbb.lock.spring.annotation.Lock)") + public void lockPointcut() { + } + + /** + * 执行核心方法 + * + * 相当于 MethodInterceptor + * @param point 切点 + * @return 结果 + * @throws Throwable 异常信息 + * @since 0.0.2 + */ + @Around("lockPointcut()") + public Object around(ProceedingJoinPoint point) throws Throwable { + Method method = SpringAopUtil.getCurrentMethod(point); + + boolean isLockAnnotation = method.isAnnotationPresent(Lock.class); + if(isLockAnnotation) { + Lock lock = method.getAnnotation(Lock.class); + + // 如果构建 key? + Object[] args = point.getArgs(); + String lockKey = buildLockKey(lock, method, args); + try { + long tryLockMills = lock.tryLockMills(); + + boolean tryLockFlag = lockBs.tryLock(tryLockMills, TimeUnit.MILLISECONDS, lockKey); + if(!tryLockFlag) { + log.warn("尝试获取锁失败 {}", lockKey); + throw new LockException("尝试获取锁失败 " + lockKey); + } + + // 执行业务 + return point.proceed(); + } catch (Throwable e) { + throw new RuntimeException(e); + } finally { + boolean unLockFlag = lockBs.unlock(lockKey); + if(!unLockFlag) { + log.warn("尝试释放锁失败 {}", lockKey); + // 这里释放异常,没有意义。 + } + } + } else { + return point.proceed(); + } + } + + /** + * 构建加锁的 key + * + * https://www.cnblogs.com/fashflying/p/6908028.html spring cache + * + * https://www.cnblogs.com/best/p/5748105.html SpEL + * @param lock 注解信息 + * @param args 参数 + * @return 结果 + */ + private String buildLockKey(Lock lock, + Method method, + Object[] args) { + final String lockValue = lock.value(); + + //创建SpEL表达式的解析器 + ExpressionParser parser = new SpelExpressionParser(); + //1. 如果没有入参怎么办? + if(ArrayUtil.isEmpty(args)) { + log.warn("对应的数组信息为空,直接返回 key 的值 {}", lockValue); + return lockValue; + } + + // 如果 lockValue 的值为空呢? + if(StringUtil.isEmpty(lockValue)) { + return Arrays.toString(args); + } + + // 如何获取参数名称呢? + + //解析表达式需要的上下文,解析时有一个默认的上下文 + // jdk1.7 之前,直接使用 arg0, arg1... + EvaluationContext ctx = new StandardEvaluationContext(); + List paramNameList = ReflectMethodUtil.getParamNames(method); + + for(int i = 0; i < paramNameList.size(); i++) { + String paramName = paramNameList.get(i); + Object paramValue = args[i]; + + //在上下文中设置变量,变量名为user,内容为user对象 + ctx.setVariable(paramName, paramValue); + } + + // 直接 toString,比较简单。 + Object value = parser.parseExpression(lockValue).getValue(ctx); + return ObjectUtil.objectToString(value, "null"); + } + +} diff --git a/lock-spring/src/main/java/com/github/houbb/lock/spring/config/LockAopConfig.java b/lock-spring/src/main/java/com/github/houbb/lock/spring/config/LockAopConfig.java new file mode 100644 index 0000000..eea50d9 --- /dev/null +++ b/lock-spring/src/main/java/com/github/houbb/lock/spring/config/LockAopConfig.java @@ -0,0 +1,69 @@ +package com.github.houbb.lock.spring.config; + +import com.github.houbb.common.cache.api.service.ICommonCacheService; +import com.github.houbb.heaven.util.lang.StringUtil; +import com.github.houbb.id.api.Id; +import com.github.houbb.id.core.core.Ids; +import com.github.houbb.lock.core.bs.LockBs; +import com.github.houbb.lock.spring.annotation.EnableLock; +import com.github.houbb.redis.config.core.factory.JedisRedisServiceFactory; +import org.springframework.beans.BeansException; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.beans.factory.config.BeanFactoryPostProcessor; +import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; +import org.springframework.context.annotation.*; +import org.springframework.core.annotation.AnnotationAttributes; +import org.springframework.core.type.AnnotationMetadata; + +/** + * aop 配置 + * + * @author binbin.hou + * @since 0.0.2 + */ +@Configuration +@ComponentScan(basePackages = "com.github.houbb.lock.spring") +@Import(LockBeanConfig.class) +public class LockAopConfig implements ImportAware, BeanFactoryPostProcessor { + + @Bean("lockBs") + public LockBs lockBs() { + int lockExpireMills = (int) enableLockAttributes.get("lockExpireMills"); + ICommonCacheService commonCacheService = beanFactory.getBean(enableLockAttributes.getString("cache"), ICommonCacheService.class); + Id id = beanFactory.getBean(enableLockAttributes.getString("id"), Id.class); + + return LockBs.newInstance() + .cache(commonCacheService) + .id(id) + .lockExpireMills(lockExpireMills) + .init(); + } + + /** + * 属性信息 + */ + private AnnotationAttributes enableLockAttributes; + + /** + * bean 工厂 + * + * @since 0.0.5 + */ + private ConfigurableListableBeanFactory beanFactory; + + @Override + public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException { + this.beanFactory = beanFactory; + } + + @Override + public void setImportMetadata(AnnotationMetadata annotationMetadata) { + enableLockAttributes = AnnotationAttributes.fromMap( + annotationMetadata.getAnnotationAttributes(EnableLock.class.getName(), false)); + if (enableLockAttributes == null) { + throw new IllegalArgumentException( + "@EnableLock is not present on importing class " + annotationMetadata.getClassName()); + } + } + +} diff --git a/lock-spring/src/main/java/com/github/houbb/lock/spring/config/LockBeanConfig.java b/lock-spring/src/main/java/com/github/houbb/lock/spring/config/LockBeanConfig.java new file mode 100644 index 0000000..b7b3e91 --- /dev/null +++ b/lock-spring/src/main/java/com/github/houbb/lock/spring/config/LockBeanConfig.java @@ -0,0 +1,46 @@ +package com.github.houbb.lock.spring.config; + +import com.github.houbb.common.cache.api.service.ICommonCacheService; +import com.github.houbb.heaven.util.lang.StringUtil; +import com.github.houbb.id.api.Id; +import com.github.houbb.id.core.core.Ids; +import com.github.houbb.redis.config.core.factory.JedisRedisServiceFactory; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.ComponentScan; +import org.springframework.context.annotation.Configuration; + +/** + * bean 配置 + * + * @author binbin.hou + * @since 0.0.2 + */ +@Configuration +@ComponentScan(basePackages = "com.github.houbb.lock.spring") +public class LockBeanConfig { + + @Value("${redis.address:127.0.0.1}") + private String redisAddress; + + @Value("${redis.port:6379}") + private int redisPort; + + @Value("${redis.password:}") + private String redisPassword; + + @Bean("lockCache") + public ICommonCacheService lockCache() { + if(StringUtil.isNotEmpty(redisPassword)) { + return JedisRedisServiceFactory.pooled(redisAddress, redisPort, redisPassword); + } + + return JedisRedisServiceFactory.simple(redisAddress, redisPort); + } + + @Bean("lockId") + public Id lockId() { + return Ids.uuid32(); + } + +} diff --git a/lock-spring/src/main/java/com/github/houbb/lock/spring/package-info.java b/lock-spring/src/main/java/com/github/houbb/lock/spring/package-info.java new file mode 100644 index 0000000..4bcc69d --- /dev/null +++ b/lock-spring/src/main/java/com/github/houbb/lock/spring/package-info.java @@ -0,0 +1 @@ +package com.github.houbb.lock.spring; \ No newline at end of file diff --git a/lock-test/pom.xml b/lock-test/pom.xml index 49d9eec..f4ad931 100644 --- a/lock-test/pom.xml +++ b/lock-test/pom.xml @@ -16,11 +16,20 @@ com.github.houbb lock-core + + com.github.houbb + lock-spring + junit junit + + + com.github.houbb + test-spring + diff --git a/lock-test/src/main/java/com/github/houbb/lock/test/config/SpringConfig.java b/lock-test/src/main/java/com/github/houbb/lock/test/config/SpringConfig.java new file mode 100644 index 0000000..5001678 --- /dev/null +++ b/lock-test/src/main/java/com/github/houbb/lock/test/config/SpringConfig.java @@ -0,0 +1,16 @@ +package com.github.houbb.lock.test.config; + + +import com.github.houbb.lock.spring.annotation.EnableLock; +import org.springframework.beans.factory.annotation.Configurable; +import org.springframework.context.annotation.ComponentScan; + +/** + * @author binbin.hou + * @since 1.0.0 + */ +@Configurable +@ComponentScan(basePackages = "com.github.houbb.lock.test.service") +@EnableLock +public class SpringConfig { +} diff --git a/lock-test/src/main/java/com/github/houbb/lock/test/model/User.java b/lock-test/src/main/java/com/github/houbb/lock/test/model/User.java new file mode 100644 index 0000000..5092d57 --- /dev/null +++ b/lock-test/src/main/java/com/github/houbb/lock/test/model/User.java @@ -0,0 +1,33 @@ +package com.github.houbb.lock.test.model; + +public class User { + + private String name; + + private String id; + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public String getId() { + return id; + } + + public void setId(String id) { + this.id = id; + } + + @Override + public String toString() { + return "User{" + + "name='" + name + '\'' + + ", id='" + id + '\'' + + '}'; + } + +} diff --git a/lock-test/src/main/java/com/github/houbb/lock/test/service/UserService.java b/lock-test/src/main/java/com/github/houbb/lock/test/service/UserService.java new file mode 100644 index 0000000..8cd3225 --- /dev/null +++ b/lock-test/src/main/java/com/github/houbb/lock/test/service/UserService.java @@ -0,0 +1,23 @@ +package com.github.houbb.lock.test.service; + +import com.github.houbb.lock.spring.annotation.Lock; +import com.github.houbb.lock.test.model.User; +import org.springframework.stereotype.Service; + +/** + * @author binbin.hou + * @since 1.0.0 + */ +@Service +public class UserService { + + @Lock + public String queryUserName(Long userId) { + return userId+"-name"; + } + + @Lock(value = "#arg0.name") + public void queryUserName2(User user) { + System.out.println("user: " + user.toString()); + } +} diff --git a/lock-test/src/test/java/com/github/houbb/lock/test/spring/SpringServiceTest.java b/lock-test/src/test/java/com/github/houbb/lock/test/spring/SpringServiceTest.java new file mode 100644 index 0000000..53b80bd --- /dev/null +++ b/lock-test/src/test/java/com/github/houbb/lock/test/spring/SpringServiceTest.java @@ -0,0 +1,38 @@ +package com.github.houbb.lock.test.spring; + + +import com.github.houbb.lock.test.config.SpringConfig; +import com.github.houbb.lock.test.model.User; +import com.github.houbb.lock.test.service.UserService; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; + +/** + * @author binbin.hou + * @since 1.0.0 + */ +@ContextConfiguration(classes = SpringConfig.class) +@RunWith(SpringJUnit4ClassRunner.class) +public class SpringServiceTest { + + @Autowired + private UserService userService; + + @Test + public void queryLogTest() { + final String key = "name"; + final String value = userService.queryUserName(1L); + } + + @Test + public void queryUserNameTest() { + User user = new User(); + user.setId("001"); + user.setName("u-001"); + userService.queryUserName2(user); + } + +} diff --git a/pom.xml b/pom.xml index 8cbe6f6..19178ad 100644 --- a/pom.xml +++ b/pom.xml @@ -10,6 +10,7 @@ lock-api lock-core lock-test + lock-spring @@ -40,6 +41,8 @@ 1.1.8 0.0.5 1.4.0 + 0.0.2 + 0.0.1 4.12 @@ -59,6 +62,12 @@ lock-core ${project.version} + + com.github.houbb + lock-spring + ${project.version} + + com.github.houbb @@ -117,6 +126,24 @@ jedis ${jedis.version} + + + com.github.houbb + aop-core + ${aop.version} + + + com.github.houbb + aop-spring + ${aop.version} + + + + com.github.houbb + test-spring + ${test.version} + +