利用Spring掃包實現發現具體的註解類

需求描述:

在RocketMQ客戶端開發過程中,發現對於消費端的消費程序客戶端在開發過程中是無法提前預知的,即消費端的消費程序需要具體到業務中去實現。
因此,發現具體的消費方法是RocketMQ客戶端設計過程中一個不得不考慮的問題。爲了降低客戶端對上層業務系統的侵入性,計劃採用業務消費類
添加特定“註解”+客戶端“掃包”的方式來發現業務消費程序。由於Spring的掃包方法是經受過普遍考驗的,因此決定在Spring源碼的基礎上進行
修改實現。

測試代碼:

package com.abc.lottery.easymq;

import java.io.IOException;
import java.lang.annotation.Annotation;
import java.util.Collections;
import java.util.Iterator;
import java.util.Set;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.core.io.Resource;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import org.springframework.core.io.support.ResourcePatternResolver;
import org.springframework.core.type.classreading.CachingMetadataReaderFactory;
import org.springframework.core.type.classreading.MetadataReader;
import org.springframework.core.type.classreading.MetadataReaderFactory;
import org.springframework.util.ClassUtils;
import org.springframework.util.StringUtils;
import org.springframework.util.SystemPropertyUtils;

import com.google.common.collect.Sets;
import com.abc.lottery.easymq.handler.MQConsumerAnnotation;

public class ScanPackageTest
{
    private final static Log log = LogFactory.getLog(ScanPackageTest.class);
    //掃描  scanPackages 下的文件匹配符
    public static final String DEFAULT_RESOURCE_PATTERN = "**/*.class";

    /**
     * 找到scanPackages下帶註解annotation的全部類信息
     * @param scanPackages 掃包路徑,多個路徑用","分割
     * @param annotation 註解類
     */
    public static Set<String> findPackageAnnotationClass(String scanPackages, Class<? extends Annotation> annotation)
    {
        if (StringUtils.isEmpty(scanPackages))
        {
            return Sets.newHashSet();
        }

        // 排重包路徑,避免父子路徑重複掃描
        Set<String> packages = checkPackages(scanPackages);
        //獲取Spring資源解析器
        ResourcePatternResolver resourcePatternResolver = new PathMatchingResourcePatternResolver();
        //創建Spring中用來讀取resource爲class的工具類
        MetadataReaderFactory metadataReaderFactory = new CachingMetadataReaderFactory(resourcePatternResolver);

        Set<String> fullClazzSet = Sets.newHashSet();
        for (String basePackage : packages)
        {
            if (StringUtils.isEmpty(basePackage))
            {
                continue;
            }
            String packageSearchPath = ResourcePatternResolver.CLASSPATH_ALL_URL_PREFIX
                    + ClassUtils.convertClassNameToResourcePath(SystemPropertyUtils.resolvePlaceholders(basePackage))
                    + "/" + DEFAULT_RESOURCE_PATTERN;
            try
            {
                //獲取packageSearchPath下的Resource,這裏得到的Resource是Class信息
                Resource[] resources = resourcePatternResolver.getResources(packageSearchPath);
                for (Resource resource : resources)
                {
                    //檢查resource,這裏的resource都是class
                    String fullClassName = loadClassName(metadataReaderFactory, resource);
                    if (isAnnotationClass(fullClassName, annotation))
                    {
                        fullClazzSet.add(fullClassName);
                    }
                }
            }
            catch (Exception e)
            {
                log.error("獲取包下面的類信息失敗,package:" + basePackage, e);
            }
        }
        return fullClazzSet;
    }

    /**
     * 排重、檢測package父子關係,避免多次掃描
     * @param scanPackages 掃包路徑
     * @return 返回全部有效的包路徑信息
     */
    private static Set<String> checkPackages(String scanPackages)
    {
        if (StringUtils.isEmpty(scanPackages))
        {
            return Sets.newHashSet();
        }
        Set<String> packages = Sets.newHashSet();
        //排重路徑
        Collections.addAll(packages, scanPackages.split(","));
        for (String packageStr : packages.toArray(new String[packages.size()]))
        {
            if (StringUtils.isEmpty(packageStr) || packageStr.equals(".") || packageStr.startsWith("."))
            {
                continue;
            }
            if (packageStr.endsWith("."))
            {
                packageStr = packageStr.substring(0, packageStr.length() - 1);
            }
            Iterator<String> packageIte = packages.iterator();
            boolean needAdd = true;
            while (packageIte.hasNext())
            {
                String pack = packageIte.next();
                if (packageStr.startsWith(pack + "."))
                {
                    //如果待加入的路徑是已經加入的pack的子集,不加入
                    needAdd = false;
                }
                else if (pack.startsWith(packageStr + "."))
                {
                    //如果待加入的路徑是已經加入的pack的父集,刪除已加入的pack
                    packageIte.remove();
                }
            }
            if (needAdd)
            {
                packages.add(packageStr);
            }
        }
        return packages;
    }

    /**
     * 加載資源,根據resource獲取className
     * @param metadataReaderFactory spring中用來讀取resource爲class的工具
     * @param resource 這裏的資源就是一個Class
     */
    private static String loadClassName(MetadataReaderFactory metadataReaderFactory, Resource resource)
            throws IOException
    {
        try
        {
            if (resource.isReadable())
            {
                MetadataReader metadataReader = metadataReaderFactory.getMetadataReader(resource);
                if (metadataReader != null)
                {
                    return metadataReader.getClassMetadata().getClassName();
                }
            }
        }
        catch (Exception e)
        {
            log.error("根據Spring resource獲取類名稱失敗", e);
        }
        return null;
    }

    /**
     * 判斷類是否包含特定的註解
     * @param fullClassName
     * @param annotationClass
     * @return
     * @throws ClassNotFoundException
     */
    private static boolean isAnnotationClass(String fullClassName, Class<? extends Annotation> annotationClass)
            throws ClassNotFoundException
    {
        //利用反射,根據類名獲取類的全部信息
        Class<?> clazz = Class.forName(fullClassName);
        //獲取該類的註解信息
        Annotation annotation = clazz.getAnnotation(annotationClass);
        if (annotation != null)
        {//包含annotationClass註解
            return true;
        }
        return false;
    }
}

輸出結果

public static void main(String[] args)
    {
        String packages = "com.abc.lottery.easymq";
        System.out.println("annotation class:" + findPackageAnnotationClass(packages, MQConsumerAnnotation.class));
    }

輸出結果:annotation class:[com.abc.lottery.easymq.handler.PaySuccessMQHandler]

結果說明

自己定義了一個註解MQConsumerAnnotation
@Documented
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface MQConsumerAnnotation
{
    String topic();

    String tags();

}

PaySuccessMQHandler類是com.abc.lottery.easymq包路徑下唯一一個添加MQConsumerAnnotation註解的類
@MQConsumerAnnotation(topic = "paySuccessTopic", tags = "tagA")
public class PaySuccessMQHandler implements MQRecMsgHandler
{
    public void handle(List<String> msg) throws Exception
    {
        System.out.println(msg);
    }
}

因此,掃描com.abc.lottery.easymq包路徑得到唯一的一個類PaySuccessMQHandler的全路徑信息。後面,可以根據PaySuccessMQHandler的類信息,通過反射獲取類實例,完成掃包獲取消費方法。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章