zoukankan      html  css  js  c++  java
  • java 注解扫描

    最近要实现一个项目启动时进行注解扫描的功能,用于实现方法的动态加载.实际实现版本有两个版本,第一个版本是直接百度的现成工具类,可以基本实现功能,但是实现的效率和安全性都存在未知性,所以改进了第二个版本,通过类库: classgraph 来实现.

    • 版本1 自定义工具类
    package a.custom.utils;
    
    import a.custom.annotation.BizPermission;
    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.stereotype.Component;
    import org.springframework.util.SystemPropertyUtils;
    
    import java.io.IOException;
    import java.lang.annotation.Annotation;
    import java.lang.reflect.Method;
    import java.lang.reflect.Modifier;
    import java.util.Collections;
    import java.util.HashSet;
    import java.util.Iterator;
    import java.util.Set;
    
    /**
     * @author 123
     * @Description
     * @create 2021/11/3 11:12
     */
    @Component
    public class PackageUtils {
    
        private final static Log log = LogFactory.getLog(PackageUtils.class);
        //扫描  scanPackages 下的文件的匹配符
        protected static final String DEFAULT_RESOURCE_PATTERN = "**/*.class";
    
        /**
         * 查询指定注解信息
         * @param scanPackages
         * @param annotation
         * @return
         * @throws ClassNotFoundException
         */
        public static Set<Annotation> findClassAnnotations(String scanPackages, Class<? extends Annotation> annotation) throws ClassNotFoundException {
            //获取所有的类
            Set<String> clazzSet = findPackageClass(scanPackages);
            Set<Annotation> methods = new HashSet<>();
            //遍历类,查询相应的annotation方法
            for (String clazz : clazzSet) {
                Set<Annotation> ms = findAnnotations(clazz, annotation);
                if (ms != null) {
                    methods.addAll(ms);
                }
            }
            return methods;
        }
    
        /**
         * 结合spring的类扫描方式
         * 根据需要扫描的包路径及相应的注解,获取最终测method集合
         * 仅返回public方法,如果方法是非public类型的,不会被返回
         * 可以扫描工程下的class文件及jar中的class文件
         *
         * @param scanPackages
         * @param annotation
         * @return
         */
        public static Set<Method> findClassAnnotationMethods(String scanPackages, Class<? extends Annotation> annotation) {
            //获取所有的类
            Set<String> clazzSet = findPackageClass(scanPackages);
            Set<Method> methods = new HashSet<>();
            //遍历类,查询相应的annotation方法
            for (String clazz : clazzSet) {
                try {
                    Set<Method> ms = findAnnotationMethods(clazz, annotation);
                    if (ms != null) {
                        methods.addAll(ms);
                    }
                } catch (ClassNotFoundException ignore) {
                }
            }
            return methods;
        }
    
        /**
         * 根据扫描包的,查询下面的所有类
         *
         * @param scanPackages 扫描的package路径
         * @return
         */
        public static Set<String> findPackageClass(String scanPackages) {
            if (StringUtils.isEmptyOrNull(scanPackages)) {
                return Collections.EMPTY_SET;
            }
            //验证及排重包路径,避免父子路径多次扫描
            Set<String> packages = checkPackage(scanPackages);
            ResourcePatternResolver resourcePatternResolver = new PathMatchingResourcePatternResolver();
            MetadataReaderFactory metadataReaderFactory = new CachingMetadataReaderFactory(resourcePatternResolver);
            Set<String> clazzSet = new HashSet<String>();
            for (String basePackage : packages) {
                if (StringUtils.isEmptyOrNull(basePackage)) {
                    continue;
                }
                String packageSearchPath = ResourcePatternResolver.CLASSPATH_ALL_URL_PREFIX +
                        org.springframework.util.ClassUtils.convertClassNameToResourcePath(SystemPropertyUtils.resolvePlaceholders(basePackage)) + "/" + DEFAULT_RESOURCE_PATTERN;
                try {
                    Resource[] resources = resourcePatternResolver.getResources(packageSearchPath);
                    for (Resource resource : resources) {
                        //检查resource,这里的resource都是class
                        String clazz = loadClassName(metadataReaderFactory, resource);
                        clazzSet.add(clazz);
                    }
                } catch (Exception e) {
                    log.error("获取包下面的类信息失败,package:" + basePackage, e);
                }
    
            }
            return clazzSet;
        }
    
        /**
         * 排重、检测package父子关系,避免多次扫描
         *
         * @param scanPackages
         * @return 返回检查后有效的路径集合
         */
        private static Set<String> checkPackage(String scanPackages) {
            if (StringUtils.isEmptyOrNull(scanPackages)) {
                return Collections.EMPTY_SET;
            }
            Set<String> packages = new HashSet<>();
            //排重路径
            Collections.addAll(packages, scanPackages.split(","));
            String[] strings = packages.toArray(new String[packages.size()]);
            for (String pInArr : strings) {
                if (StringUtils.isEmptyOrNull(pInArr) || pInArr.equals(".") || pInArr.startsWith(".")) {
                    continue;
                }
                if (pInArr.endsWith(".")) {
                    pInArr = pInArr.substring(0, pInArr.length() - 1);
                }
                Iterator<String> packageIte = packages.iterator();
                boolean needAdd = true;
                while (packageIte.hasNext()) {
                    String pack = packageIte.next();
                    if (pInArr.startsWith(pack + ".")) {
                        //如果待加入的路径是已经加入的pack的子集,不加入
                        needAdd = false;
                    } else if (pack.startsWith(pInArr + ".")) {
                        //如果待加入的路径是已经加入的pack的父集,删除已加入的pack
                        packageIte.remove();
                    }
                }
                if (needAdd) {
                    packages.add(pInArr);
                }
            }
            return packages;
        }
    
    
        /**
         * 加载资源,根据resource获取className
         *
         * @param metadataReaderFactory spring中用来读取resource为class的工具
         * @param resource              这里的资源就是一个Class
         * @throws IOException
         */
        private static String loadClassName(MetadataReaderFactory metadataReaderFactory, Resource resource) {
            try {
                if (resource.isReadable()) {
                    MetadataReader metadataReader = metadataReaderFactory.getMetadataReader(resource);
                    if (metadataReader != null) {
                        return metadataReader.getClassMetadata().getClassName();
                    }
                }
            } catch (Exception e) {
                log.error("根据resource获取类名称失败", e);
            }
            return null;
        }
    
        /**
         * 把action下面的所有method遍历一次,标记他们是否需要进行敏感词验证
         * 如果需要,放入cache中
         *
         * @param fullClassName
         */
        public static Set<Method> findAnnotationMethods(String fullClassName, Class<? extends Annotation> anno) throws ClassNotFoundException {
            Set<Method> methodSet = new HashSet<>();
            Class<?> clz = Class.forName(fullClassName);
            Method[] methods = clz.getDeclaredMethods();
            for (Method method : methods) {
                if (method.getModifiers() != Modifier.PUBLIC) {
                    continue;
                }
                Annotation annotation = method.getAnnotation(anno);
                if (annotation != null) {
                    methodSet.add(method);
                }
            }
            return methodSet;
        }
    
        /**
         * 查询指定注解信息
         * @param fullClassName
         * @param anno
         * @return
         * @throws ClassNotFoundException
         */
        public static Set<Annotation> findAnnotations(String fullClassName, Class<? extends Annotation> anno) throws ClassNotFoundException {
            Set<Annotation> methodSet = new HashSet<>();
            Class<?> clz = Class.forName(fullClassName);
            Method[] methods = clz.getDeclaredMethods();
            for (Method method : methods) {
                if (method.getModifiers() != Modifier.PUBLIC) {
                    continue;
                }
                Annotation annotation = method.getAnnotation(anno);
                if (annotation != null) {
                    if(methodSet.contains(annotation)){
                        log.error("注解不存在");
                    }
                    methodSet.add(annotation);
                }
            }
            return methodSet;
        }
    
        public static void main(String[] args) throws ClassNotFoundException {
            String packages = "scan.package";
            Set<Annotation> classAnnotationMethods = findClassAnnotations(packages, BizPermission.class);
            classAnnotationMethods.forEach(set->{
                BizPermission annotation = (BizPermission)set;
                System.out.println(annotation.code()+" "+annotation.name());
            });
        }
    
    }
    

    该版本功能上只提供了方法注解的查询,类注解的需要自己再完善;优点是原生实现,不需要额外的包依赖

    • 版本2 classgraph

    需要引入classgraph maven依赖

            <dependency>
                <groupId>io.github.classgraph</groupId>
                <artifactId>classgraph</artifactId>
                <version>4.8.132</version>
            </dependency>
    

    查询方法注解

    package a.custom.utils;
    
    import io.github.classgraph.AnnotationParameterValueList;
    import io.github.classgraph.ClassGraph;
    import io.github.classgraph.ClassInfoList;
    import io.github.classgraph.ScanResult;
    
    import java.lang.annotation.Annotation;
    import java.util.List;
    import java.util.stream.Collectors;
    
    /**
     * @author 123
     * @Description 类工具
     * @create 2021/11/18 9:56
     */
    public class ClassUtils {
    
        /**
         * 扫描指定方法注解
         * @param pkg 扫描包
         * @param annotation 获取的注解类型
         * @return 返回注解参数 [{name:name,value:value}]
         */
        public static List<AnnotationParameterValueList> methodAnnotionScan(String pkg, Annotation annotation) {
            try (ScanResult scanResult =                // Assign scanResult in try-with-resources
                         new ClassGraph()                    // Create a new ClassGraph instance
                                 .enableAllInfo()                // Scan classes, methods, fields, annotations
                                 .acceptPackages(pkg)      // Scan com.xyz and subpackages
                                 .scan()) {                      // Perform the scan and return a ScanResult
                // 获取类里指定方法注解
                ClassInfoList ciList = scanResult.getClassesWithMethodAnnotation(annotation.getClass());
                // 指定方法注解内容提取,提取流程: ClassInfoList -> ClassInfo -> MethodInfo -> AnnotationInfo -> ParameterValues -> AnnotationParameterValue
                return ciList.stream().flatMap(ci->ci.getMethodInfo().stream().filter(me->me.getAnnotationInfo(annotation.getClass())!=null)
                        .map(me->me.getAnnotationInfo(annotation.getClass()).getParameterValues())).collect(Collectors.toList());
            }
        }
    }
    

    classgraph是一个基于jvm语言进行类路径和包扫描的开源工具包.基于jvm语言,它拥有基于分析或响应其他代码属性而编写代码的能力.拥有了更灵活的扩展性.

    根据类的层级关系,它的数据提取层级如下:

    ClassInfoList -> ClassInfo -> MethodInfo -> AnnotationInfo -> ParameterValues -> AnnotationParameterValue

    • 常用的反射工具类库
    Reflections
    Corn Classpath Scanner
    annotation-detector
    Scannotation
    Sclasner
    Annovention
    ClassIndex (compiletime annotation scanner/processor)
    Jandex (Java annotation indexer, part of Wildfly)
    Spring has built-in classpath scanning
    Hibernate has the class org.hibernate.ejb.packaging.Scanner.
    extcos -- the Extended Component Scanner
    Javassist
    ObjectWeb ASM
    QDox, a fast Java source parser and indexer
    bndtools, which is able to "crawl"/parse the bytecode of class files to find all imports/dependencies, among other things.
    coffea, a command line tool and Python library for analyzing static dependences in Java bytecode
    org.clapper.classutil.ClassFinder
    com.google.common.reflect.ClassPath
    jdependency
    Burningwave Core
    
    • 参考资料:

    classgraph

    喜欢关注一下,不喜欢点评一下
  • 相关阅读:
    初次用SqlServer查看本地的Excel文件时需要注意的地方
    Oracle字符集的查看查询和Oracle字符集的设置修改(转)
    Oracle查字符集查版本号
    NoClassDefFoundError: org/springframework/boot/bind/RelaxedDataBinder
    Unknown initial character set index '255' received from server. Initial client character 解决方法
    Oracle查看用户密码过期,修改永不过期
    XPath 爬虫解析库
    Python 之 PyMySQL 安装和使用
    Python使用场景和应用领域
    Python基本语法变量
  • 原文地址:https://www.cnblogs.com/chengmuyu/p/15571230.html
Copyright © 2011-2022 走看看