首页 > 数据库 >Spring Boot 集成 Redisson分布式锁(注解版)

Spring Boot 集成 Redisson分布式锁(注解版)

时间:2024-02-07 17:45:39浏览次数:26  
标签:lock Redisson return String Spring Boot param key distributedLock

转载自:https://blog.csdn.net/Ascend1977/article/details/131126047

        Redisson 是一种基于 Redis 的 Java 驻留集群的分布式对象和服务库,可以为我们提供丰富的分布式锁和线程安全集合的实现。在 Spring Boot 应用程序中使用 Redisson 可以方便地实现分布式应用程序的某些方面,例如分布式锁、分布式集合、分布式事件发布和订阅等。本篇是一个使用 Redisson 实现分布式锁的详细示例,在这个示例中,我们定义了DistributedLock注解,它可以标注在方法上,配合DistributedLockAspect切面以及IDistributedLock分布式锁封装的接口,来实现redisson 分布式锁的 API 调用。

Spring Boot 集成 Redisson

 1、在 pom.xml 文件中添加 Redisson 的相关依赖

  1.   <dependency>
  2.   <groupId>org.redisson</groupId>
  3.   <artifactId>redisson-spring-boot-starter</artifactId>
  4.   <version>3.16.1</version>
  5.   </dependency>

2、在 application.yml 中配置 Redisson单机模式 的连接信息和相关参数

  1.   spring:
  2.   redis:
  3.   host: localhost
  4.   port: 6379
  5.   password: null
  6.   redisson:
  7.   codec: org.redisson.codec.JsonJacksonCodec
  8.   threads: 4
  9.   netty:
  10.   threads: 4
  11.   single-server-config:
  12.   address: "redis://localhost:6379"
  13.   password: null
  14.   database: 0

3、Redission还支持主从、集群、哨兵配置

  1.   //主从模式
  2.   spring:
  3.   redis:
  4.   sentinel:
  5.   master: my-master
  6.   nodes: localhost:26379,localhost:26389
  7.   password: your_password
  8.   redisson:
  9.   master-slave-config:
  10.   master-address: "redis://localhost:6379"
  11.   slave-addresses: "redis://localhost:6380,redis://localhost:6381"
  12.   password: ${spring.redis.password}
  13.    
  14.   // 集群模式
  15.   spring:
  16.   redis:
  17.   cluster:
  18.   nodes: localhost:6379,localhost:6380,localhost:6381,localhost:6382,localhost:6383,localhost:6384
  19.   password: your_password
  20.   redisson:
  21.   cluster-config:
  22.   node-addresses: "redis://localhost:6379,redis://localhost:6380,redis://localhost:6381,redis://localhost:6382,redis://localhost:6383,redis://localhost:6384"
  23.   password: ${spring.redis.password}
  24.    
  25.   // 哨兵模式
  26.   spring:
  27.   redis:
  28.   sentinel:
  29.   master: my-master
  30.   nodes: localhost:26379,localhost:26389
  31.   password: your_password
  32.   redisson:
  33.   sentinel-config:
  34.   master-name: my-master
  35.   sentinel-addresses: "redis://localhost:26379,redis://localhost:26380,redis://localhost:26381"
  36.   password: ${spring.redis.password}
  37.    

 本地封装Redisson 分布式锁

1、定义IDistributedLock分布式锁接口

  1.   public interface IDistributedLock {
  2.   /**
  3.   * 获取锁,默认30秒失效,失败一直等待直到获取锁
  4.   *
  5.   * @param key 锁的key
  6.   * @return 锁对象
  7.   */
  8.   ILock lock(String key);
  9.    
  10.   /**
  11.   * 获取锁,失败一直等待直到获取锁
  12.   *
  13.   * @param key 锁的key
  14.   * @param lockTime 加锁的时间,超过这个时间后锁便自动解锁; 如果lockTime为-1,则保持锁定直到显式解锁
  15.   * @param unit {@code lockTime} 参数的时间单位
  16.   * @param fair 是否公平锁
  17.   * @return 锁对象
  18.   */
  19.   ILock lock(String key, long lockTime, TimeUnit unit, boolean fair);
  20.    
  21.   /**
  22.   * 尝试获取锁,30秒获取不到超时异常,锁默认30秒失效
  23.   *
  24.   * @param key 锁的key
  25.   * @param tryTime 获取锁的最大尝试时间
  26.   * @return
  27.   * @throws Exception
  28.   */
  29.   ILock tryLock(String key, long tryTime) throws Exception;
  30.    
  31.   /**
  32.   * 尝试获取锁,获取不到超时异常
  33.   *
  34.   * @param key 锁的key
  35.   * @param tryTime 获取锁的最大尝试时间
  36.   * @param lockTime 加锁的时间
  37.   * @param unit {@code tryTime @code lockTime} 参数的时间单位
  38.   * @param fair 是否公平锁
  39.   * @return
  40.   * @throws Exception
  41.   */
  42.   ILock tryLock(String key, long tryTime, long lockTime, TimeUnit unit, boolean fair) throws Exception;
  43.    
  44.   /**
  45.   * 解锁
  46.   *
  47.   * @param lock
  48.   * @throws Exception
  49.   */
  50.   void unLock(Object lock);
  51.    
  52.    
  53.   /**
  54.   * 释放锁
  55.   *
  56.   * @param lock
  57.   * @throws Exception
  58.   */
  59.   default void unLock(ILock lock) {
  60.   if (lock != null) {
  61.   unLock(lock.getLock());
  62.   }
  63.   }
  64.    
  65.    
  66.   }

