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
沒有留言:
張貼留言