利用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的类信息,通过反射获取类实例,完成扫包获取消费方法。

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值