2、IDistributedLock实现类

  1.   @Slf4j
  2.   @Component
  3.   public class RedissonDistributedLock implements IDistributedLock {
  4.    
  5.   @Resource
  6.   private RedissonClient redissonClient;
  7.   /**
  8.   * 统一前缀
  9.   */
  10.   @Value("${redisson.lock.prefix:bi:distributed:lock}")
  11.   private String prefix;
  12.    
  13.   @Override
  14.   public ILock lock(String key) {
  15.   return this.lock(key, 0L, TimeUnit.SECONDS, false);
  16.   }
  17.    
  18.   @Override
  19.   public ILock lock(String key, long lockTime, TimeUnit unit, boolean fair) {
  20.   RLock lock = getLock(key, fair);
  21.   // 获取锁,失败一直等待,直到获取锁,不支持自动续期
  22.   if (lockTime > 0L) {
  23.   lock.lock(lockTime, unit);
  24.   } else {
  25.   // 具有Watch Dog 自动延期机制 默认续30s 每隔30/3=10 秒续到30s
  26.   lock.lock();
  27.   }
  28.   return new ILock(lock, this);
  29.   }
  30.    
  31.   @Override
  32.   public ILock tryLock(String key, long tryTime) throws Exception {
  33.   return this.tryLock(key, tryTime, 0L, TimeUnit.SECONDS, false);
  34.   }
  35.    
  36.   @Override
  37.   public ILock tryLock(String key, long tryTime, long lockTime, TimeUnit unit, boolean fair)
  38.   throws Exception {
  39.   RLock lock = getLock(key, fair);
  40.   boolean lockAcquired;
  41.   // 尝试获取锁,获取不到超时异常,不支持自动续期
  42.   if (lockTime > 0L) {
  43.   lockAcquired = lock.tryLock(tryTime, lockTime, unit);
  44.   } else {
  45.   // 具有Watch Dog 自动延期机制 默认续30s 每隔30/3=10 秒续到30s
  46.   lockAcquired = lock.tryLock(tryTime, unit);
  47.   }
  48.   if (lockAcquired) {
  49.   return new ILock(lock, this);
  50.   }
  51.   return null;
  52.   }
  53.    
  54.   /**
  55.   * 获取锁
  56.   *
  57.   * @param key
  58.   * @param fair
  59.   * @return
  60.   */
  61.   private RLock getLock(String key, boolean fair) {
  62.   RLock lock;
  63.   String lockKey = prefix + ":" + key;
  64.   if (fair) {
  65.   // 获取公平锁
  66.   lock = redissonClient.getFairLock(lockKey);
  67.   } else {
  68.   // 获取普通锁
  69.   lock = redissonClient.getLock(lockKey);
  70.   }
  71.   return lock;
  72.   }
  73.    
  74.   @Override
  75.   public void unLock(Object lock) {
  76.   if (!(lock instanceof RLock)) {
  77.   throw new IllegalArgumentException("Invalid lock object");
  78.   }
  79.   RLock rLock = (RLock) lock;
  80.   if (rLock.isLocked()) {
  81.   try {
  82.   rLock.unlock();
  83.   } catch (IllegalMonitorStateException e) {
  84.   log.error("释放分布式锁异常", e);
  85.   }
  86.   }
  87.   }
  88.   }

 3、定义ILock锁对象

  1.   import lombok.AllArgsConstructor;
  2.   import lombok.Getter;
  3.    
  4.   import java.util.Objects;
  5.    
  6.   /**
  7.   * <p>
  8.   * RedissonLock 包装的锁对象 实现AutoCloseable接口,在java7的try(with resource)语法,不用显示调用close方法
  9.   * </p>
  10.    
  11.   * @since 2023-06-08 16:57
  12.   */
  13.   @AllArgsConstructor
  14.   public class ILock implements AutoCloseable {
  15.   /**
  16.   * 持有的锁对象
  17.   */
  18.   @Getter
  19.   private Object lock;
  20.   /**
  21.   * 分布式锁接口
  22.   */
  23.   @Getter
  24.   private IDistributedLock distributedLock;
  25.    
  26.   @Override
  27.   public void close() throws Exception {
  28.   if(Objects.nonNull(lock)){
  29.   distributedLock.unLock(lock);
  30.   }
  31.   }
  32.   }

