Springboot2.x AOPはキャッシュロックと分散ロックを実装します

Springboot2.x AOPはキャッシュロックと分散ロックを実装します

[[410877]]

Springboot2.x AOPは重複送信を防ぐためにキャッシュロックと分散ロックを実装しています。

私はバックエンドシステムで長年の経験を持っています。ユーザーがネットワーク状態が悪い状態でフォームを送信すると、送信が繰り返し発生する可能性があります。したがって、フォームが再度送信されないようにする必要があります。

Google の Guave Cache

  1. <依存関係>
  2. <groupId>org.springframework.boot</groupId>
  3. <artifactId>spring-boot-starter-web</artifactId>
  4. </依存関係>
  5. <依存関係>
  6. <groupId>org.springframework.boot</groupId>
  7. <artifactId>spring-boot-starter-aop</artifactId>
  8. </依存関係>
  9. <依存関係>
  10. <groupId>com.google.guava</groupId>
  11. <artifactId>グアバ</artifactId>
  12. <バージョン>21.0</バージョン>
  13. </依存関係>

注釈インターフェース

  1. パッケージ com.ouyue.xiwenapi.annotation;
  2.  
  3. java.lang.annotation.* をインポートします。
  4.  
  5. /**
  6. * @クラス名:${}
  7. * @説明:TODO
  8. * @著者:[email protected]
  9. * @日付
  10. */
  11. @ターゲット(要素タイプ.METHOD)
  12. @保持(保持ポリシー.RUNTIME)
  13. @文書化済み
  14. @継承
  15. パブリック@interface GuaveLock
  16. {
  17.  
  18. 文字列キー()デフォルト  "" ;
  19.  
  20. /**
  21. * 有効期限 TODO guava を使用しているため、現時点ではこのプロパティは無視します。 Redisを統合するために必要
  22. *
  23. * @author フライ
  24. */
  25. int expire()デフォルト5;
  26. }

AOPの応用

  1. パッケージ com.ouyue.xiwenapi.config;
  2.  
  3. com.google.common.cache.Cache をインポートします。
  4. com.google.common.cache.CacheBuilder をインポートします。
  5. com.ouyue.xiwenapi.annotation.GuaveLock をインポートします。
  6. org.aspectj.lang.ProceedingJoinPoint をインポートします。
  7. org.aspectj.lang.annotation.Around をインポートします。
  8. org.aspectj.lang.annotation.Aspect をインポートします。
  9. org.aspectj.lang.reflect.MethodSignature をインポートします。
  10. org.springframework.context.annotation.Configuration をインポートします。
  11. org.springframework.util.StringUtils をインポートします。
  12.  
  13. java.lang.reflect.Method をインポートします。
  14. java.util.concurrent.TimeUnitをインポートします。
  15.  
  16. /**
  17. * @クラス名:${}
  18. * @説明:TODO
  19. * @著者:[email protected]
  20. * @日付
  21. */
  22. @側面
  23. @構成
  24. パブリッククラスLockMethodAopConfigure {
  25. プライベート静的最終Cache<String, Object> CACHES = CacheBuilder.newBuilder()
  26. // 最大キャッシュサイズ: 100
  27. .最大サイズ(1000)
  28. // 書き込みキャッシュを5秒で期限切れになるように設定します
  29. .expireAfterWrite(5, 時間単位.SECONDS)
  30. 。建てる();
  31.  
  32. @Around( "実行(public * *(..)) && @annotation(com.ouyue.xiwenapi.annotation.GuaveLock)" )
  33. パブリックオブジェクトインターセプター(ProceedingJoinPoint pjp) {
  34. メソッド署名署名 = (メソッド署名) pjp.getSignature();
  35. メソッド method = signature.getMethod();
  36. GuaveLock の localLock = method.getAnnotation(GuaveLock.class);
  37. 文字列キー= getKey(localLock.key ( ), pjp.getArgs());
  38. if (!StringUtils.isEmpty(キー)) {
  39. CACHES.getIfPresent(キー) != null場合
  40. throw new RuntimeException( "リクエストを繰り返さないでください" );
  41. }
  42. // 最初のリクエストの場合は、キーの現在のオブジェクトをキャッシュにプッシュします
  43. CACHES.put(キー,キー);
  44. }
  45. 試す {
  46. pjp.proceed()を返します
  47. } キャッチ (Throwable スロー可能) {
  48. 新しい RuntimeException( "サーバー例外" ) をスローします。
  49. ついに
  50. // TODO 効果を示すために、CACHES.invalidate( key );コードはここでは呼び出されません
  51. }
  52. }
  53.  
  54. /**
  55. *キー生成戦略は、柔軟性が必要な場合は、インターフェースと実装クラスの形式で記述できます(TODOは後で説明します)
  56. *
  57. * @param keyExpress式
  58. * @param argsパラメータはMD5を使用して暗号化できます
  59. * @return生成されたキー 
  60. */
  61. プライベート文字列getKey(文字列keyExpress、オブジェクト[]引数) {
  62. ( int i = 0; i < args.length; i++) {
  63. keyExpress = keyExpress.replace ( "arg[" + i + "]" 、 args[i].toString());
  64. }
  65. keyExpressを返します
  66. }
  67. }

