[[410877]] Springboot2.x AOPは重複送信を防ぐためにキャッシュロックと分散ロックを実装しています。私はバックエンドシステムで長年の経験を持っています。ユーザーがネットワーク状態が悪い状態でフォームを送信すると、送信が繰り返し発生する可能性があります。したがって、フォームが再度送信されないようにする必要があります。 Google の Guave Cache- <依存関係>
- <groupId>org.springframework.boot</groupId>
- <artifactId>spring-boot-starter-web</artifactId>
- </依存関係>
- <依存関係>
- <groupId>org.springframework.boot</groupId>
- <artifactId>spring-boot-starter-aop</artifactId>
- </依存関係>
- <依存関係>
- <groupId>com.google.guava</groupId>
- <artifactId>グアバ</artifactId>
- <バージョン>21.0</バージョン>
- </依存関係>
注釈インターフェース- パッケージ com.ouyue.xiwenapi.annotation;
-
- java.lang.annotation.* をインポートします。
-
- /**
- * @クラス名:${}
- * @説明:TODO
- * @著者:[email protected]
- * @日付:
- */
- @ターゲット(要素タイプ.METHOD)
- @保持(保持ポリシー.RUNTIME)
- @文書化済み
- @継承
- パブリック@interface GuaveLock
- {
-
- 文字列キー()デフォルト "" ;
-
- /**
- * 有効期限 TODO guava を使用しているため、現時点ではこのプロパティは無視します。 Redisを統合するために必要
- *
- * @author フライ
- */
- int expire()デフォルト5;
- }
AOPの応用- パッケージ com.ouyue.xiwenapi.config;
-
- com.google.common.cache.Cache をインポートします。
- com.google.common.cache.CacheBuilder をインポートします。
- com.ouyue.xiwenapi.annotation.GuaveLock をインポートします。
- org.aspectj.lang.ProceedingJoinPoint をインポートします。
- org.aspectj.lang.annotation.Around をインポートします。
- org.aspectj.lang.annotation.Aspect をインポートします。
- org.aspectj.lang.reflect.MethodSignature をインポートします。
- org.springframework.context.annotation.Configuration をインポートします。
- org.springframework.util.StringUtils をインポートします。
-
- java.lang.reflect.Method をインポートします。
- java.util.concurrent.TimeUnitをインポートします。
-
- /**
- * @クラス名:${}
- * @説明:TODO
- * @著者:[email protected]
- * @日付:
- */
- @側面
- @構成
- パブリッククラスLockMethodAopConfigure {
- プライベート静的最終Cache<String, Object> CACHES = CacheBuilder.newBuilder()
- // 最大キャッシュサイズ: 100
- .最大サイズ(1000)
- // 書き込みキャッシュを5秒で期限切れになるように設定します
- .expireAfterWrite(5, 時間単位.SECONDS)
- 。建てる();
-
- @Around( "実行(public * *(..)) && @annotation(com.ouyue.xiwenapi.annotation.GuaveLock)" )
- パブリックオブジェクトインターセプター(ProceedingJoinPoint pjp) {
- メソッド署名署名 = (メソッド署名) pjp.getSignature();
- メソッド method = signature.getMethod();
- GuaveLock の localLock = method.getAnnotation(GuaveLock.class);
- 文字列キー= getKey(localLock.key ( ), pjp.getArgs());
- if (!StringUtils.isEmpty(キー)) {
- CACHES.getIfPresent(キー) != nullの場合
- throw new RuntimeException( "リクエストを繰り返さないでください" );
- }
- // 最初のリクエストの場合は、キーの現在のオブジェクトをキャッシュにプッシュします
- CACHES.put(キー,キー);
- }
- 試す {
- pjp.proceed()を返します。
- } キャッチ (Throwable スロー可能) {
- 新しい RuntimeException( "サーバー例外" ) をスローします。
- ついに
- // TODO 効果を示すために、CACHES.invalidate( key );コードはここでは呼び出されません
- }
- }
-
- /**
- *キー生成戦略は、柔軟性が必要な場合は、インターフェースと実装クラスの形式で記述できます(TODOは後で説明します)
- *
- * @param keyExpress式
- * @param argsパラメータはMD5を使用して暗号化できます
- * @return生成されたキー
- */
- プライベート文字列getKey(文字列keyExpress、オブジェクト[]引数) {
- ( int i = 0; i < args.length; i++) {
- keyExpress = keyExpress.replace ( "arg[" + i + "]" 、 args[i].toString());
- }
- keyExpressを返します。
- }
- }
コントローラ- @レストコントローラ
- @RequestMapping( "/ビジネス" )
- パブリッククラス BusinessController {
- @GuaveLock(キー= "ビジネス:arg[0]" )
- @GetMapping
- パブリック文字列クエリ(@RequestParam 文字列トークン) {
- 戻る 「成功 - 」 + トークン;
- }
- }
上記は基本的にメモリレベルのキャッシュであり、分散システムでは満たすことができません。したがって、分散システムでそれらを使用できる必要があります。 Redis キャッシュロックの実装ドキュメント- <依存関係>
- <groupId>org.springframework.boot</groupId>
- <artifactId>spring-boot-starter-data-redis</artifactId>
- </依存関係>
- spring.redis.host=ローカルホスト
- spring.redis.port=6379
レディロック- プレフィックス: キャッシュ内のキーのプレフィックス
- 有効期限: 有効期限、デフォルト値は5秒
- timeUnit: タイムアウトの単位。デフォルトは秒です。
- 区切り文字: キー区切り文字。異なるパラメータ値を区切ります。
- パッケージ com.ouyue.xiwenapi.annotation;
-
- java.lang.annotation.* をインポートします。
- java.util.concurrent.TimeUnitをインポートします。
-
- /**
- * @クラス名:${}
- * @説明:TODO
- * @著者:[email protected]
- * @日付:
- */
- @ターゲット(要素タイプ.METHOD)
- @保持(保持ポリシー.RUNTIME)
- @文書化済み
- @継承
- パブリック@interface RedisLock {
- /**
- * Redisロックキーのプレフィックス
- *
- * @return redis ロックキープレフィックス
- */
- 文字列プレフィックス()デフォルト "" ;
-
- /**
- * 有効期限の秒数、デフォルトは5秒
- *
- * @returnポーリングロック時間
- */
- int expire()デフォルト5;
-
- /**
- * タイムアウト単位
- *
- * @return秒
- */
- TimeUnit timeUnit()デフォルトTimeUnit.SECONDS;
-
- /**
- * <p>キーセパレーター (デフォルト:)</p>
- * <p>生成されたキー: N:SO1008:500</p>
- *
- * @return文字列
- */
- 文字列区切り文字()デフォルト ":" ;
- }
CacheParam アノテーション- パッケージ com.ouyue.xiwenapi.annotation;
-
- java.lang.annotation.* をインポートします。
-
- /**
- * @クラス名:${}
- * @説明:TODO
- * @著者:[email protected]
- * @日付:
- */
- @Target({ElementType.PARAMETER, ElementType.METHOD, ElementType.FIELD})
- @保持(保持ポリシー.RUNTIME)
- @文書化済み
- @継承
- パブリック@interface CacheParam {
- /**
- * フィールド名
- *
- * @return文字列
- */
- 文字列名()デフォルト "" ;
- }
キー生成戦略- パッケージ com.ouyue.xiwenapi.component;
-
- org.aspectj.lang.ProceedingJoinPoint をインポートします。
-
- パブリックインターフェースCacheKeyGenerator {
- /**
- * AOPパラメータを取得し、指定されたキャッシュキーを生成する
- *
- * @param pjp PJP
- * @returnキャッシュキー
- */
- 文字列 getLockKey(ProceedingJoinPoint pjp);
- }
鍵生成戦略(実装)- パッケージ com.ouyue.xiwenapi.service;
-
- com.ouyue.xiwenapi.annotation.CacheParam をインポートします。
- com.ouyue.xiwenapi.annotation.RedisLock をインポートします。
- com.ouyue.xiwenapi.comonet.CacheKeyGenerator をインポートします。
- org.aspectj.lang.ProceedingJoinPoint をインポートします。
- org.aspectj.lang.reflect.MethodSignature をインポートします。
- org.springframework.util.ReflectionUtils をインポートします。
- org.springframework.util.StringUtils をインポートします。
-
- java.lang.annotation.Annotation をインポートします。
- java.lang.reflect.Field をインポートします。
- java.lang.reflect.Method をインポートします。
- java.lang.reflect.Parameterをインポートします。
-
- /**
- * @クラス名:${}
- * @説明:TODO
- * @著者:[email protected]
- * @日付:
- */
- パブリッククラスLockKeyGeneratorはCacheKeyGeneratorを実装します{
- @オーバーライド
- パブリック文字列 getLockKey(ProceedingJoinPoint pjp) {
- メソッド署名署名 = (メソッド署名) pjp.getSignature();
- メソッド method = signature.getMethod();
- RedisLock lockAnnotation = method.getAnnotation(RedisLock.class);
- 最終 Object[] args = pjp.getArgs();
- 最終的なパラメータ[]パラメータ = method.getParameters();
- StringBuilder ビルダー = new StringBuilder();
- // TODO デフォルトの解析メソッドには、CacheParam アノテーション付きプロパティが含まれています。エンティティオブジェクトを解析しようとしない場合
- ( int i = 0; i < パラメータの長さ; i++) {
- 最終的なCacheParamアノテーション = パラメータ[i].getAnnotation(CacheParam.class);
- if (アノテーション == null ) {
- 続く;
- }
- builder.append(lockAnnotation.delimiter()).append(args[i]);
- }
- StringUtils.isEmpty(builder.toString()) の場合 {
- 最終的な Annotation[][] パラメータ Annotations = method.getParameterAnnotations();
- ( int i = 0; i < パラメータ注釈の長さ; i++) {
- 最終的なオブジェクトオブジェクト = args[i];
- 最終的なフィールド[] fields = object.getClass().getDeclaredFields();
- for (フィールド フィールド: フィールド) {
- 最終的な CacheParam アノテーション = field.getAnnotation(CacheParam.class);
- if (アノテーション == null ) {
- 続く;
- }
- フィールドをアクセス可能に設定( true );
- builder.append(lockAnnotation.delimiter()).append(ReflectionUtils.getField(field, object));
- }
- }
- }
- lockAnnotation.prefix() + builder.toString()を返します。
- }
- }
ロックインターセプター (AOP)- パッケージ com.ouyue.xiwenapi.config;
-
- com.ouyue.xiwenapi.annotation.RedisLock をインポートします。
- com.ouyue.xiwenapi.comonet.CacheKeyGenerator をインポートします。
- org.aspectj.lang.ProceedingJoinPoint をインポートします。
- org.aspectj.lang.annotation.Around をインポートします。
- org.aspectj.lang.annotation.Aspect をインポートします。
- org.aspectj.lang.reflect.MethodSignature をインポートします。
- org.springframework.beans.factory.annotation.Autowired をインポートします。
- org.springframework.context.annotation.Configuration をインポートします。
- org.springframework.data.redis をインポートします。接続.RedisStringCommands;
- org.springframework.data.redis.core.RedisCallback をインポートします。
- org.springframework.data.redis.core.StringRedisTemplate をインポートします。
- org.springframework.data.redis.core.types.Expiration をインポートします。
- org.springframework.util.StringUtils をインポートします。
-
- java.lang.reflect.Method をインポートします。
-
- /**
- * @クラス名:${}
- * @説明:TODO
- * @著者:[email protected]
- * @日付:
- */
- @側面
- @構成
- パブリッククラスLockMethodInterceptor {
- オートワイヤード
- パブリックLockMethodInterceptor(StringRedisTemplate lockRedisTemplate、CacheKeyGenerator cacheKeyGenerator) {
- this.lockRedisTemplate = lockRedisTemplate;
- this.cacheKeyGenerator = キャッシュキージェネレータ;
- }
-
- プライベート最終 StringRedisTemplate lockRedisTemplate;
- プライベート最終 CacheKeyGenerator cacheKeyGenerator;
-
-
- @Around( "実行(public * *(..)) && @annotation(com.ouyue.xiwenapi.annotation.RedisLock)" )
- パブリックオブジェクトインターセプター(ProceedingJoinPoint pjp) {
- メソッド署名署名 = (メソッド署名) pjp.getSignature();
- メソッド method = signature.getMethod();
- RedisLock ロック = method.getAnnotation(RedisLock.class);
- StringUtils.isEmpty(lock.prefix()) の場合 {
- 新しい RuntimeException をスローします ( "ロック キーは null ではありません..." );
- }
- 最終的な文字列 lockKey = cacheKeyGenerator.getLockKey(pjp);
- 試す {
- // ネイティブAPIを使用して分散ロックを実装する
- 最終的なブール値の成功 = lockRedisTemplate。実行((RedisCallback<Boolean>) connection -> connection . set (lockKey.getBytes(), new byte[0], Expiration. from (lock.expire(), lock.timeUnit()), RedisStringCommands.SetOption.SET_IF_ABSENT));
- 成功の場合
- // TODO 論理的には、カスタム CacheLockException をスローする必要があります。ここでは怠けましょう
- throw new RuntimeException( "リクエストを繰り返さないでください" );
- }
- 試す {
- pjp.proceed()を返します。
- } キャッチ (Throwable スロー可能) {
- 新しい RuntimeException( "システム例外" ) をスローします。
- }
- ついに
- // TODO これを実証したい場合は、このコードをコメント化する必要があります。実際には、開いたままにしておく必要があります。
- //Redisテンプレートをロックします。削除(lockKey);
- }
- }
- }
聞く- パッケージ com.ouyue.xiwenapi.controller;
-
- com.ouyue.xiwenapi.annotation.CacheParam をインポートします。
- com.ouyue.xiwenapi.annotation.GuaveLock をインポートします。
- com.ouyue.xiwenapi.annotation.RedisLock をインポートします。
- org.springframework.web.bind.annotation.GetMapping をインポートします。
- org.springframework.web.bind.annotation.RequestMapping をインポートします。
- org.springframework.web.bind.annotation.RequestParam をインポートします。
- org.springframework.web.bind.annotation.RestController をインポートします。
-
- /**
- * @クラス名:${}
- * @説明:TODO
- * @著者:[email protected]
- * @日付:
- */
-
- @レストコントローラ
- @RequestMapping( "/ビジネス" )
- パブリッククラス BusinessController {
- @GuaveLock(キー= "ビジネス:arg[0]" )
- @GetMapping
- パブリック文字列クエリ(@RequestParam 文字列トークン) {
- 戻る 「成功 - 」 + トークン;
- }
-
- @RedisLock(プレフィックス = "ユーザー" )
- @GetMapping
- パブリック文字列queryRedis(@CacheParam( name = "token" ) @RequestParam 文字列token) {
- 戻る 「成功 - 」 + トークン;
- }
- }
メイン関数のスタートアップクラスに、キー生成戦略関数を挿入します。 - @ビーン
- パブリックCacheKeyGenerator キャッシュキージェネレーター() {
- 新しい LockKeyGenerator()を返します。
- }
|