Reflections Java Library

repository·master·Indexed 26 days ago

https://github.com/ronmamo/reflections

A Java library for runtime metadata analysis that scans and indexes the classpath. It enables reverse transitive queries on the type system to find subtypes, annotated classes, and specific method signatures. Features include a functional API for composing queries via QueryFunction, a ReflectionUtils utility for direct metadata access, and the ability to persist scanned metadata to XML, JSON, or Java source code to optimize application startup.

Tokens
2.4K
Snippets
5
Records
10
Agent score
39%

What's inside Reflections

  1. Install Reflections via Maven or Gradle

    master

    Add the reflections dependency to your project to enable Java runtime metadata analysis. The current stable version is 0.10.2.

    <!-- Maven -->
    <dependency>
        <groupId>org.reflections</groupId>
        <artifactId>reflections</artifactId>
        <version>0.10.2</version>
    </dependency>
    // Gradle
    implementation 'org.reflections:reflections:0.10.2'
  2. Optimize startup by saving and collecting scanned metadata

    master

    To avoid expensive classpath scanning during application bootstrap, you can integrate Reflections with your build lifecycle:

    1. Use Reflections.save() to persist scanned metadata into XML or JSON during the build.
    2. Use Reflections.collect() at runtime to load the persisted metadata instead of re-scanning the classpath.

    For implementation details, see the reflections-maven project.

  3. Configure and initialize Reflections scanning

    master

    To use Reflections, you must create an instance using a ConfigurationBuilder. You should specify the packages to scan and use filterInputsBy with a FilterBuilder to include or exclude specific packages to avoid scanning unnecessary 3rd party libraries.

    Important: You must configure the specific Scanners you intend to query. If a scanner is not configured, the query will return an empty result. By default, only SubTypes and TypesAnnotated are used.

    import org.reflections.Reflections;
    import org.reflections.util.ConfigurationBuilder;
    import org.reflections.util.FilterBuilder;
    import static org.reflections.scanners.Scanners.*;
    
    // Typical usage: scan a package with default scanners (SubTypes, TypesAnnotated)
    Reflections reflections = new Reflections(
      new ConfigurationBuilder()
        .forPackage("com.my.project")
        .filterInputsBy(new FilterBuilder().includePackage("com.my.project")));
    
    // Advanced usage: scan with specific scanners and package filters
    Reflections reflections = new Reflections(
      new ConfigurationBuilder()
        .forPackage("com.my.project")
        .filterInputsBy(new FilterBuilder().includePackage("com.my.project").excludePackage("com.my.project.exclude"))
        .setScanners(TypesAnnotated, MethodsAnnotated, MethodsReturn));
  4. Merge annotations using AnnotationMergeCollector

    master

    You can use AnnotationMergeCollector to merge member values from similar annotations (e.g., merging annotations from a method and its declaring class) into a single hash map. This is useful for handling annotation hierarchies like Spring's @RequestMapping.

    // get all annotations of RequestMapping hierarchy (GetMapping, PostMapping, ...)
    Set<Class<?>> metaAnnotations =
      reflections.get(TypesAnnotated.getAllIncluding(RequestMapping.class.getName()).asClass());
    
    QueryFunction<Store, Map<String, Object>> queryAnnotations =
      // get all controller endpoint methods      
      MethodsAnnotated.with(metaAnnotations).as(Method.class)
        .map(method ->
          // get both method's + declaring class's RequestMapping annotations   
          get(Annotations.of(method.getDeclaringClass())
            .add(Annotations.of(method))
            .filter(a -> metaAnnotations.contains(a.annotationType())))
            .stream()
            // merge annotations' member values into a single hash map
            .collect(new AnnotationMergeCollector(method)));
    
    // apply query and map merged hashmap into java annotation proxy
    Set<RequestMapping> mergedAnnotations = 
      reflections.get(mergedAnnotation
        .map(map -> ReflectionUtils.toAnnotation(map, metaAnnotation)));
  5. Compose queries using QueryFunction

    master

    Each function also implements QueryFunction, providing a fluent functional interface to compose queries using methods like filter(), map(), flatMap(), and as().

    Common patterns include:

    • Filtering and casting: Use .filter() with predicates and .as(Class) to transform the result type.
    • Composing Scanners and ReflectionUtils: Use .flatMap() to bridge classpath-scanned metadata with Java Reflection API results.
    • Functions of functions: Use .map() to transform results from one query into the input for another.
    // filter, as/map
    QueryFunction<Store, Method> getters =
      Methods.of(C1.class)
        .filter(withModifier(Modifier.PUBLIC))
        .filter(withPrefix("get").and(withParametersCount(0)))
        .as(Method.class);
    
    // compose Scanners and ReflectionUtils functions 
    QueryFunction<Store, Method> methods = 
      SubTypes.of(type).asClass()  // <-- classpath scanned metadata
        .flatMap(Methods::of);     // <-- java reflection api
    
    // function of function
    QueryFunction<Store, Class<? extends Annotation>> queryAnnotations = 
      Annotations.of(Methods.of(C4.class))
        .map(Annotation::annotationType);
  6. Query metadata using the Reflections API

    master
    Once initialized, use the reflections.get(...) method with various Scanners to query the indexed metadata. The new 0.10+ API uses a functional approach with .asClass(), .as(Type.class), or similar mappings.
  7. Use ReflectionUtils for quick metadata access

    master

    For a more direct way to access metadata for a specific type T without manually managing a Reflections instance for every query, use ReflectionUtils. This provides static methods to retrieve super types, fields, constructors, methods, resources, and annotations.

    import static org.reflections.ReflectionUtils.*;
    
    // Example usage for a type T
    Set<Class<?>>    superTypes   = get(SuperTypes.of(T));
    Set<Field>       fields       = get(Fields.of(T));
    Set<Constructor> constructors = get(Constructors.of(T));
    Set<Method>      methods      = get(Methods.of(T));
    Set<URL>         resources    = get(Resources.with(T));
    
    Set<Annotation>  annotations  = get(Annotations.of(T));
    Set<Class<? extends Annotation>> annotationTypes = get(AnnotationTypes.of(T));
  8. Use QueryBuilder for direct or transitive values

    master

    Each Scanner and ReflectionUtils function implements QueryBuilder, allowing you to choose between direct results or the entire transitive hierarchy:

    • get(): Returns direct values only.
    • with() or of(): Returns all transitive values (the full hierarchy).

    For example, Scanners.SubTypes.get(T) returns only direct subtypes, whereas Scanners.SubTypes.of(T) returns the entire subtype hierarchy.

  9. Query member usages with MemberUsageScanner

    master
    The experimental MemberUsageScanner allows you to query for member usages (getMemberUsages()) of packages, types, or elements in the classpath. This can be used to identify dependencies and usages between different packages, layers, or modules.
  10. Persist metadata to Java source code with JavaCodeSerializer

    master
    The JavaCodeSerializer allows you to persist scanned metadata into generated Java source code. This enables accessing types and members in a strongly typed manner at runtime.