【问题标题】:Java 9 Classpath and Library Path ExtensionJava 9 类路径和库路径扩展
【发布时间】:2017-06-22 12:19:33
【问题描述】:

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

【问题讨论】:

  • “discussion starter”不是堆栈溢出的工作方式,请将您的问题表述为问题,然后回答您的解决方案。
  • 请取消删除答案,因为它现在符合您的要求。
  • 我已将答案标记为版主注意取消删除它。

标签: java jdbc classpath urlclassloader library-path


【解决方案1】:

以下是对如何以“授权”方式以编程方式扩展类路径或 java.library.path 而不进行反射或尝试访问非公共方法或字段的数小时研究结果。我还将展示如何绕过 java.sql.DriverManager,因为如果 JDBC 驱动程序是使用与调用类的 ClassLoader 不同的 ClassLoader 创建的,那么它的“是类授权”检查将失败。这已经在 J​​ava 8 和 Java 9 上进行了测试,并且可以在两种环境中运行(URLClassLoader 代码应该可以追溯到 1.1)。

这是将在整个应用程序中使用的基本 ClassLoader:

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。

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

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 的调用类是否与用于加载 JDBC 驱动程序的 ClassLoader 相同(如果调用类已加载则不会由应用程序 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 类的类名(每个人都发布这个),而不仅仅是 DriverManager 所做的 URL 搜索,但 DriverManager 需要在启动时加载 JDBC 类,而不必执行 Class.forName 函数。

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

【讨论】:

  • 我更新了 findLibrary() 以调用 super.findLibrary() 以防有人更改 ClassLoader.findLibrary() 的默认行为以返回 null 以外的其他内容。
  • 更正 findLibrary() 以返回原生库的绝对路径而不是目录路径。
猜你喜欢
  • 1970-01-01
  • 2015-11-15
  • 1970-01-01
  • 1970-01-01
  • 2012-05-21
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多