Java 9 是模块化的,排除了通过反射基础 Java 组件进行的访问。这使得大多数扩展类路径和 java.library.path 的方法以编程方式无效。怎么做才对?以及如何使其与 java.sql.DriverManager 和 javax.activation 兼容?
1 回答
以下是对如何以“授权”方式以编程方式扩展类路径或 java.library.path 而不进行反射或尝试访问非公共方法或字段的数小时研究结果。我还将展示如何绕过 java.sql.DriverManager,因为如果 JDBC 驱动程序是使用与调用类的 ClassLoader 不同的 ClassLoader 创建的,那么它的“是类授权”检查将失败。这已经在 Java 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 中的本机库(也在属性文件中描述)。