SpringBoot が AOP を使用してグローバル インターフェイスの訪問数をカウントする方法

WBOY
リリース: 2023-05-13 09:10:05
転載
1464 人が閲覧しました

AOP とは

#AOP (アスペクト指向プログラミング) はアスペクト指向プログラミングであり、プリコンパイルと実行時の動的プロキシを通じてプログラム機能を実現する、伝統的に維持されているテクノロジです。

AOP の機能と利点

機能: プログラムの実行中に、ソース コードを変更せずに一部のメソッドを機能拡張できます

利点: 重複コードを削減します。開発効率の向上とメンテナンスの容易化

一般的なダイナミック プロキシ テクノロジ

jdk プロキシ: インターフェイス ベースのダイナミック プロキシ テクノロジ

SpringBoot が AOP を使用してグローバル インターフェイスの訪問数をカウントする方法

cglib プロキシ:親クラスに基づく動的プロキシ テクノロジ

SpringBoot が AOP を使用してグローバル インターフェイスの訪問数をカウントする方法

AOP 関連の概念

  • リスト項目 - ターゲット (ターゲット オブジェクト): プロキシ ターゲット オブジェクト

  • Proxy (プロキシ): AOP による拡張機能にクラスが組み込まれた後、結果として得られるプロキシ クラス

  • Joinpoint (接続ポイント): 接続ポイントはインターセプトされたポイントです。 Spring では、Spring はメソッド タイプの接続ポイントのみをサポートするため、これらのポイントはメソッドを参照します (拡張できるメソッドは接続ポイントと呼ばれます)

  • PointCut (ポイントカット): ポイントカットどのジョインポイントをインターセプトするかの定義

  • #アドバイス (通知/拡張): 通知とは、ジョインポイントをインターセプトした後に行う必要があることを指します。通知
  • #アスペクト (アスペクト): ポイントカットと通知の組み合わせです。
  • #ウィービング (ウィービング): ターゲット オブジェクトに拡張機能を適用して、新しいプロキシ オブジェクトを作成するプロセス。 Spring は動的プロキシ ウィービングを使用しますが、AspectJ はコンパイラ ウィービングとクラス ローダー ウィービングを使用します。

  • 実装

  • ここではアノテーション ベースの AOP 開発を使用します。

開発手順

依存関係の追加

		<!--引入AOP依赖-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-aop</artifactId>
        </dependency>
        <!--AOP-->
        <dependency>
            <groupId>org.aspectj</groupId>
            <artifactId>aspectjrt</artifactId>
            <version>1.9.4</version>
        </dependency>
        <dependency>
            <groupId>org.aspectj</groupId>
            <artifactId>aspectjweaver</artifactId>
            <version>1.9.4</version>
        </dependency>
        <dependency>
            <groupId>cglib</groupId>
            <artifactId>cglib</artifactId>
            <version>3.2.12</version>
        </dependency>
ログイン後にコピー

ターゲット インターフェイスとターゲット クラスの作成 (内部カット ポイントあり)

アスペクト クラスの作成 (内部拡張あり)メソッド)

ターゲット クラスとアスペクト クラスのオブジェクト作成権限を Spirng に任せる

アノテーションを使用してアスペクト クラスのウィービング関係を構成する

コンポーネント スキャンと AOP を有効にする構成ファイル内 自動プロキシ

#私のプロジェクトは SpringBoot Web プロジェクトであるため、ここで注釈を有効にするだけです。

SpringBoot が AOP を使用してグローバル インターフェイスの訪問数をカウントする方法次のコードは、使用されるアトミック カウンティング クラスです。

import java.util.concurrent.atomic.AtomicInteger;
public class AtomicCounter {
    private static final AtomicCounter atomicCounter = new AtomicCounter();
    /**
     * 单例,不允许外界主动实例化
     */
    private AtomicCounter() {
    }
    public static AtomicCounter getInstance() {
        return atomicCounter;
    }
    private static AtomicInteger counter = new AtomicInteger();
    public int getValue() {
        return counter.get();
    }
    public int increase() {
        return counter.incrementAndGet();
    }
    public int decrease() {
        return counter.decrementAndGet();
    }
    // 清零
    public void toZero(){
        counter.set(0);
    }
}
ログイン後にコピー

次のコードは、実装されたグローバル インターフェイス監視です。

プロジェクト内のキャッシュに Redis を使用しているだけで、Redis 関連の操作がすべて表示されます。

@Before を使用して事前通知を構成します。拡張メソッドがポイントカット メソッドの前に実行されるように指定します。

@ @AfterReturning を使用して投稿通知を構成します。拡張メソッドがポイントカット メソッドの後に実行されるように指定します。

@ @AfterThrowing を使用して、例外スロー通知を構成します。例外が発生したときに拡張メソッドが実行されるように指定します。

