Java9 类路径和库路径扩展

Java 9 Classpath and Library Path Extension

Java 9 是模块化的,排除了通过反射基础 Java 组件进行访问。这使得大多数扩展类路径的方法和 java.library.path 以编程方式无效。怎么做才对?以及如何使其与 java.sql.DriverManager 和 javax.activation 兼容?

以下是对如何以 "authorized" 方式以编程方式扩展 class 路径或 java.library.path 而无需反思或尝试访问方法或字段的数小时研究结果不是 public。我还将展示如何绕过 java.sql.DriverManager,因为如果 JDBC 驱动程序是使用与调用 class' ClassLoader 不同的 ClassLoader 创建的,它的 "is class authorized" 检查将失败。这已经在 Java 8 和 Java 9 上进行了测试,并且可以在两种环境中工作(URLClassLoader 代码应该可以回到 1.1)。

这是将在整个应用程序中使用的基本类加载器:

public class MiscTools
{
    private static class SpclClassLoader extends URLClassLoader
    {
        static
        {
            ClassLoader.registerAsParallelCapable();
        }

        private final Set<Path> userLibPaths = new CopyOnWriteArraySet<>();

        private SpclClassLoader()
        {
            super(new URL[0]);
        }

        @Override
        protected void addURL(URL url)
        {
            super.addURL(url);
        }

        protected void addLibPath(String newpath)
        {
            userLibPaths.add(Paths.get(newpath).toAbsolutePath());
        }

        @Override
        protected String findLibrary(String libname)
        {
            String nativeName = System.mapLibraryName(libname);
            return userLibPaths.stream().map(tpath -> tpath.resolve(nativeName)).filter(Files::exists).map(Path::toString).findFirst().orElse(super.findLibrary(libname));            }
    }
    private final static SpclClassLoader ucl = new SpclClassLoader();

    /**
     * Adds a jar file or directory to the classpath. From Utils4J.
     *
     * @param newpaths JAR filename(s) or directory(s) to add
     * @return URLClassLoader after newpaths added if newpaths != null
     */
    public static ClassLoader addToClasspath(String... newpaths)
    {
        if (newpaths != null)
            try
            {
                for (String newpath : newpaths)
                    if (newpath != null && !newpath.trim().isEmpty())
                        ucl.addURL(Paths.get(newpath.trim()).toUri().toURL());
            }
            catch (IllegalArgumentException | MalformedURLException e)
            {
                RuntimeException re = new RuntimeException(e);
                re.setStackTrace(e.getStackTrace());
                throw re;
            }
        return ucl;
    }

    /**
     * Adds to library path in ClassLoader returned by addToClassPath
     *
     * @param newpaths Path(s) to directory(s) holding OS library files
     */
    public static void addToLibraryPath(String... newpaths)
    {
        for (String newpath : Objects.requireNonNull(newpaths))
            ucl.addLibPath(newpath);
    }
}

在 main() 的早期放置以下代码来处理诸如 javax.activation.

之类的事情
Thread.currentThread().setContextClassLoader(MiscTools.addToClasspath());

所有线程(包括由java.util.concurrent.Executors创建的线程)继承上下文ClassLoader。

对于从扩展 class 路径加载的 classes,使用以下代码:

try
{
    Class.forName(classname, true, MiscTools.addToClasspath(cptoadd);
}
catch (ClassNotFoundException IllegalArgumentException | SecurityException e)
{
    classlogger.log(Level.WARNING, "Error loading ".concat(props.getProperty("Class")), e);
}

最后,如何绕过java.sql.DriverManager,它检查 DriverManager.getDriver() ClassLoader 的调用 class 是否与用于加载 JDBC 驱动程序的 ClassLoader 相同(如果调用 class 是由应用程序 ClassLoader 加载但驱动程序是使用 SpclClassLoader 加载的,则不会。

private final static CopyOnWriteArraySet<Driver> loadedDrivers = new CopyOnWriteArraySet<>();

private static Driver isLoaded(String drivername, String... classpath) throws ClassNotFoundException
{
    Driver tdriver = loadedDrivers.stream().filter(d -> d.getClass().getName().equals(drivername)).findFirst().orElseGet(() ->
    {
        try
        {
            Driver itdriver = (Driver) Class.forName(drivername, true, addToClasspath(classpath)).newInstance();
            loadedDrivers.add(itdriver);
            return itdriver;
        }
        catch (ClassNotFoundException | IllegalAccessException | InstantiationException e)
        {
            return null;
        }
    });
    if (tdriver == null)
        throw new java.lang.ClassNotFoundException(drivername + " not found.");
    return tdriver;
}

isLoader 确保我们在提供请求的驱动程序时不会加载一堆具有所有额外开销的相同驱动程序。缺点是它需要知道 JDBC class 的 class 名称(每个人都发布这个),而不仅仅是 DriverManager 执行的 URL 搜索,但 DriverManager 需要 JDBC class 在启动时加载,不必执行 Class.forName 功能。

希望这会帮助其他人避免我花费大量时间为我编写的应用程序改进此方法,该应用程序在许多平台和许多配置中使用,需要能够加载 class 基于class 属性文件中提供的路径,并扩展 library.path 以使用不在默认 library.path 中的本机库(也在属性文件中描述)。