Java 与代码设计 / 实践记录

reflections使用总结

从解析 JAR、扫描注解的需求出发,阅读 Reflections 的配置、扫描和索引查询流程。

一、项目背景

背景:项目中有解析jar的业务场景,通过解析指定注解标注的class获取指定注解标注的方法

二、reflections能力概览

reflections的部分能力如下

  • 类处理
    • 获取jar包中的所有class对象
    • 获取jar中指定注解的所有class对象
    • 获取jar中指定类及其所有的子类的class对象
    • …
  • 方法处理
    • 获取jar包中所有的method对象
    • 获取jar中被指定注解标记的method对象
    • 获取jar中返回值为指定类的method对象
    • 获取jar中入参为指定类的method对象
    • …
  • 字段处理
    • 获取jar中被指定注解标记的filed对象
    • …
  • 资源处理
    • 获取jar中resource中的properties文件
    • …
  • 工具类:
    • 过滤类中方法(类似stream流式写法)
    • …
  • 序列化为xml/json
  • 更多能力见官方文档:https://github.com/ronmamo/reflections

三、使用demo

public Set<Class<?>> queryClass(String jarPath, String url) throws MalformedURLException {
  // 根据jarPath去构建classloader,此处是自定义classloader
        WarClassLoader warClassLoader =
                SwaggerGenerator.getWarClassLoader(jarPath, Thread.currentThread().getContextClassLoader());

  // 构建configuration,设置classloader和要解析的url
        ConfigurationBuilder config = ConfigurationBuilder.build();
        config.addClassLoader((URLClassLoader) warClassLoader.getParent());
        config.setScanners(new ResourcesScanner(), new TypeAnnotationsScanner(), new SubTypesScanner());
        config.getUrls().clear();
        config.getUrls().add(new URL(url)); // 需要解析哪一个jar就传入对应的url地址

  // 构建Reflection对象,获取符合指定注解的class
        Reflections ref = new Reflections(config);
        Stream<Class<?>> apiStream = ref.getTypesAnnotatedWith(Api.class).stream();
        Stream<Class<?>> restStream = ref.getTypesAnnotatedWith(RestController.class).stream();
        Stream<Class<?>> apiAndDef = Stream.concat(apiStream, restStream);

        // 反击class的set集合
        return apiAndDef.collect(Collectors.toSet());
    }
  1. 通过reflections.classes() 获取所有的class文件
  2. reflections需要传入configuration才能进行初始化
  3. configuration配置
    1. classloader:声明要扫描哪一个classloader下的class
    2. scanner:声明需要扫描哪些内容:type和annotation的scanner
    3. url:真实要扫描的jar执行流程
  4. 初始化configuration并设置
    1. classloader:声明要扫描哪一个classloader下的class
    2. scanner:声明需要扫描哪些内容:type和annotation的scanner
    3. url:真实要扫描的jar

四、主要流程源码分析

  1. 初始化, 调用 new Reflections(config)执行如下

    public Reflections(final Configuration configuration) {
      // 设置configuration
            this.configuration = configuration;
      // 设置存在class元信息的store对象,其内部维护 Map<String, Multimap<String, String>> storeMap
            store = new Store(configuration);
    
            if (configuration.getScanners() != null && !configuration.getScanners().isEmpty()) {
                //inject to scanners
                for (Scanner scanner : configuration.getScanners()) {
                    scanner.setConfiguration(configuration);
                    scanner.setStore(store.getOrCreate(scanner.getClass().getSimpleName()));
                }
    // 扫描configuration中持有的所有url
                scan();
            }
      }
    
  2. 扫描url, 调用scan()执行如下

    protected void scan() {
            // 一些代码...
    
      // 遍历configuration中的所有url
            for (final URL url : configuration.getUrls()) {
                try {
                  // 如果configuration配置了线程池,则把scan加入到线程池异步执行
                    if (executorService != null) {
                        futures.add(executorService.submit(new Runnable() {
                            public void run() {
                                if (log != null && log.isDebugEnabled()) log.debug("[" + Thread.currentThread().toString() + "] scanning " + url);
                                scan(url);
                            }
                        }));
                    } else {
                        scan(url);
                    }
                    scannedUrls++;
                } catch (ReflectionsException e) {
                    if (log != null && log.isWarnEnabled()) log.warn("could not create Vfs.Dir from url. ignoring the exception and continuing", e);
                }
            }
    
            // 一些代码...
    }
    
        public void scan(URL url) {
          // 通过迭代器遍历url中的所有符合要求的文件
            for (final Vfs.File file : Vfs.fromURL(url).getFiles()) {
                String input = file.getRelativePath().replace('/', '.');
                if (configuration.getInputsFilter() == null || configuration.getInputsFilter().apply(input)) {
                    Object classObject = null;
                  // 遍历configuration中的声明的scanner实现类,扫描不同的内容并加入store
                    for (Scanner scanner : configuration.getScanners()) {
                        try {
                            if (scanner.acceptsInput(input)) {
                                classObject = scanner.scan(file, classObject);
                            }
                        } catch (Exception e) {
    
                    }
                }
            }
        }
    
  3. 存储, 根据传入的scanner的不同,执行不同的scan扫描逻辑,这里以new TypeAnnotationsScanner()为例

    public void scan(final Object cls) {
      // 获取class name
    final String className = getMetadataAdapter().getClassName(cls);
    
            for (String annotationType : (List<String>) getMetadataAdapter().getClassAnnotationNames(cls)) {
    
                if (acceptResult(annotationType) ||
                    annotationType.equals(Inherited.class.getName())) { //as an exception, accept Inherited as well
                    // 以(注解,class name)为键值对,存入private Multimap<String, String> store;
                    getStore().put(annotationType, className);
                }
            }
        }
    
  4. 获取class对象, 通过注解去store中获取类集合

    public Set<Class<?>> getTypesAnnotatedWith(final Class<? extends Annotation> annotation, boolean honorInherited) {
      // 直接去store中根据注解name去获取对应的class列表
            Iterable<String> annotated = store.get(index(TypeAnnotationsScanner.class), annotation.getName());
            Iterable<String> classes = getAllAnnotated(annotated, annotation.isAnnotationPresent(Inherited.class), honorInherited);
            return Sets.newHashSet(concat(forNames(annotated, loaders()), forNames(classes, loaders())));
    }
    

总结

  1. reflections需要传入configuration才能进行初始化

  2. configuration配置

    1. classloader:声明要扫描哪一个classloader下的class
    2. scanner:声明需要扫描哪些内容:type和annotation的scanner
    3. url:真实要扫描的jar
  3. 执行流程

    1. 初始化configuration并设置

    2. classloader:声明要扫描哪一个classloader下的class

    3. scanner:声明需要扫描哪些内容:type和annotation的scanner

    4. url:真实要扫描的jar

    5. 执行scan,扫描指定url

    6. 根据设置的scanner,把对应的信息存入store

    7. 根据key去store获取指定信息