@Component
@Aspect
public class GlobalActuator {
    private static final Logger log = LoggerFactory.getLogger(GlobalActuator.class);
    @Resource
    private StringRedisTemplate stringRedisTemplate;
    ThreadLocal<Long> startTime = new ThreadLocal<>();
    ConcurrentHashMap<Object, Object> countMap = new ConcurrentHashMap<Object, Object>();
    /**
     * 匹配控制层层通知 这里监控controller下的所有接口
     */
    @Pointcut("execution(* com.sf.controller.*Controller.*(..))")
    private void controllerPt() {
    }
    /**
     * 在接口原有的方法执行前,将会首先执行此处的代码
     */
    @Before("com.sf.actuator.GlobalActuator.controllerPt()")
    public void doBefore(JoinPoint joinPoint) throws Throwable {
        startTime.set(System.currentTimeMillis());
        //获取传入目标方法的参数
        Object[] args = joinPoint.getArgs();
    }
    /**
     * 只有正常返回才会执行此方法
     * 如果程序执行失败,则不执行此方法
     */
    @AfterReturning(returning = "returnVal", pointcut = "com.sf.actuator.GlobalActuator.controllerPt()")
    public void doAfterReturning(JoinPoint joinPoint, Object returnVal) throws Throwable {
        Signature signature = joinPoint.getSignature();
        String declaringName = signature.getDeclaringTypeName();
        String methodName = signature.getName();
        String mapKey = declaringName + methodName;
        // 执行成功则计数加一
        int increase = AtomicCounter.getInstance().increase();
        HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
        synchronized (this) {
        //在项目启动时,需要在Redis中读取原有的接口请求次数
            if (countMap.size() == 0) {
                JSONObject jsonObject = RedisUtils.objFromRedis(StringConst.INTERFACE_ACTUATOR);
                if (jsonObject != null) {
                    Set<String> strings = jsonObject.keySet();
                    for (String string : strings) {
                        Object o = jsonObject.get(string);
                        countMap.putIfAbsent(string, o);
                    }
                }
            }
        }
        // 如果此次访问的接口不在countMap,放入countMap
        countMap.putIfAbsent(mapKey, 0);
        countMap.compute(mapKey, (key, value) -> (Integer) value + 1);
        synchronized (this) {
            // 内存计数达到30 更新redis
            if (increase == 30) {
                RedisUtils.objToRedis(StringConst.INTERFACE_ACTUATOR, countMap, Constants.AVA_REDIS_TIMEOUT);
                //删除过期时间
                stringRedisTemplate.persist(StringConst.INTERFACE_ACTUATOR);
                //计数器置为0
                AtomicCounter.getInstance().toZero();
            }
        }
        //log.info("方法执行次数:" + mapKey + "------>" + countMap.get(mapKey));
        //log.info("URI:[{}], 耗费时间:[{}] ms", request.getRequestURI(), System.currentTimeMillis() - startTime.get());
    }
    /**
     * 当接口报错时执行此方法
     */
    @AfterThrowing(pointcut = "com.sf.actuator.GlobalActuator.controllerPt()")
    public void doAfterThrowing(JoinPoint joinPoint) {
        HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
        log.info("接口访问失败,URI:[{}], 耗费时间:[{}] ms", request.getRequestURI(), System.currentTimeMillis() - startTime.get());
    }
}
ログイン後にコピー

コントローラ層のコードをここに示します。

    @GetMapping("/interface/{intCount}")
    @ApiOperation(value = "查找接口成功访问次数(默认倒序)")
    public Result<List<InterfaceDto>> findInterfaceCount(
            @ApiParam(name = "intCount", value = "需要的接口数") @PathVariable Integer intCount
    ) {
        HashMap<String, Integer> hashMap = new HashMap<>();
        JSONObject jsonObject = RedisUtils.objFromRedis(StringConst.INTERFACE_ACTUATOR);
        if (jsonObject != null) {
            Set<String> strings = jsonObject.keySet();
            for (String string : strings) {
                Integer o = (Integer) jsonObject.get(string);
                hashMap.putIfAbsent(string, o);
            }
        }
        //根据value倒序
        Map<String, Integer> sortedMap = hashMap.entrySet().stream().sorted(Collections.reverseOrder(Map.Entry.comparingByValue()))
                .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue, (e1, e2) -> e1, LinkedHashMap::new));
        //返回列表
        List<InterfaceDto> resultList = new ArrayList<>();
        //排序后中的map中所有的key
        Object[] objects = sortedMap.keySet().toArray();
        for (int i = 0; i < intCount; i++) {
            InterfaceDto interfaceDto = new InterfaceDto();
            interfaceDto.setName((String) objects[i]);
            interfaceDto.setCount(sortedMap.get((String) objects[i]));
            resultList.add(interfaceDto);
        }
        return Result.success(resultList);
    }
ログイン後にコピー

プロジェクトが一定期間実行されると、Redis のインターフェイスに対するリクエストの数を確認できます。

Web の最終的なレンダリングは次のとおりです:

SpringBoot が AOP を使用してグローバル インターフェイスの訪問数をカウントする方法

以上がSpringBoot が AOP を使用してグローバル インターフェイスの訪問数をカウントする方法の詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。

関連ラベル:
ソース:yisu.com
このウェブサイトの声明
この記事の内容はネチズンが自主的に寄稿したものであり、著作権は原著者に帰属します。このサイトは、それに相当する法的責任を負いません。盗作または侵害の疑いのあるコンテンツを見つけた場合は、admin@php.cn までご連絡ください。
人気のチュートリアル
詳細>
最新のダウンロード
詳細>
ウェブエフェクト
公式サイト
サイト素材
フロントエンドテンプレート