實(shí)戰(zhàn)Spring之注解處理器

實(shí)戰(zhàn)Spring之注解處理器

需求場(chǎng)景

有時(shí)候我們希望定義一個(gè)特定的注解,被注解打標(biāo)過的方法能夠被代理,完成一些特定的操作。

當(dāng)然我們可以通過Spring的切面去完成比如:

@Around(value = "execution(* com.xxx.xxx.xxx.dao..*(..))")

但這種方式感覺還是太麻煩了,我們希望像事務(wù)注解@Transaction 一樣,打上標(biāo)記就會(huì)被代理,不需要定義各種表達(dá)式切面。

另外簡(jiǎn)單點(diǎn)、通用點(diǎn)、好理解一點(diǎn)。。。

另外簡(jiǎn)單點(diǎn)、通用點(diǎn)、好理解一點(diǎn)。。。

另外簡(jiǎn)單點(diǎn)、通用點(diǎn)、好理解一點(diǎn)。。。

實(shí)現(xiàn)思路

其實(shí)本質(zhì)的做法也是通過切面去完成,不過@Around 是基于表達(dá)式去處理,而我們希望能通過注解方式,來決定是否需要代理。(PS: 表達(dá)式也能實(shí)現(xiàn)注解攔截)

這里涉及到切面的兩個(gè)點(diǎn):

  • Advice : 你可以理解為攔截器
  • Pointcut: 你可以理解規(guī)則匹配器

當(dāng)我們梳理思路的時(shí)候只需要思考:

  1. Spring在bean處理的時(shí)候,會(huì)遍歷每個(gè)類和方法。
  2. 這個(gè)時(shí)候在遍歷時(shí),去判斷每個(gè)方法是否符合pointcut規(guī)則,滿足的話則進(jìn)行代理
  3. 那么一旦代理的話,肯定是需要有具體的代理業(yè)務(wù)攔截邏輯的,advice就是邏輯處理攔截器。
  4. 我們只需要將兩者結(jié)合形成一個(gè)工廠類每次去找被代理后的邏輯就行了。

好,那么我們看如何去實(shí)現(xiàn)?

實(shí)現(xiàn)代碼

1. Pointcut

我們先來定義規(guī)則,我們希望特定的注解打在方法上,這些方法能夠被代理,然后查看Pointcut 的實(shí)現(xiàn)類有一個(gè)

StaticMethodMatcherPointcut

/**
 * 特定注解攔截器
 *
 * @author : liukx
 * @time : 2020/7/9 - 20:02
 */
public class AnnotationAttributeSourcePointcut extends StaticMethodMatcherPointcut implements Serializable {

    /**
     * 需要被攔截代理的注解列表
     */
    private Set<Class<? extends Annotation>> annotationsOperation = new LinkedHashSet<>(8);

    public void addAnnotations(Class<? extends Annotation> annotation) {
        annotationsOperation.add(annotation);
    }

    /**
     * 符合該注解的通通被代理起來
     *
     * @param method
     * @param targetClass
     * @return
     */
    @Override
    public boolean matches(Method method, Class<?> targetClass) {
        if (CacheParseUtil.isContainAnnotations(annotationsOperation, method)) {
            return true;
        }
        return false;
    }

    /**
     * 遍歷該方法是否包含特定的注解
     *
     * @param annotations
     * @param element
     * @return
     */
    public boolean isContainAnnotations(Set<Class<? extends Annotation>> annotations, AnnotatedElement element) {
        boolean isContain = false;
        for (Class<? extends Annotation> annotation : annotations) {
            if (AnnotatedElementUtils.hasAnnotation(element, annotation)) {
                isContain = true;
                break;
            }
        }
        return isContain;
    }
}

2. Advice

我們?cè)诘谝徊蕉x了匹配規(guī)則,一旦被規(guī)則match匹配上,那么對(duì)應(yīng)的邏輯希望交給advice。