コントローラ

  1. @レストコントローラ
  2. @RequestMapping( "/ビジネス" )
  3. パブリッククラス BusinessController {
  4. @GuaveLock(キー= "ビジネス:arg[0]" )
  5. @GetMapping
  6. パブリック文字列クエリ(@RequestParam 文字列トークン) {
  7. 戻る  「成功 - 」 + トークン;
  8. }
  9. }

上記は基本的にメモリレベルのキャッシュであり、分散システムでは満たすことができません。したがって、分散システムでそれらを使用できる必要があります。

Redis キャッシュロックの実装

ドキュメント

  1. <依存関係>
  2. <groupId>org.springframework.boot</groupId>
  3. <artifactId>spring-boot-starter-data-redis</artifactId>
  4. </依存関係>
  5. spring.redis.host=ローカルホスト
  6. spring.redis.port=6379

レディロック

  1. プレフィックス: キャッシュ内のキーのプレフィックス
  2. 有効期限: 有効期限、デフォルト値は5秒
  3. timeUnit: タイムアウトの単位。デフォルトは秒です。
  4. 区切り文字: キー区切り文字。異なるパラメータ値を区切ります。
  1. パッケージ com.ouyue.xiwenapi.annotation;
  2.  
  3. java.lang.annotation.* をインポートします。
  4. java.util.concurrent.TimeUnitをインポートします。
  5.  
  6. /**
  7. * @クラス名:${}
  8. * @説明:TODO
  9. * @著者:[email protected]
  10. * @日付
  11. */
  12. @ターゲット(要素タイプ.METHOD)
  13. @保持(保持ポリシー.RUNTIME)
  14. @文書化済み
  15. @継承
  16. パブリック@interface RedisLock {
  17. /**
  18. * Redisロックキーのプレフィックス
  19. *
  20. * @return redis ロックキープレフィックス
  21. */
  22. 文字列プレフィックス()デフォルト  "" ;
  23.  
  24. /**
  25. * 有効期限の秒数、デフォルトは5秒
  26. *
  27. * @returnポーリングロック時間
  28. */
  29. int expire()デフォルト5;
  30.  
  31. /**
  32. * タイムアウト単位
  33. *
  34. * @return
  35. */
  36. TimeUnit timeUnit()デフォルトTimeUnit.SECONDS;
  37.  
  38. /**
  39. * <p>キーセパレーター (デフォルト:)</p>
  40. * <p>生成されたキー: N:SO1008:500</p>
  41. *
  42. * @return文字列
  43. */
  44. 文字列区切り文字()デフォルト  ":" ;
  45. }

CacheParam アノテーション

  1. パッケージ com.ouyue.xiwenapi.annotation;
  2.  
  3. java.lang.annotation.* をインポートします。
  4.  
  5. /**
  6. * @クラス名:${}
  7. * @説明:TODO
  8. * @著者:[email protected]
  9. * @日付
  10. */
  11. @Target({ElementType.PARAMETER, ElementType.METHOD, ElementType.FIELD})
  12. @保持(保持ポリシー.RUNTIME)
  13. @文書化済み
  14. @継承
  15. パブリック@interface CacheParam {
  16. /**
  17. * フィールド名
  18. *
  19. * @return文字列
  20. */
  21. 文字列()デフォルト  "" ;
  22. }

