在我們實(shí)際的開發(fā)過程當(dāng)中,可能會用到一些自定義注解去實(shí)現(xiàn)一些功能,自定義注解可以注解在接口類的方法上,也可以注解在接口實(shí)現(xiàn)類的方法上,這樣這個(gè)自定義注解運(yùn)用起來就會更加的靈活,其實(shí)想要在SpringBoot中達(dá)到這樣的效果是一件非常簡單的事,奈何某度搜索引擎及某某DN搜索出來的文章都沒有一篇是切中我想搜索的內(nèi)容
以下的實(shí)現(xiàn)方式借鑒了keetone大佬的(原創(chuàng))spring aop無法攔截接口上的注解文章,中間做了一些修改,如果想要更為詳細(xì)的了解可以去看看他的這篇文章
好了費(fèi)話不多說,直接上代碼(中間有很多我個(gè)人的理解的描述,可能不正確,勿噴, 但功能是能用的):
pom引入aspectjweaver依賴
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
</dependency>
自定義一個(gè)注解類,例如:
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD})
public @interface Ax {
String value() default "";
}
Target({ElementType.METHOD})由這里定義只能注解在方法上面,如果有其他的需求,可以去看看 java.lang.annotation.ElementType這個(gè)枚舉類的定義
定義一個(gè)方法匹配切入點(diǎn)顧問類,例如:
public class AxMethodPointcutAdvisor extends StaticMethodMatcherPointcutAdvisor {
@Override
public boolean matches(@NotNull Method method, @NotNull Class<?> targetClass) {
return AnnotatedElementUtils.hasAnnotation(method, Ax.class);
}
}
定義這個(gè)類的目的是把所有接口類及接口實(shí)現(xiàn)類中被我們上面定義的Ax注解的所有方法都過濾出來,讓spring給我們自動生成CGLIB代理(其實(shí)是自動生成SpringProxy代理)
定義一個(gè)方法攔截器
這個(gè)是作為過濾出來的方法的切面處理,我們對于自己定義的注解要實(shí)現(xiàn)功能的處理邏輯就寫在這個(gè)里面
public class AxInterceptor implements MethodInterceptor {
@Override
public Object invoke(@NotNull MethodInvocation invocation) throws Throwable {
String name = invocation.getMethod().getName();
Ax ax = invocation.getMethod().getAnnotation(Ax.class);
Object ret;
if (Objects.nonNull(ax)) {
// 在這里,攔截的有可能是我們的接口實(shí)現(xiàn)類的方法,也有可能是Spring為我們自動創(chuàng)建的SpringProxy動態(tài)代理的方法
// 在接口實(shí)現(xiàn)類和動態(tài)代理類中,方法上面的注解源信息都可以拿到
// 當(dāng)我們在接口類的方法上添加我們自定義的注解時(shí),Spring為我們創(chuàng)建的動態(tài)代理的方法上也會有該注解,且含有我們在接口類中設(shè)置的注解源信息,比如Ax的value值
// 調(diào)用該方法之前的處理邏輯... 這里只是打印了一下信息,自定義的注解想要實(shí)現(xiàn)的功能邏輯就從這里開始寫,調(diào)用方法前后或者不調(diào)用方法大家各自發(fā)揮
System.out.println("before -- " + ax.value() + " --->> " + name);
ret = invocation.proceed();
// 調(diào)用該方法之后的處理邏輯... 這里也只是打印了一下信息
System.out.println("after -- " + ax.value() + " ---->> " + name);
} else {
// 該地方是的接口類中被注解的方法的攔截,但是在這里我們拿不到自定義注解源信息:ax都是null,更別說獲取ax的value
// 故讓方法調(diào)用繼續(xù)往下,下面就有可能是實(shí)現(xiàn)類的方法調(diào)用,也有可能是Spring為我們創(chuàng)建的動態(tài)代理類的方法調(diào)用
ret = invocation.proceed();
}
return ret;
}
}
讓Spring把我們上面定義的類粘合起來
@Configuration
public class AxConfig implements BeanPostProcessor {
/**
* 切面處理類注冊為Spring的Bean
* 這個(gè)里面是我們自定義注解需想要實(shí)現(xiàn)功能的核心
*
* @return AxInterceptor
*/
@Bean
public AxInterceptor axInterceptor() {
return new AxInterceptor();
}
/**
* 方法匹配切入點(diǎn)顧問
* (我個(gè)人覺得更像是ApplicationContext中的Bean的掃描過濾器,過濾出需要創(chuàng)建動態(tài)代理的方法)
*
* @return AxMethodPointcutAdvisor
*/
@Bean
public AxMethodPointcutAdvisor axMethodPointcutAdvisor() {
AxMethodPointcutAdvisor advisor = new AxMethodPointcutAdvisor();
// 設(shè)置切面處理
advisor.setAdvice(axInterceptor());
return advisor;
}
/**
* 該Bean是讓Spring自動創(chuàng)建代理的核心
* 可以不添加這個(gè)Bean,如果不添加,那么接口類的方法注解就攔截不到了,只能攔截到接口實(shí)現(xiàn)類中被注解的方法
* 它和上面申明的AxMethodPointcutAdvisor一起協(xié)同工作,它要創(chuàng)建的代理由AxMethodPointcutAdvisor中matches方法決定
*
* @return DefaultAdvisorAutoProxyCreator
*/
@Bean
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
public DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator() {
return new DefaultAdvisorAutoProxyCreator();
}
}
需要注意的是該類為BeanPostProcessor的實(shí)現(xiàn),也可以不實(shí)現(xiàn)BeanPostProcessor接口,不影響使用,如果不實(shí)現(xiàn)BeanPostProcessor接口,那么在Spring啟動的時(shí)候會出現(xiàn)一行像是錯(cuò)誤的提示(Info級別的日志):
Bean 'axConfig' of type [com.proxyclient.advisor.AxConfig$$EnhancerBySpringCGLIB$$60a28775] is not eligible for getting processed by all BeanPostProcessors (for example: not eligible for auto-proxying)
這個(gè)提示出現(xiàn)的原因是我們在這個(gè)類里申明了DefaultAdvisorAutoProxyCreator這個(gè)Bean,如果不創(chuàng)建DefaultAdvisorAutoProxyCreator這個(gè)Bean,就可以不用實(shí)現(xiàn)BeanPostProcessor接口,啟動時(shí)不會出現(xiàn)上面那個(gè)日志
這里需要說明一下:我們在申明DefaultAdvisorAutoProxyCreator這個(gè)Bean之后,可能會產(chǎn)生一些"副使用",比如我自己這個(gè)Demo中的SpringRetry實(shí)例,啟動會報(bào)錯(cuò),需要在Retryable的實(shí)例上添加@Scope(proxyMode = ScopedProxyMode.TARGET_CLASS)這個(gè)注解才能正常啟動和工作,這只是我個(gè)人遇到了這個(gè)問題及解決的辦法,所以大家在以這種方式去實(shí)現(xiàn)接口類和實(shí)現(xiàn)類的自定義注解攔截時(shí)需要注意由DefaultAdvisorAutoProxyCreator這個(gè)Bean帶來的副作用
測試一下
測試接口類
public interface AxTestService {
@Ax(value = "interface a method")
String a();
@Ax(value = "interface b method")
String b();
String c();
String d();
String e();
}
測試實(shí)現(xiàn)類
@Service
public class AxTestServiceImpl implements AxTestService {
@Override
public String a() {
System.out.println("impl a method");
return "a method\n";
}
@Override
public String b() {
System.out.println("impl b method");
return "b method\n";
}
@Ax(value = "impl c method")
@Override
public String c() {
System.out.println("impl c method");
return "c method\n";
}
@Ax(value = "impl d method")
@Override
public String d() {
System.out.println("impl d method");
return "d method\n";
}
@Override
public String e() {
System.out.println("impl e method");
return "e method\n";
}
}
測試WEB入口類
@RestController
@RequestMapping("/ax")
public class AxTestController {
@Resource
private AxTestService axTestService;
@GetMapping
public void a() {
System.out.println("controller-->> " + axTestService.a());
System.out.println("controller-->> " + axTestService.b());
System.out.println("controller-->> " + axTestService.c());
System.out.println("controller-->> " + axTestService.d());
System.out.println("controller-->> " + axTestService.e());
}
}
直接瀏覽器請求:http://host:port/ax時(shí),打印日志為:
before -- interface a method --->> a
impl a method
after -- interface a method ---->> a
controller-->> a method
before -- interface b method --->> b
impl b method
after -- interface b method ---->> b
controller-->> b method
before -- impl c method --->> c
impl c method
after -- impl c method ---->> c
controller-->> c method
before -- impl d method --->> d
impl d method
after -- impl d method ---->> d
controller-->> d method
impl e method
controller-->> e method
通過上面的日志可以看到,不論是在接口類中還是實(shí)現(xiàn)類中的方法上添加了Ax注解的都攔截到了,沒有被Ax注解的方法就不會被攔截,這已經(jīng)達(dá)到了我們想要的效果
必須要注意的是接口類必須要有實(shí)現(xiàn)類
必須要注意的是接口類必須要有實(shí)現(xiàn)類
必須要注意的是接口類必須要有實(shí)現(xiàn)類
重要的事情說三遍,除非你自己為這些接口創(chuàng)建動態(tài)代理類,不然Spring啟動直接報(bào)錯(cuò)!
不會有人還要問:你上面不是由Spring自動創(chuàng)建代理了嗎?