import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;
import org.springframework.aop.framework.ReflectiveMethodInvocation;
import org.springframework.beans.factory.annotation.Autowired;

import java.io.Serializable;
import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.util.List;

/**
 * 實(shí)現(xiàn)緩存的攔截器
 *
 * @author : liukx
 * @time : 2020/7/9 - 20:08
 */
public class AnnotationInterceptor implements MethodInterceptor, Serializable {

    @Autowired(required = false)
    private List<AnnotationProcessService> cacheProcessServices;

    @Override
    public Object invoke(MethodInvocation invocation) throws Throwable {
        Object proceed = null;
        if (cacheProcessServices != null && invocation instanceof ReflectiveMethodInvocation) {
            ReflectiveMethodInvocation methodInvocation = (ReflectiveMethodInvocation)invocation;
            Method method = invocation.getMethod();
            Annotation[] annotations = method.getAnnotations();
            for (int i = 0; i < annotations.length; i++) {
                Annotation annotation = annotations[i];
                for (int j = 0; j < cacheProcessServices.size(); j++) {
                    AnnotationProcessService cache = cacheProcessServices.get(i);
                    if (annotation.annotationType() == cache.annotation()) {
                        proceed = cache.invokeWithinTransaction(methodInvocation);
                    }
                }
            }
            return proceed;
        }
        return invocation.proceed();
    }
}

AnnotationProcessService

定義一個(gè)這樣的接口是希望,后續(xù)如果還有其他注解需要處理時(shí),只需要實(shí)現(xiàn)該接口準(zhǔn)備好注解類和邏輯方法,我們可以直接回調(diào)它處理,這樣會(huì)更為通用。

import org.springframework.aop.framework.ReflectiveMethodInvocation;
import org.springframework.core.Ordered;

import java.lang.annotation.Annotation;

/**
 * 注解的執(zhí)行器
 *
 * @author liukaixiong
 * @Email liukx@elab-plus.com
 * @date 2021/9/27 - 10:49
 */
public interface AnnotationProcessService extends Ordered {

    @Override
    default int getOrder() {
        return LOWEST_PRECEDENCE;
    }

    /**
     * 具體的注解
     *
     * @return
     */
    public Class<? extends Annotation> annotation();

    /**
     * 上面匹配到的注解會(huì)被觸發(fā),盡量不要對(duì)結(jié)果做改變。
     *
     * @param invocation
     * @return
     * @throws Throwable
     */
    public Object invokeWithinTransaction(ReflectiveMethodInvocation invocation) throws Throwable;
}

被代理的類,先走這個(gè)接口類進(jìn)行注解annotation匹配,然后在流轉(zhuǎn)到invokeWithinTransaction 方法。這樣更為通用。

3. DefaultBeanFactoryPointcutAdvisor

org.springframework.aop.support.DefaultBeanFactoryPointcutAdvisor 這個(gè)是Spring內(nèi)部提供的類,用于組合PointcutAdvisor的。

我們只需要在配置文件進(jìn)行組合

@Configuration
public class AopConfig {

    @Bean
    @Role(BeanDefinition.ROLE_INFRASTRUCTURE)
    @ConditionalOnBean(AnnotationProcessService.class)
    public DefaultBeanFactoryPointcutAdvisor transactionAdvisor(
        AnnotationAttributeSourcePointcut annotationAttributeSourcePointcut) {
        DefaultBeanFactoryPointcutAdvisor advisor = new DefaultBeanFactoryPointcutAdvisor();
        // 具體的攔截器
        advisor.setAdvice(annotationInterceptor());
        // 需要被攔截方法的規(guī)則判斷器,一旦符合,才會(huì)被代理給攔截器處理
        advisor.setPointcut(annotationAttributeSourcePointcut);
        return advisor;
    }