キー生成戦略

  1. パッケージ com.ouyue.xiwenapi.component;
  2.  
  3. org.aspectj.lang.ProceedingJoinPoint をインポートします。
  4.  
  5. パブリックインターフェースCacheKeyGenerator {
  6. /**
  7. * AOPパラメータを取得し、指定されたキャッシュキーを生成する 
  8. *
  9. * @param pjp PJP
  10. * @returnキャッシュキー 
  11. */
  12. 文字列 getLockKey(ProceedingJoinPoint pjp);
  13. }

鍵生成戦略(実装)

  1. パッケージ com.ouyue.xiwenapi.service;
  2.  
  3. com.ouyue.xiwenapi.annotation.CacheParam をインポートします。
  4. com.ouyue.xiwenapi.annotation.RedisLock をインポートします。
  5. com.ouyue.xiwenapi.comonet.CacheKeyGenerator をインポートします。
  6. org.aspectj.lang.ProceedingJoinPoint をインポートします。
  7. org.aspectj.lang.reflect.MethodSignature をインポートします。
  8. org.springframework.util.ReflectionUtils をインポートします。
  9. org.springframework.util.StringUtils をインポートします。
  10.  
  11. java.lang.annotation.Annotation をインポートします。
  12. java.lang.reflect.Field をインポートします。
  13. java.lang.reflect.Method をインポートします。
  14. java.lang.reflect.Parameterをインポートします。
  15.  
  16. /**
  17. * @クラス名:${}
  18. * @説明:TODO
  19. * @著者:[email protected]
  20. * @日付
  21. */
  22. パブリッククラスLockKeyGeneratorはCacheKeyGeneratorを実装します{
  23. @オーバーライド
  24. パブリック文字列 getLockKey(ProceedingJoinPoint pjp) {
  25. メソッド署名署名 = (メソッド署名) pjp.getSignature();
  26. メソッド method = signature.getMethod();
  27. RedisLock lockAnnotation = method.getAnnotation(RedisLock.class);
  28. 最終 Object[] args = pjp.getArgs();
  29. 最終的なパラメータ[]パラメータ = method.getParameters();
  30. StringBuilder ビルダー = new StringBuilder();
  31. // TODO デフォルトの解析メソッドには、CacheParam アノテーション付きプロパティが含まれています。エンティティオブジェクトを解析しようとしない場合
  32. ( int i = 0; i < パラメータ長さ; i++) {
  33. 最終的なCacheParamアノテーション = パラメータ[i].getAnnotation(CacheParam.class);
  34. if (アノテーション == null ) {
  35. 続く;
  36. }
  37. builder.append(lockAnnotation.delimiter()).append(args[i]);
  38. }
  39. StringUtils.isEmpty(builder.toString()) の場合 {
  40. 最終的な Annotation[][] パラメータ Annotations = method.getParameterAnnotations();
  41. ( int i = 0; i < パラメータ注釈のさ; i++) {
  42. 最終的なオブジェクトオブジェクト = args[i];
  43. 最終的なフィールド[] fields = object.getClass().getDeclaredFields();
  44. for (フィールド フィールド: フィールド) {
  45. 最終的な CacheParam アノテーション = field.getAnnotation(CacheParam.class);
  46. if (アノテーション == null ) {
  47. 続く;
  48. }
  49. フィールドをアクセス可能に設定( true );
  50. builder.append(lockAnnotation.delimiter()).append(ReflectionUtils.getField(field, object));
  51. }
  52. }
  53. }
  54. lockAnnotation.prefix() + builder.toString()を返します
  55. }
  56. }