4、注入IDistributedLock接口使用示例

  1.   // 定义接口
  2.   public interface IProductSkuSupplierMeasureService {
  3.   /**
  4.   * 保存SKU供应商供货信息
  5.   *
  6.   * @param dto
  7.   * @return
  8.   *
  9.   Boolean saveSupplierInfo(ProductSkuSupplierInfoDTO dto);
  10.    
  11.   /**
  12.   * 编辑SKU供应商供货信息
  13.   *
  14.   * @param dto
  15.   * @return
  16.   */
  17.   Boolean editSupplierInfo(ProductSkuSupplierInfoDTO dto);
  18.   }
  19.    
  20.    

手动释放锁示例 

  1.   // 实现类
  2.   @Service
  3.   public class ProductSkuSupplierMeasureServiceImpl
  4.   implements IProductSkuSupplierMeasureService {
  5.    
  6.   @Resource
  7.   private IDistributedLock distributedLock;
  8.    
  9.   @Override
  10.   @Transactional(rollbackFor = Exception.class)
  11.   public Boolean saveSupplierInfo(ProductSkuSupplierInfoDTO dto) {
  12.   // 手动释放锁
  13.   String sku = dto.getSku();
  14.   ILock lock = null;
  15.   try {
  16.   lock = distributedLock.lock(dto.getSku(),10L, TimeUnit.SECONDS, false);
  17.   if (Objects.isNull(lock)) {
  18.   throw new BusinessException("Duplicate request for method still in process");
  19.   }
  20.   // 业务代码
  21.   }catch (BusinessException e) {
  22.   throw new BusinessException(e.getMessage());
  23.   } catch (Exception e) {
  24.   log.error("保存异常", e);
  25.   throw new BusinessException (e.getMessage());
  26.   } finally {
  27.   if (Objects.nonNull(lock)) {
  28.   distributedLock.unLock(lock);
  29.   }
  30.   }
  31.   return Boolean.TRUE;
  32.   }
  33.    
  34.   }

 使用try-with-resources 语法糖自动释放锁

  1.   // 实现类
  2.   @Service
  3.   public class ProductSkuSupplierMeasureServiceImpl
  4.   implements IProductSkuSupplierMeasureService {
  5.    
  6.   @Resource
  7.   private IDistributedLock distributedLock;
  8.    
  9.    
  10.   @Override
  11.   @Transactional(rollbackFor = Exception.class)
  12.   public Boolean editSupplierInfo(ProductSkuSupplierInfoDTO dto) {
  13.    
  14.   String sku = dto.getSku();
  15.   // try-with-resources 语法糖自动释放锁
  16.   try(ILock lock = distributedLock.lock(dto.getSku(),10L, TimeUnit.SECONDS, false)) {
  17.   if(Objects.isNull(lock)){
  18.   throw new BusinessException ("Duplicate request for method still in process");
  19.   }
  20.    
  21.   // 业务代码
  22.   }catch (BusinessException e) {
  23.   throw new BusinessException (e.getMessage());
  24.   } catch (Exception e) {
  25.   log.error("修改异常", e);
  26.   throw new BusinessException ("修改异常");
  27.   }
  28.   return Boolean.TRUE;
  29.   }
  30.   }

