Search code examples
javareflectionjava-17

I want to use reflection in Java 17 for all JRE classes. Is there a way to do this easily?


I could assemble a very long list of --add-opens flags, but I was hoping there was a cleaner way to allow reflection to all JRE classes. My code needs to use reflection for many operations, for instance to get the files[] value of a java.util.logging.FileHandler, but there are many cases like this, and I don't have time to extensively test every possible operation to find all the ways that this could break at runtime.

Could somebody either provide 1) a short command line launch param to enable this, or 2) a complete list of --add-opens commands to enable reflection for every JRE class?

Answers telling me not to use reflection are unhelpful and will be downvoted.


Solution

  • As I noted in a comment, this seems to be exactly what the developers of Java are trying to prevent other developers from doing. More and more, they're forcing developers to be explicit when breaking encapsulation. As such, I doubt there is a quick and easy way to open up all modules in the run-time image to reflection.

    That said, you can use the jimage tool to list all the contents of an image. The output can easily be parsed to associate package names with module names. Meaning you could write a script to build an --add-opens argument for every package in the image.

    Warning: The JVM appears to limit the number of --add-opens arguments to 1,000 (one thousand)1.

    Example of invoking jimage (command line):

    jimage list <JAVA_HOME>/lib/modules
    

    Example script (Python):

    import os
    import sys
    from subprocess import Popen, PIPE
    
    # Usage: python script.py <jdk_home> [targets]
    #
    #     jdk_home - absolute or relative path to a JDK installation
    #     targets  - optional comma-separated list of modules as targets for --add-opens (default: ALL-UNNAMED)
    
    if __name__ == '__main__':
        jdk_home = os.path.abspath(sys.argv[1])
        command = os.path.join(jdk_home, 'bin', 'jimage')
        image = os.path.join(jdk_home, 'lib', 'modules')
        targets = 'ALL-UNNAMED' if len(sys.argv) < 3 else sys.argv[2]
    
        with Popen([command, 'list', image], text=True, stdout=PIPE) as proc:
            visited_pkgs = set()
            for line in proc.stdout.readlines()[1:]:
                line = line.strip()
                if len(line) == 0:
                    continue
                elif line.startswith('Module: '):
                    mod_name = line[len('Module: '):]
                elif line.endswith('.class') and line != 'module-info.class':
                    pkg_name = line[:len(line) - len('.class')]
                    pkg_name = pkg_name.replace('/', '.')
                    pkg_name = pkg_name[0:pkg_name.rindex('.')]
                    if pkg_name not in visited_pkgs:
                        visited_pkgs.add(pkg_name)
                        print(f'--add-opens={mod_name}/{pkg_name}={targets}')
    

    1. Limiting --add-opens arguments to packages in java.* modules resulted in 536 arguments. Including packages in jdk.* modules resulted in 843 arguments. Additionally including packages in javafx.* modules (for those JDKs that include JavaFX) resulted in 1,021 arguments. No attempt was made to limit packages to only those whose module exports them.