ロックインターセプター (AOP)

  1. パッケージ com.ouyue.xiwenapi.config;
  2.  
  3. com.ouyue.xiwenapi.annotation.RedisLock をインポートします。
  4. com.ouyue.xiwenapi.comonet.CacheKeyGenerator をインポートします。
  5. org.aspectj.lang.ProceedingJoinPoint をインポートします。
  6. org.aspectj.lang.annotation.Around をインポートします。
  7. org.aspectj.lang.annotation.Aspect をインポートします。
  8. org.aspectj.lang.reflect.MethodSignature をインポートします。
  9. org.springframework.beans.factory.annotation.Autowired をインポートします。
  10. org.springframework.context.annotation.Configuration をインポートします。
  11. org.springframework.data.redis をインポートします。接続.RedisStringCommands;
  12. org.springframework.data.redis.core.RedisCallback をインポートします。
  13. org.springframework.data.redis.core.StringRedisTemplate をインポートします。
  14. org.springframework.data.redis.core.types.Expiration をインポートします。
  15. org.springframework.util.StringUtils をインポートします。
  16.  
  17. java.lang.reflect.Method をインポートします。
  18.  
  19. /**
  20. * @クラス名:${}
  21. * @説明:TODO
  22. * @著者:[email protected]
  23. * @日付
  24. */
  25. @側面
  26. @構成
  27. パブリッククラスLockMethodInterceptor {
  28. オートワイヤード
  29. パブリックLockMethodInterceptor(StringRedisTemplate lockRedisTemplate、CacheKeyGenerator cacheKeyGenerator) {
  30. this.lockRedisTemplate = lockRedisTemplate;
  31. this.cacheKeyGenerator = キャッシュキージェネレータ;
  32. }
  33.  
  34. プライベート最終 StringRedisTemplate lockRedisTemplate;
  35. プライベート最終 CacheKeyGenerator cacheKeyGenerator;
  36.  
  37.  
  38. @Around( "実行(public * *(..)) && @annotation(com.ouyue.xiwenapi.annotation.RedisLock)" )
  39. パブリックオブジェクトインターセプター(ProceedingJoinPoint pjp) {
  40. メソッド署名署名 = (メソッド署名) pjp.getSignature();
  41. メソッド method = signature.getMethod();
  42. RedisLock ロック = method.getAnnotation(RedisLock.class);
  43. StringUtils.isEmpty(lock.prefix()) の場合 {
  44. 新しい RuntimeException をスローします ( "ロック キーは null ではありません..." );
  45. }
  46. 最終的な文字列 lockKey = cacheKeyGenerator.getLockKey(pjp);
  47. 試す {
  48. // ネイティブAPIを使用して分散ロックを実装する
  49. 最終的なブール値の成功 = lockRedisTemplate。実行((RedisCallback<Boolean>) connection -> connection . set (lockKey.getBytes(), new byte[0], Expiration. from (lock.expire(), lock.timeUnit()), RedisStringCommands.SetOption.SET_IF_ABSENT));
  50. 成功の場合
  51. // TODO 論理的には、カスタム CacheLockException をスローする必要があります。ここでは怠けましょう
  52. throw new RuntimeException( "リクエストを繰り返さないでください" );
  53. }
  54. 試す {
  55. pjp.proceed()を返します
  56. } キャッチ (Throwable スロー可能) {
  57. 新しい RuntimeException( "システム例外" ) をスローします。
  58. }
  59. ついに
  60. // TODO これを実証したい場合は、このコードをコメント化する必要があります。実際には、開いたままにしておく必要があります。
  61. //Redisテンプレートをロックします。削除(lockKey);
  62. }
  63. }
  64. }