5、使用AOP切面实现分布式锁的绑定

  定义DistributedLock注解

  1.   @Target({ElementType.METHOD})
  2.   @Retention(RetentionPolicy.RUNTIME)
  3.   @Documented
  4.   public @interface DistributedLock {
  5.    
  6.   /**
  7.   * 保证业务接口的key的唯一性,否则失去了分布式锁的意义 锁key
  8.   * 支持使用spEl表达式
  9.   */
  10.   String key();
  11.    
  12.   /**
  13.   * 保证业务接口的key的唯一性,否则失去了分布式锁的意义 锁key 前缀
  14.   */
  15.   String keyPrefix() default "";
  16.    
  17.   /**
  18.   * 是否在等待时间内获取锁,如果在等待时间内无法获取到锁,则返回失败
  19.   */
  20.   boolean tryLok() default false;
  21.    
  22.   /**
  23.   * 获取锁的最大尝试时间 ,会尝试tryTime时间获取锁,在该时间内获取成功则返回,否则抛出获取锁超时异常,tryLok=true时,该值必须大于0。
  24.   *
  25.   */
  26.   long tryTime() default 0;
  27.    
  28.   /**
  29.   * 加锁的时间,超过这个时间后锁便自动解锁
  30.   */
  31.   long lockTime() default 30;
  32.    
  33.   /**
  34.   * tryTime 和 lockTime的时间单位
  35.   */
  36.   TimeUnit unit() default TimeUnit.SECONDS;
  37.    
  38.   /**
  39.   * 是否公平锁,false:非公平锁,true:公平锁
  40.   */
  41.   boolean fair() default false;
  42.   }

 定义DistributedLockAspect Lock切面

  1.   @Aspect
  2.   @Slf4j
  3.   public class DistributedLockAspect {
  4.    
  5.   @Resource
  6.   private IDistributedLock distributedLock;
  7.    
  8.   /**
  9.   * SpEL表达式解析
  10.   */
  11.   private SpelExpressionParser spelExpressionParser = new SpelExpressionParser();
  12.    
  13.   /**
  14.   * 用于获取方法参数名字
  15.   */
  16.   private DefaultParameterNameDiscoverer nameDiscoverer = new DefaultParameterNameDiscoverer();
  17.    
  18.   @Pointcut("@annotation(com.yt.bi.common.redis.distributedlok.annotation.DistributedLock)")
  19.   public void distributorLock() {
  20.   }
  21.    
  22.   @Around("distributorLock()")
  23.   public Object around(ProceedingJoinPoint pjp) throws Throwable {
  24.   // 获取DistributedLock
  25.   DistributedLock distributedLock = this.getDistributedLock(pjp);
  26.   // 获取 lockKey
  27.   String lockKey = this.getLockKey(pjp, distributedLock);
  28.   ILock lockObj = null;
  29.   try {
  30.   // 加锁,tryLok = true,并且tryTime > 0时,尝试获取锁,获取不到超时异常
  31.   if (distributedLock.tryLok()) {
  32.   if(distributedLock.tryTime() <= 0){
  33.   throw new IdempotencyException("tryTime must be greater than 0");
  34.   }
  35.   lockObj = this.distributedLock.tryLock(lockKey, distributedLock.tryTime(), distributedLock.lockTime(), distributedLock.unit(), distributedLock.fair());
  36.   } else {
  37.   lockObj = this.distributedLock.lock(lockKey, distributedLock.lockTime(), distributedLock.unit(), distributedLock.fair());
  38.   }
  39.    
  40.   if (Objects.isNull(lockObj)) {
  41.   throw new IdempotencyException("Duplicate request for method still in process");
  42.   }
  43.    
  44.   return pjp.proceed();
  45.   } catch (Exception e) {
  46.   throw e;
  47.   } finally {
  48.   // 解锁
  49.   this.unLock(lockObj);
  50.   }
  51.   }
  52.    
  53.   /**
  54.   * @param pjp
  55.   * @return
  56.   * @throws NoSuchMethodException
  57.   */
  58.   private DistributedLock getDistributedLock(ProceedingJoinPoint pjp) throws NoSuchMethodException {
  59.   String methodName = pjp.getSignature().getName();
  60.   Class clazz = pjp.getTarget().getClass();
  61.   Class<?>[] par = ((MethodSignature) pjp.getSignature()).getParameterTypes();
  62.   Method lockMethod = clazz.getMethod(methodName, par);
  63.   DistributedLock distributedLock = lockMethod.getAnnotation(DistributedLock.class);
  64.   return distributedLock;
  65.   }
  66.    
  67.   /**
  68.   * 解锁
  69.   *
  70.   * @param lockObj
  71.   */
  72.   private void unLock(ILock lockObj) {
  73.   if (Objects.isNull(lockObj)) {
  74.   return;
  75.   }
  76.    
  77.   try {
  78.   this.distributedLock.unLock(lockObj);
  79.   } catch (Exception e) {
  80.   log.error("分布式锁解锁异常", e);
  81.   }
  82.   }
  83.    
  84.   /**
  85.   * 获取 lockKey
  86.   *
  87.   * @param pjp
  88.   * @param distributedLock
  89.   * @return
  90.   */
  91.   private String getLockKey(ProceedingJoinPoint pjp, DistributedLock distributedLock) {
  92.   String lockKey = distributedLock.key();
  93.   String keyPrefix = distributedLock.keyPrefix();
  94.   if (StringUtils.isBlank(lockKey)) {
  95.   throw new IdempotencyException("Lok key cannot be empty");
  96.   }
  97.   if (lockKey.contains("#")) {
  98.   this.checkSpEL(lockKey);
  99.   MethodSignature methodSignature = (MethodSignature) pjp.getSignature();
  100.   // 获取方法参数值
  101.   Object[] args = pjp.getArgs();
  102.   lockKey = getValBySpEL(lockKey, methodSignature, args);
  103.   }
  104.   lockKey = StringUtils.isBlank(keyPrefix) ? lockKey : keyPrefix + lockKey;
  105.   return lockKey;
  106.   }
  107.    
  108.   /**
  109.   * 解析spEL表达式
  110.   *
  111.   * @param spEL
  112.   * @param methodSignature
  113.   * @param args
  114.   * @return
  115.   */
  116.   private String getValBySpEL(String spEL, MethodSignature methodSignature, Object[] args) {
  117.   // 获取方法形参名数组
  118.   String[] paramNames = nameDiscoverer.getParameterNames(methodSignature.getMethod());
  119.   if (paramNames == null || paramNames.length < 1) {
  120.   throw new IdempotencyException("Lok key cannot be empty");
  121.   }
  122.   Expression expression = spelExpressionParser.parseExpression(spEL);
  123.   // spring的表达式上下文对象
  124.   EvaluationContext context = new StandardEvaluationContext();
  125.   // 给上下文赋值
  126.   for (int i = 0; i < args.length; i++) {
  127.   context.setVariable(paramNames[i], args[i]);
  128.   }
  129.   Object value = expression.getValue(context);
  130.   if (value == null) {
  131.   throw new IdempotencyException("The parameter value cannot be null");
  132.   }
  133.   return value.toString();
  134.   }
  135.    
  136.   /**
  137.   * SpEL 表达式校验
  138.   *
  139.   * @param spEL
  140.   * @return
  141.   */
  142.   private void checkSpEL(String spEL) {
  143.   try {
  144.   ExpressionParser parser = new SpelExpressionParser();
  145.   parser.parseExpression(spEL, new TemplateParserContext());
  146.   } catch (Exception e) {
  147.   log.error("spEL表达式解析异常", e);
  148.   throw new IdempotencyException("Invalid SpEL expression [" + spEL + "]");
  149.   }
  150.   }
  151.   }

