<em>Mac</em>Book项目 2009年学校开始实施<em>Mac</em>Book项目,所有师生配备一本<em>Mac</em>Book,并同步更新了校园无线网络。学校每周进行电脑技术更新,每月发送技术支持资料,极大改变了教学及学习方式。因此2011
2021-06-01 09:32:01
<?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.4.4</version> <relativePath/> <!-- lookup parent from repository --> </parent> <groupId>com.example</groupId> <artifactId>demo</artifactId> <version>0.0.1-SNAPSHOT</version> <name>demo</name> <description>Demo project for Spring Boot</description> <properties> <java.version>1.8</java.version> </properties> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-redis</artifactId> <version>2.3.7.RELEASE</version> </dependency> <dependency> <groupId>org.springframework.integration</groupId> <artifactId>spring-integration-redis</artifactId> <version>5.4.5</version> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-aop</artifactId> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins> </build> </project>
package com.example.demo.config; import org.springframework.beans.factory.annotation.Value; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.data.redis.connection.RedisConnectionFactory; import org.springframework.integration.redis.util.RedisLockRegistry; @Configuration public class RedisLockRegistryConfig { /** * 預設過期時間300s */ @Value("${distribute.lock.expireTime:300}") private long expireTime; @Value("${spring.application.name:'distributeLock'}") private String registryKey; @Bean public RedisLockRegistry redisLockRegistry(RedisConnectionFactory factory){ return new RedisLockRegistry(factory, registryKey, expireTime * 1000); } }
package com.example.demo.aop.annotation; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; @Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) public @interface DistributeLock { String name() default ""; }
package com.example.demo.aop; import com.example.demo.aop.annotation.DistributeLock; import org.aspectj.lang.ProceedingJoinPoint; import org.aspectj.lang.annotation.Around; import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.reflect.MethodSignature; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.core.DefaultParameterNameDiscoverer; import org.springframework.core.annotation.AnnotationUtils; import org.springframework.core.annotation.Order; import org.springframework.expression.EvaluationContext; import org.springframework.expression.Expression; import org.springframework.expression.spel.standard.SpelExpressionParser; import org.springframework.expression.spel.support.StandardEvaluationContext; import org.springframework.integration.redis.util.RedisLockRegistry; import org.springframework.stereotype.Component; import java.lang.reflect.Method; import java.util.Objects; import java.util.concurrent.locks.Lock; @Aspect @Order @Component public class DistributeLockAop { private static final Logger LOGGER = LoggerFactory.getLogger(DistributeLockAop.class); private static SpelExpressionParser parser = new SpelExpressionParser(); private static DefaultParameterNameDiscoverer discoverer = new DefaultParameterNameDiscoverer(); private RedisLockRegistry redisLockRegistry; public DistributeLockAop(RedisLockRegistry redisLockRegistry) { this.redisLockRegistry = redisLockRegistry; } @Around("@annotation(com.example.demo.aop.annotation.DistributeLock)") public Object around(ProceedingJoinPoint joinPoint) throws Throwable { Class<?> clazz = joinPoint.getTarget().getClass(); MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature(); Method method = clazz.getDeclaredMethod(methodSignature.getName(), methodSignature.getParameterTypes()); DistributeLock distributeLock = AnnotationUtils.findAnnotation(method, DistributeLock.class); assert distributeLock != null; String spel = distributeLock.name(); String lockName = generateKeyBySpEL(spel, method, joinPoint.getArgs()); Lock lock = redisLockRegistry.obtain(lockName); if (lock.tryLock()) { LOGGER.info("DistributeLock locked Success. key:{}", lockName); return joinPoint.proceed(); } else { LOGGER.error("DistributeLock locked Failure. key:{}", lockName); throw new Exception("Lock failure"); } } public static String generateKeyBySpEL(String spELString, Method method, Object[] args) { String[] paramNames = discoverer.getParameterNames(method); Expression expression = parser.parseExpression(spELString); EvaluationContext context = new StandardEvaluationContext(); for (int i = 0; i < args.length; i++) { assert paramNames != null; context.setVariable(paramNames[i], args[i]); } return Objects.requireNonNull(expression.getValue(context)).toString(); } }
package com.example.demo.base; import com.example.demo.aop.annotation.DistributeLock; import org.springframework.stereotype.Service; @Service public class SomeService { @DistributeLock(name = "'lock:' + #something.name") public void doSomething(Something something) { } }
package com.example.demo.base; public class Something { private String name; public String getName() { return name; } public void setName(String name) { this.name = name; } }
package com.example.demo; import com.example.demo.base.Something; import com.example.demo.base.SomeService; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.context.ConfigurableApplicationContext; @SpringBootApplication public class DemoApplication { public static void main(String[] args) { ConfigurableApplicationContext context = SpringApplication.run(DemoApplication.class, args); SomeService someService = context.getBean("someService", SomeService.class); Something something = new Something(); something.setName("gogogo"); someService.doSomething(something); } }
. ____ _ __ _ _
/\ / ___'_ __ _ _(_)_ __ __ _
( ( )___ | '_ | '_| | '_ / _` |
\/ ___)| |_)| | | | | || (_| | ) ) ) )
' |____| .__|_| |_|_| |___, | / / / /
=========|_|==============|___/=/_/_/_/
:: Spring Boot :: (v2.4.4)2021-03-25 16:27:49.638 INFO 10492 --- [ main] com.example.demo.DemoApplication : Starting DemoApplication using Java 1.8.0_141 on P80320948 with PID 10492 (D:workspacedemotargetclasses started by 80320948 in D:workspacedemo)
2021-03-25 16:27:49.641 INFO 10492 --- [ main] com.example.demo.DemoApplication : No active profile set, falling back to default profiles: default
2021-03-25 16:27:50.006 INFO 10492 --- [ main] .s.d.r.c.RepositoryConfigurationDelegate : Multiple Spring Data modules found, entering strict repository configuration mode!
2021-03-25 16:27:50.008 INFO 10492 --- [ main] .s.d.r.c.RepositoryConfigurationDelegate : Bootstrapping Spring Data Redis repositories in DEFAULT mode.
2021-03-25 16:27:50.028 INFO 10492 --- [ main] .s.d.r.c.RepositoryConfigurationDelegate : Finished Spring Data repository scanning in 6 ms. Found 0 Redis repository interfaces.
2021-03-25 16:27:50.144 INFO 10492 --- [ main] faultConfiguringBeanFactoryPostProcessor : No bean named 'errorChannel' has been explicitly defined. Therefore, a default PublishSubscribeChannel will be created.
2021-03-25 16:27:50.153 INFO 10492 --- [ main] faultConfiguringBeanFactoryPostProcessor : No bean named 'taskScheduler' has been explicitly defined. Therefore, a default ThreadPoolTaskScheduler will be created.
2021-03-25 16:27:50.156 INFO 10492 --- [ main] faultConfiguringBeanFactoryPostProcessor : No bean named 'integrationHeaderChannelRegistry' has been explicitly defined. Therefore, a default DefaultHeaderChannelRegistry will be created.
2021-03-25 16:27:50.253 INFO 10492 --- [ main] trationDelegate$BeanPostProcessorChecker : Bean 'org.springframework.integration.config.IntegrationManagementConfiguration' of type [org.springframework.integration.config.IntegrationManagementConfiguration] is not eligible for getting processed by all BeanPostProcessors (for example: not eligible for auto-proxying)
2021-03-25 16:27:50.329 INFO 10492 --- [ main] trationDelegate$BeanPostProcessorChecker : Bean 'integrationChannelResolver' of type [org.springframework.integration.support.channel.BeanFactoryChannelResolver] is not eligible for getting processed by all BeanPostProcessors (for example: not eligible for auto-proxying)
2021-03-25 16:27:50.330 INFO 10492 --- [ main] trationDelegate$BeanPostProcessorChecker : Bean 'integrationDisposableAutoCreatedBeans' of type [org.springframework.integration.config.annotation.Disposables] is not eligible for getting processed by all BeanPostProcessors (for example: not eligible for auto-proxying)
2021-03-25 16:27:50.832 INFO 10492 --- [ main] o.s.s.c.ThreadPoolTaskScheduler : Initializing ExecutorService 'taskScheduler'
2021-03-25 16:27:50.872 INFO 10492 --- [ main] o.s.i.endpoint.EventDrivenConsumer : Adding {logging-channel-adapter:_org.springframework.integration.errorLogger} as a subscriber to the 'errorChannel' channel
2021-03-25 16:27:50.872 INFO 10492 --- [ main] o.s.i.channel.PublishSubscribeChannel : Channel 'demo.errorChannel' has 1 subscriber(s).
2021-03-25 16:27:50.872 INFO 10492 --- [ main] o.s.i.endpoint.EventDrivenConsumer : started bean '_org.springframework.integration.errorLogger'
2021-03-25 16:27:50.878 INFO 10492 --- [ main] com.example.demo.DemoApplication : Started DemoApplication in 1.756 seconds (JVM running for 2.569)
2021-03-25 16:27:51.545 INFO 10492 --- [ main] com.example.demo.aop.DistributeLockAop : DistributeLock locked Success. key:lock:gogogo
分散式鎖的實現有兩種方法
為了方便分散式鎖的使用, 基於註解的方式抽取成公用元件
DisLock註解
/** * 分散式鎖的註解, 通過指定key作為分散式鎖的key * * @author wang.js on 2019/1/29. * @version 1.0 */ @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.METHOD) public @interface DisLock { /** * 分散式鎖的key * * @return */ String key(); /** * 分散式鎖用的業務場景id * * @return */ String biz(); /** * 過期時間, 預設是5秒 * 單位是秒 * * @return */ int expireTime() default 5; }
處理DisLock的切面
/** * 處理@DisLock註解的切面 * * @author wang.js on 2019/1/29. * @version 1.0 */ @Aspect @Order(value = 1) @Component public class DisLockAspect { @Resource private DisLockUtil disLockUtil; private static final int MIN_EXPIRE_TIME = 3; @Around(value = "@annotation(disLock)") public Object execute(ProceedingJoinPoint proceedingJoinPoint, DisLock disLock) throws Throwable { int expireTIme = disLock.expireTime() < MIN_EXPIRE_TIME ? MIN_EXPIRE_TIME : disLock.expireTime(); String disKey = CacheKeyParser.parse(proceedingJoinPoint, disLock.key(), disLock.biz()); boolean lock = disLockUtil.lock(disKey, expireTIme); int count = 1; while (!lock && count < MIN_EXPIRE_TIME) { lock = disLockUtil.lock(disKey, expireTIme); count++; TimeUnit.SECONDS.sleep(1); } Object proceed; if (lock) { // 允許查詢 try { proceed = proceedingJoinPoint.proceed(); } finally { // 刪除分散式鎖 disLockUtil.unlock(disKey, false); } } else { throw new CustomException(ErrorCodeEnum.DUPLICATE_REQUEST.getMessage()); } return proceed; } }
redis的設定
/** * @author wang.js * @date 2018/12/17 * @copyright yougou.com */ @Configuration public class RedisConfig { @Value("${spring.redis.host}") private String host; @Value("${spring.redis.port:6379}") private Integer port; @Bean public JedisPool jedisPool() { //1.設定連線池的設定物件 JedisPoolConfig config = new JedisPoolConfig(); //設定池中最大連線數 config.setMaxTotal(50); //設定空閒時池中保有的最大連線數 config.setMaxIdle(10); config.setMaxWaitMillis(3000L); config.setTestOnBorrow(true); //2.設定連線池物件 return new JedisPool(config,host,port); } }
/** * redis分散式鎖的實現 * * @author wang.js * @date 2018/12/18 * @copyright yougou.com */ @Component public class DisLockUtil { @Resource private JedisPool jedisPool; private static final int DEFAULT_EXPIRE_TIME = 5; private static final Long RELEASE_SUCCESS = 1L; private static final String LOCK_SUCCESS = "OK"; private static final String SET_IF_NOT_EXIST = "NX"; private static final String SET_WITH_EXPIRE_TIME = "PX"; /** * 嘗試獲取分散式鎖 * * @param jedis Redis使用者端 * @param lockKey 鎖 * @param requestId 請求標識 * @param expireTime 超期時間 * @return 是否獲取成功 */ public static boolean tryGetDistributedLock(Jedis jedis, String lockKey, String requestId, int expireTime) { String result = jedis.set(lockKey, requestId, SET_IF_NOT_EXIST, SET_WITH_EXPIRE_TIME, expireTime); if (LOCK_SUCCESS.equals(result)) { return true; } return false; } /** * 釋放分散式鎖 * * @param jedis Redis使用者端 * @param lockKey 鎖 * @param requestId 請求標識 * @return 是否釋放成功 */ public static boolean releaseDistributedLock(Jedis jedis, String lockKey, String requestId) { String script = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end"; Object result = jedis.eval(script, Collections.singletonList(lockKey), Collections.singletonList(requestId)); if (RELEASE_SUCCESS.equals(result)) { return true; } return false; } /** * 釋放鎖 * * @param key * @return */ public final boolean unlock(String key, boolean needCheck) { boolean result = false; Jedis jedis = jedisPool.getResource(); try { if (needCheck) { String expireTimeCache = jedis.get(key); // 判斷鎖是否過期了 if (StringUtils.isBlank(expireTimeCache)) { result = true; } if (System.currentTimeMillis() - Long.parseLong(expireTimeCache) > 0) { // 直接刪除 jedis.del(key); result = true; } } else { jedis.del(key); } } finally { jedis.close(); } return result; } /** * 獲取分散式鎖 * * @param key * @param expireSecond * @return */ public final boolean lock(String key, int expireSecond) { if (StringUtils.isBlank(key)) { throw new RuntimeException("傳入的key為空"); } expireSecond = expireSecond == 0 ? DEFAULT_EXPIRE_TIME : expireSecond; // 過期的時候的時間戳 long expireTime = System.currentTimeMillis() + expireSecond * 1000 + 1; boolean setResult = false; Jedis jedis = jedisPool.getResource(); try { if (jedis.setnx(key, String.valueOf(expireTime)) == 1) { // 說明加鎖成功 setResult = true; } if (jedis.ttl(key) < 0) { jedis.expire(key, expireSecond); } if (setResult) { return true; } String expireTimeCache = jedis.get(key); System.out.println(expireTimeCache + "====" + jedis.ttl(key) + ", now:" + System.currentTimeMillis()); // 判斷鎖是否過期了 if (StringUtils.isNotBlank(expireTimeCache) && System.currentTimeMillis() - Long.parseLong(expireTimeCache) > 0) { String oldExpireTime = jedis.getSet(key, String.valueOf(expireTime)); if (StringUtils.isNotBlank(oldExpireTime) && oldExpireTime.equals(String.valueOf(expireTime))) { jedis.expire(key, expireSecond); setResult = true; } } } finally { jedis.close(); } return setResult; } }
實現分散式鎖的關鍵是對key的設定, 需要獲取實際的引數來設定分散式鎖, 這裡自定義瞭解析器
/** * cache key 的解析器 * * @author wang.js on 2019/2/27. * @version 1.0 */ public class CacheKeyParser { /** * 解析快取的key * * @param proceedingJoinPoint 切面 * @param cacheKey 快取的key * @param biz 業務 * @return String * @throws IllegalAccessException 異常 */ public static String parse(ProceedingJoinPoint proceedingJoinPoint, String cacheKey, String biz) throws IllegalAccessException { // 解析實際引數的key String key = cacheKey.replace("#", ""); StringTokenizer stringTokenizer = new StringTokenizer(key, "."); Map<String, Object> nameAndValue = getNameAndValue(proceedingJoinPoint); Object actualKey = null; while (stringTokenizer.hasMoreTokens()) { if (actualKey == null) { actualKey = nameAndValue.get(stringTokenizer.nextToken()); } else { actualKey = getPropValue(actualKey, stringTokenizer.nextToken()); } } return biz + actualKey; } /** * 獲取引數Map集合 * * @param joinPoint 切面 * @return Map<String, Object> */ private static Map<String, Object> getNameAndValue(ProceedingJoinPoint joinPoint) { Object[] paramValues = joinPoint.getArgs(); String[] paramNames = ((CodeSignature) joinPoint.getSignature()).getParameterNames(); Map<String, Object> param = new HashMap<>(paramNames.length); for (int i = 0; i < paramNames.length; i++) { param.put(paramNames[i], paramValues[i]); } return param; } /** * 獲取指定引數名的引數值 * * @param obj * @param propName * @return * @throws IllegalAccessException */ public static Object getPropValue(Object obj, String propName) throws IllegalAccessException { Field[] fields = obj.getClass().getDeclaredFields(); for (Field f : fields) { if (f.getName().equals(propName)) { //在反射時能存取私有變數 f.setAccessible(true); return f.get(obj); } } return null; } }
ErrorCodeEnum
public enum ErrorCodeEnum { SUCCESS("查詢成功", "200"), SERVER_ERROR("伺服器異常", "500"), SECKILL_END("秒殺活動已結束", "250"), GOODS_KILLED("秒殺成功", "502"), ERROR_SIGN("簽名不合法", "260"), UPDATE_SUCCESS("更新成功", "0"), SAVE_SUCCESS("儲存成功", "0"), UPDATE_FAIL("更新失敗", "256"), EMPTY_PARAM("引數為空", "257"), SAVE_ERROR("儲存失敗", "262"), SERVER_TIMEOUT("呼叫超時", "501"), USER_NOT_FOUND("找不到使用者", "502"), COUPON_NOT_FOUND("找不到優惠券", "503"), DUPLICATE("出現重複", "504"), USER_STATUS_ABNORMAL("使用者狀態異常", "505"), NO_TOKEN("無token,請重新登入", "506"), ERROR_TOKEN("token不合法", "507"), EMPTY_RESULT("暫無資料", "508"), DUPLICATE_REQUEST("重複請求", "509"), ; /** * 定義的message */ private String message; /** * 定義的錯誤碼 */ private String errCode; ErrorCodeEnum(String message, String errCode) { this.message = message; this.errCode = errCode; } public String getMessage() { return message; } protected void setMessage(String message) { this.message = message; } public String getErrCode() { return errCode; } protected void setErrCode(String errCode) { this.errCode = errCode; } }
自定義異常CustomException
/** * @author Eric on 2018/12/24. * @version 1.0 */ @Data @NoArgsConstructor @AllArgsConstructor @Accessors(chain = true) @EqualsAndHashCode(callSuper = true) public class CustomException extends RuntimeException { private String message; }
組態檔
spring: redis: host: mini7 port: 6379
定義一個方法, 加上@RedisCache註解, cacheKey的值必須是#實際引數名.屬性名的格式, 如果想要成其他的格式可以修改CacheKeyParser中的parse方法
@DisLock(key = "#id", biz = CommonBizConstant.SECOND_KILL) @Override public String testRedisCache(String id) { LOGGER.info("呼叫方法獲取值"); return "大傻逼"; }
在springboot啟動類上加上@ComponentScan({“com.eric”})
/** * @author Eric on 2019/1/26. * @version 1.0 */ @SpringBootApplication @MapperScan("com.eric.base.data.dao") @ComponentScan({"com.eric"}) @EnableFeignClients @EnableDiscoveryClient public class BaseDataApplication { public static void main(String[] args) { SpringApplication.run(BaseDataApplication.class, args); } }
寫個測試類呼叫上面的方法
/** * 基礎資料 * * @author wang.js on 2019/2/27. * @version 1.0 */ @SpringBootTest @RunWith(SpringRunner.class) public class BaseDataTest { @Resource private SysDictService sysDictService; @Test public void t1() { for (int i = 0; i < 100; i++) { sysDictService.testRedisCache("1"); } } }
以上為個人經驗,希望能給大家一個參考,也希望大家多多支援it145.com。
相關文章
<em>Mac</em>Book项目 2009年学校开始实施<em>Mac</em>Book项目,所有师生配备一本<em>Mac</em>Book,并同步更新了校园无线网络。学校每周进行电脑技术更新,每月发送技术支持资料,极大改变了教学及学习方式。因此2011
2021-06-01 09:32:01
综合看Anker超能充系列的性价比很高,并且与不仅和iPhone12/苹果<em>Mac</em>Book很配,而且适合多设备充电需求的日常使用或差旅场景,不管是安卓还是Switch同样也能用得上它,希望这次分享能给准备购入充电器的小伙伴们有所
2021-06-01 09:31:42
除了L4WUDU与吴亦凡已经多次共事,成为了明面上的厂牌成员,吴亦凡还曾带领20XXCLUB全队参加2020年的一场音乐节,这也是20XXCLUB首次全员合照,王嗣尧Turbo、陈彦希Regi、<em>Mac</em> Ova Seas、林渝植等人全部出场。然而让
2021-06-01 09:31:34
目前应用IPFS的机构:1 谷歌<em>浏览器</em>支持IPFS分布式协议 2 万维网 (历史档案博物馆)数据库 3 火狐<em>浏览器</em>支持 IPFS分布式协议 4 EOS 等数字货币数据存储 5 美国国会图书馆,历史资料永久保存在 IPFS 6 加
2021-06-01 09:31:24
开拓者的车机是兼容苹果和<em>安卓</em>,虽然我不怎么用,但确实兼顾了我家人的很多需求:副驾的门板还配有解锁开关,有的时候老婆开车,下车的时候偶尔会忘记解锁,我在副驾驶可以自己开门:第二排设计很好,不仅配置了一个很大的
2021-06-01 09:30:48
不仅是<em>安卓</em>手机,苹果手机的降价力度也是前所未有了,iPhone12也“跳水价”了,发布价是6799元,如今已经跌至5308元,降价幅度超过1400元,最新定价确认了。iPhone12是苹果首款5G手机,同时也是全球首款5nm芯片的智能机,它
2021-06-01 09:30:45