聞く

  1. パッケージ com.ouyue.xiwenapi.controller;
  2.  
  3. com.ouyue.xiwenapi.annotation.CacheParam をインポートします。
  4. com.ouyue.xiwenapi.annotation.GuaveLock をインポートします。
  5. com.ouyue.xiwenapi.annotation.RedisLock をインポートします。
  6. org.springframework.web.bind.annotation.GetMapping をインポートします。
  7. org.springframework.web.bind.annotation.RequestMapping をインポートします。
  8. org.springframework.web.bind.annotation.RequestParam をインポートします。
  9. org.springframework.web.bind.annotation.RestController をインポートします。
  10.  
  11. /**
  12. * @クラス名:${}
  13. * @説明:TODO
  14. * @著者:[email protected]
  15. * @日付
  16. */
  17.  
  18. @レストコントローラ
  19. @RequestMapping( "/ビジネス" )
  20. パブリッククラス BusinessController {
  21. @GuaveLock(キー= "ビジネス:arg[0]" )
  22. @GetMapping
  23. パブリック文字列クエリ(@RequestParam 文字列トークン) {
  24. 戻る  「成功 - 」 + トークン;
  25. }
  26.  
  27. @RedisLock(プレフィックス = "ユーザー" )
  28. @GetMapping
  29. パブリック文字列queryRedis(@CacheParam( name = "token" ) @RequestParam 文字列token) {
  30. 戻る  「成功 - 」 + トークン;
  31. }
  32. }

メイン関数のスタートアップクラスに、キー生成戦略関数を挿入します。

  1. @ビーン
  2. パブリックCacheKeyGenerator キャッシュキージェネレーター() {
  3. 新しい LockKeyGenerator()を返します
  4. }

<<:  ブルーグリーン/カナリアリリース向けの Argo ロールアウト

>>:  クラウドネイティブシステムの弾力性モデル

推薦する

静的ページと動的ページ、検索エンジンはどちらを優先するのでしょうか?

ウェブサイトの最適化に関して言えば、静的ページと動的ページのどちらが優れているかは、多くの最適化担当...

プリインストールされたソフトウェアをアンインストールできないとしてユーザーがAppleを訴える

新快報によると、深セン在住の姚さんは、アップルのiPhoneにはプリインストールされたソフトウェアを...

APPプロモーションのアイデアがありませんか? 1 つのマップですべてのチャネルを取得できます。 (超完成)

インターネット業界で働く人なら誰でも、APP アプリケーションの宣伝にはユーザーを集める長いプロセス...

7年間の努力を経て、QingCloudは汎用クラウドコンピューティングに取り組んでいます

[51CTO.com からのオリジナル記事] 情報と生活のデジタル化に続き、産業のデジタル化の時代が...

クリエイティブマーケティングを刺激する4つのアイデアを共有

マーケティングの創造性は、マーケティングの専門家だけの特権ではありません。誰にでもできるし、どんなこ...

知っておくべき Kafka コンシューマーのパラメーターは何ですか?

[[407801]]この記事では、Kafka Consumer について簡単に紹介します。これは、K...

Hostodo: ウェブサイト構築に適したラスベガス KVM シリーズの格安 VPS、「Alipay」をサポート

Hostodo は、西海岸に近く、KVM 仮想化機能を備えたラスベガスのファイバーハブ データ セン...

鄒聖林氏独占インタビュー:IT教育の最前線で活躍し、オープンソースの発展を推進

Red Hat は、コミュニティの力を活用して、安定性、信頼性、高性能を備えたクラウド テクノロジー...

spinservers: 109 ドル、サンノゼ データ センター、10Gbps 帯域幅、2*e5-2650L v3/64g メモリ/1.6T SSD

spinservers (MET のブランド、1994~) は現在、サンノゼ データ センターの専用...

HostMist - 年間 25 ドル / 256 MB RAM / 20 GB HDD / 300 GB Flow / ロサンゼルス QR

ここでは、手間をかけずにブログや個人のウェブサイトを構築したい友人にお勧めの、ホストミスト社の小メモ...

アプリが新規ユーザーを引き付けるための2つの重要なチャネル:ASOプロモーション+既存ユーザーの維持

1. ASO最適化:内部と外部の統合、ユーザー不足を心配する必要はありませんASO最適化は長い間存在...

インターネット上であらゆる操作の記録を残すことはどれほど役に立つでしょうか?

数日前、インターネットのブロガーグループで、グループリーダーが私をタグ付けしてこう言いました。「袁坤...

タオバオ無料ブログマーケティング

誰もが Taobao Affiliate をよく知っています。Taobao Affiliate とは...

外部リンク、プロモーション?ウェブマスター、あなたは間違っています、データ分析もあります

多くのウェブマスターは、自分のウェブサイトが長く発展し、存続することを望んでいます。もちろん、生き残...