Java 9模块化,阻止通过反射基础Java组件进行访问。这使得大多数扩展classpath和java.library.path的方法在编程上都无效。怎么做对了?以及如何使其与java.sql.DriverManager和javax.activation兼容?
答案 0 :(得分:5)
以下是关于如何以“授权”方式以编程方式扩展classpath或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中的本机库(也在属性文件中描述)。