    @Bean
    @Role(BeanDefinition.ROLE_INFRASTRUCTURE)
    @ConditionalOnBean(AnnotationProcessService.class)
    public AnnotationAttributeSourcePointcut annotationAttributeSourcePointcut(
        List<AnnotationProcessService> annotationProcessServiceList) {
        // 該類是用作比對(duì)方法的注解是否符合代理的條件
        AnnotationAttributeSourcePointcut cacheAttributeSourcePointcut = new AnnotationAttributeSourcePointcut();
        annotationProcessServiceList.forEach((annotation) -> {
            cacheAttributeSourcePointcut.addAnnotations(annotation.annotation());
        });
        return cacheAttributeSourcePointcut;
    }

    @Bean
    @Role(BeanDefinition.ROLE_INFRASTRUCTURE)
    @ConditionalOnBean(AnnotationProcessService.class)
    public AnnotationInterceptor annotationInterceptor() {
        AnnotationInterceptor interceptor = new AnnotationInterceptor();
        return interceptor;
    }
}

后續(xù)你需要定義各種自定義的注解只需要實(shí)現(xiàn)AnnotationProcessService 接口就行了,簡(jiǎn)單方便好拓展。

測(cè)試用例

我們來定義一個(gè)@AuthorDescription注解處理器,

/**
 * 對(duì)于功能定義的一些攔截描述
 *
 * @author liukaixiong
 * @Email liukx@elab-plus.com
 * @date 2021/9/27 - 11:10
 */
public class AuthorDescriptionAnnotationProcess implements AnnotationProcessService {
    private Logger logger = LoggerFactory.getLogger(getClass());
     
    /**
     * 表示只處理@AuthorDescription注解內(nèi)容 
     */ 
    @Override
    public Class<? extends Annotation> annotation() {
        return AuthorDescription.class;
    }

    @Override
    public Object invokeWithinTransaction(ReflectiveMethodInvocation invocation) throws Throwable {
        Method method = invocation.getMethod();
        AuthorDescription authorDescription = AnnotationUtils.getAnnotation(method, AuthorDescription.class);
        String clazzName = method.getDeclaringClass().getSimpleName();
        String name = method.getName();
        String methodName = clazzName + "." + name;
        Object[] arguments = invocation.getArguments();  
        // 業(yè)務(wù)邏輯處理
        return invocation.proceed();
    }
}

隨便定義的測(cè)試方法

public class TestAuthorAnnotation {

    @AuthorDescription(modulesName = "user", describe = "這是一個(gè)測(cè)試", nickname = {"liukx",
        "jay"}, searchKey = "${request[0].id}-${request[1].username}")
    public String test(Map<String, String> request, UserModel userModel) {
        return "OK";
    }

}

測(cè)試類

@RunWith(SpringRunner.class)
//@EnableCaching
// 這個(gè)是開啟切面,必要的。
@EnableAspectJAutoProxy
@SpringBootTest(classes = {AopConfig.class,AuthorDescriptionAnnotationProcess.class,
    TestAuthorAnnotation.class})
public class AuthorDescriptionAnnotationProcessTest {

    @Autowired
    private TestAuthorAnnotation testAnnotation;

    @Test
    public void testInvokeWithinTransaction() {
        Map<String, String> request = new HashMap<>();
        request.put("id", "1314");
        request.put("username", "jayzhou");
        request.put("sex", "MAN");
        request.put("age", "13");

        UserModel userModel = new UserModel();
        userModel.setUserId("5555");
        userModel.setUsername("liukx");

        String test = testAnnotation.test(request, userModel);
        System.out.println(test);
    }

}

為了觀看體驗(yàn),有的無關(guān)緊要的代碼我就不貼了,希望大家更關(guān)注核心邏輯流轉(zhuǎn)。

如果你有疑問,歡迎留言交流,我看到了會(huì)第一時(shí)間答復(fù)你。

如果你覺得有收獲,可以點(diǎn)個(gè)關(guān)注點(diǎn)贊支持下,謝謝啦。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

推薦閱讀更多精彩內(nèi)容