定义分布式锁注解版启动元注解

  1.   @Target(ElementType.TYPE)
  2.   @Retention(RetentionPolicy.RUNTIME)
  3.   @Documented
  4.   @Import({DistributedLockAspect.class})
  5.   public @interface EnableDistributedLock {
  6.   }

6、AOP切面实现分布式锁的绑定使用示例

   启动类添加@EnableDistributedLock启用注解支持

  1.   @SpringBootApplication
  2.   @EnableDistributedLock
  3.   public class BiCenterGoodsApplication {
  4.    
  5.   public static void main(String[] args) {
  6.    
  7.   SpringApplication.run(BiCenterGoodsApplication.class, args);
  8.    
  9.   }
  10.   }

@DistributedLock标注需要使用分布式锁的方法 

  1.   @ApiOperation("编辑SKU供应商供货信息")
  2.    
  3.   @PostMapping("/editSupplierInfo")
  4.   //@DistributedLock(key = "#dto.sku + '-' + #dto.skuId", lockTime = 10L, keyPrefix = "sku-")
  5.   @DistributedLock(key = "#dto.sku", lockTime = 10L, keyPrefix = "sku-")
  6.   public R<Boolean> editSupplierInfo(@RequestBody @Validated ProductSkuSupplierInfoDTO dto) {
  7.   return R.ok(productSkuSupplierMeasureService.editSupplierInfo(dto));
  8.   }
