網頁

2021/6/21

Spring Boot AOP MethodInterceptor 方法攔截器 簡單範例

Spring AOP的MethodInterceptor可以用來對目標方法進行呼叫前及回傳後的攔截並切入邏輯。


一般切入直接用Spring AOP @Aspect的方式即可。本篇的或許是用在需要更細微的切入設定。

範例環境:

  • Java 8
  • Spring Boot 2.3.2.RELEASE
  • Spring AOP
  • Log4j 2
  • JUnit 5


範例

例如下面DemoService.add()中會呼叫DemoDao.getAmount(),將以MethodInterceptor在呼叫方法前後切入印出方法名稱、參數及回傳值的邏輯。

DemoDao

package com.abc.demo.dao;

import com.abc.demo.aop.annotation.Intercept;
import org.springframework.stereotype.Repository;

@Repository
public class DemoDao {

    @Intercept
    public int getAmount(long id) {
        return 1000;
    }
}

DemoService

package com.abc.demo.service;

import com.abc.demo.dao.DemoDao;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

@Service
public class DemoService {

    @Autowired
    private DemoDao demoDao;

    public int add(long id, int amount) {
        return amount + demoDao.getAmount(id);
    }
}

DemoMethodInterceptor實作MethodInterceptor介面及其invoke(MethodInvocation invocation)方法做為切入的邏輯。

DemoMethodInterceptor

package com.abc.demo.aop.interceptor;

import lombok.extern.slf4j.Slf4j;
import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;

@Slf4j
public class DemoMethodInterceptor implements MethodInterceptor {

    @Override
    public Object invoke(MethodInvocation invocation) throws Throwable {
        log.info("method={}, args={}", invocation.getMethod(), invocation.getArguments()); // 印出切入方法的名稱及參數
        Object ret = invocation.proceed(); // 執行切入方法並取得回傳值
        log.info("return={}", ret); // 印出切入方法的回傳值
        return ret; // 返回回傳值給caller
    }
}

MethodInterceptor的父介面為Advice可了解到其為AOP的一種Advice(切入方式)


僅實作MethodInterceptor並不會有效果,必須註冊Advisor及設定Pointcut(切入點)及上面的Advice才有作用。Spring Boot可在配置類透過DefaultPointcutAdvisor設定Pointcut和Advice。例如下面在配置類DemoConfig設定兩個DefaultPointcutAdvisor分別提供不同的Pointcut方式﹔
一是AspectJExpressionPointcut以pointcut expression切入;
另一AnnotationMatchingPointcut對掛有指定annotation的方法切入。
兩個Advisor的Advice皆為上面的DemoMethodInterceptor

DemoConfig

package com.abc.demo.config;

import com.abc.demo.aop.annotation.Intercept;
import org.aopalliance.intercept.MethodInterceptor;
import org.springframework.aop.aspectj.AspectJExpressionPointcut;
import org.springframework.aop.support.DefaultPointcutAdvisor;
import org.springframework.aop.support.annotation.AnnotationMatchingPointcut;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class DemoConfig {

    @Autowired
    private MethodInterceptor demoMethodInterceptor;

    @Bean
    public DefaultPointcutAdvisor defaultPointcutAdvisorByAspectJExpressionPointcut() {
        AspectJExpressionPointcut pointcut = new AspectJExpressionPointcut();
        pointcut.setExpression("execution(* com.abc.demo.service..*.*(..))");

        DefaultPointcutAdvisor advisor = new DefaultPointcutAdvisor();
        advisor.setPointcut(pointcut);
        advisor.setAdvice(demoMethodInterceptor);
        return advisor;
    }

    @Bean
    public DefaultPointcutAdvisor defaultPointcutAdvisorByAnnotationMatchingPointcut() {
        AnnotationMatchingPointcut pointcut = AnnotationMatchingPointcut.forMethodAnnotation(Intercept.class);

        DefaultPointcutAdvisor advisor = new DefaultPointcutAdvisor();
        advisor.setPointcut(pointcut);
        advisor.setAdvice(demoMethodInterceptor);
        return advisor;
    }
}

新增@Intercept作為AnnotationMatchingPointcut用來切入的annotation。

@Intercept

package com.abc.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 Intercept {
}


測試

DemoServiceTests測試類呼叫DemoService.add()並觀察印出的log。

DemoServiceTests

package com.abc.demo.service;

import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;

@SpringBootTest
public class DemoServiceTests {

    @Autowired
    private DemoService demoService;

    @Test
    public void test() {
        int result = demoService.add(1, 999);
        Assertions.assertEquals(1999, result);
    }
}

執行測試後可看到在console印出DemoMethodInterceptor切入的邏輯如下。

2021-06-22 13:16:07.224  INFO 11676 --- [           main] c.a.d.a.i.DemoMethodInterceptor          : method=public int com.abc.demo.service.DemoService.add(long,int), args=[1, 999]
2021-06-22 13:16:07.229  INFO 11676 --- [           main] c.a.d.a.i.DemoMethodInterceptor          : method=public int com.abc.demo.dao.DemoDao.getAmount(long), args=[1]
2021-06-22 13:16:07.233  INFO 11676 --- [           main] c.a.d.a.i.DemoMethodInterceptor          : return=1000
2021-06-22 13:16:07.233  INFO 11676 --- [           main] c.a.d.a.i.DemoMethodInterceptor          : return=1999

github


沒有留言:

張貼留言