#dto.sku是 SpEL表达式。Spring中支持的它都支持的。比如调用静态方法,三目表达式。SpEL 可以使用方法中的任何参数。SpEL表达式参考

从原理到实践,分析 Redis 分布式锁的多种实现方案(一)_Ascend JF的博客-CSDN博客


标签:lock,Redisson,return,String,Spring,Boot,param,key,distributedLock
From: https://www.cnblogs.com/wanghengbin/p/18011134

相关文章

  • Spring Boot项目常用配置整理
    〇、常用地址一、配置文件1.1bootatrap.xml#Spring配置spring:#应用名application:name:data-xx-platform#启动环境profiles:active:@spring.profiles.active@cloud:nacos:#注册中心discovery:server-addr:http://nacos......
  • SpringBoot简介
    1、为什么有SpringBoot?J2EE笨重的开发、繁多的配置、低下的开发效率、复杂的部署流程、第三方技术集成难度大。2、SpringBoot是什么?是一个一站式整合所有应用框架的框架;并且完美整合Spring技术栈。SpringBoot来简化开发,约定大于配置,去繁从简,justrun就能创建一个......
  • Spring 接点、切点、切面、引入、织入、通知 概念
    importcom.github.pagehelper.PageHelper;importorg.aspectj.lang.ProceedingJoinPoint;importorg.aspectj.lang.annotation.Around;importorg.aspectj.lang.annotation.Aspect;importorg.aspectj.lang.annotation.Pointcut;importorg.springframework.context.ann......
  • Spring Boot + Lua = 王炸!
    曾经有一位魔术师,他擅长将SpringBoot和Redis这两个强大的工具结合成一种令人惊叹的组合。他的魔法武器是Redis的Lua脚本。今天,我们将揭开这个魔术师的秘密,探讨如何在SpringBoot项目中使用Lua脚本,以解锁新的可能性和提高性能。如果你一直在寻找提升你的应用程序的方法,那么这篇博......
  • 【Spring】SpringBoot3+SpringBatch5.xの構築
    ■概要  ■POMのXMLの設定<?xmlversion="1.0"encoding="UTF-8"?><projectxmlns="http://maven.apache.org/POM/4.0.0"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation=&qu......
  • SpringBoot 优雅实现超大文件上传,通用方案
    文件上传是一个老生常谈的话题了,在文件相对比较小的情况下,可以直接把文件转化为字节流上传到服务器,但在文件比较大的情况下,用普通的方式进行上传,这可不是一个好的办法,毕竟很少有人会忍受,当文件上传到一半中断后,继续上传却只能重头开始上传,这种让人不爽的体验。那有没有比较好的上传......
  • Bootstrap5 导航组件和面包屑
    Bootstrap5导航组件和面包屑Bootstrap5提供了一种简单快捷的方法来创建基本导航,它提供了非常灵活和优雅的选项卡和Pills等组件。Bootstrap5的所有导航组件,包括选项卡和Pills,都通过基本的 .nav 类共享相同的基本标记和样式。使用Bootstrap5创建基本导航我们可以使用Bootstr......
  • Bootstrap5 导航组件和面包屑
    Bootstrap5导航组件和面包屑Bootstrap5提供了一种简单快捷的方法来创建基本导航,它提供了非常灵活和优雅的选项卡和Pills等组件。Bootstrap5的所有导航组件,包括选项卡和Pills,都通过基本的 .nav 类共享相同的基本标记和样式。使用Bootstrap5创建基本导航我们可以使用Bootstr......
  • Springboot和Vue(2或者3都行)实现Twitter授权登录,并获取用户公开信息-OAuth1.0。
    第一步先申请twitter开发者账号,创建App,我这里没有创建app,当时好像是默认有一个app,twitter官方说,创建一个app需要先删除一个app,我是没有充钱的,不知道充钱和免费使用接口的是不是一样的。第二步在生成CustomerKey以及CustomeSecret,我之后会用到这两个,这写密钥一生成永久有效,除非......
  • 【Spring】- 自动注入注解
    【@Autowired】冷知识:@AutowiredprivateMovieCatalog[]movieCatalogs;//根据类型注入全部的bean对象数组@AutowiredprivateSet<MovieCatalog>movieCatalogs;//根据类型注入全部的bean对象集合@AutowiredprivateMap<String,MovieCatalog>movieCatalogs;//根据类型注......