如何在 Java 中使用自定义类加载器到新对象
How to use Custom ClassLoader to new Object in Java
我想创建一个自定义 ClassLoader 来加载某个路径中的所有 jar 文件(例如 /home/custom/lib)。
那么我希望每次我使用new
操作符创建一个对象时,它会在该路径下的所有jar文件中搜索class,然后搜索定义的class路径按参数 (-cp
).
可能吗?
例如/home/custom/lib/a.jar
中有一个jar文件
主要 Class
public class Main {
public static void main(String[] args) {
// do something here to use custom ClassLoader
// here will search Car in /home/custom/lib/a.jar first then in java class path
Car car = new Car();
}
}
class 加载程序无法完全按照您的预期进行。
引用 another answer 相关问答:
Java will always use the classloader that loaded the code that is executing.
以你的例子为例:
public static void main(String[] args) {
// whatever you do here...
Car car = new Car(); // ← this code is already bound to system class loader
}
最接近的方法是使用 child-first(parent-last)class loader 例如 this one,用你的 jar,然后使用反射从那个 jar 创建一个 Car
的实例。
Car
class a.jar
:
package com.acme;
public class Car {
public String honk() {
return "Honk honk!";
}
}
您的主要应用:
public static void main(String[] args) throws MalformedURLException, ClassNotFoundException, IllegalAccessException, InstantiationException, NoSuchMethodException, InvocationTargetException {
ParentLastURLClassLoader classLoader = new ParentLastURLClassLoader(
Arrays.asList(new File("/home/lib/custom/a.jar").toURI().toURL()));
Class<?> carClass = classLoader.loadClass("com.acme.Car");
Object someCar = carClass.newInstance();
Object result = carClass.getMethod("honk").invoke(someCar);
System.out.println(result); // Honk honk!
}
注意:如果您的 class 路径中也有一个 com.acme.Car
class,那是不一样的 class,因为 class 是由其全名和 class 加载程序标识。
为了说明这一点,假设我使用我的本地 Car
class 如下所示,而 carClass
由我的自定义 class 加载程序如上加载:
Car someCar = (Car) carClass.newInstance();
// java.lang.ClassCastException: com.acme.Car cannot be cast to com.acme.Car
可能会造成混淆,但这是因为名称本身并不能识别 class。该转换无效,因为 2 classes 不同。它们可能具有不同的成员,或者它们可能具有相同的成员但实现不同,或者它们可能逐字节相同:它们不相同 class.
现在,这不是一个很有用的东西。
当您的 jar 中的自定义 classes 实现一个通用 API 时,这些东西变得 有用,主程序知道如何使用 .
例如,假设接口 Vehicle
(具有方法 String honk()
)在公共 class 路径中,而您的 Car
在 a.jar
中并实现 Vehicle
.
ParentLastURLClassLoader classLoader = new ParentLastURLClassLoader(
Arrays.asList(new File("/home/lib/custom/a.jar").toURI().toURL()));
Class<?> carClass = classLoader.loadClass("com.acme.Car");
Vehicle someCar = (Vehicle) carClass.newInstance(); // Now more useful
String result = someCar.honk(); // can use methods as normal
System.out.println(result); // Honk honk!
这类似于 servlet 容器的作用:
- 您的应用程序实现了 servlet API(例如 class 实现了
javax.servlet.Servlet
)
- 它被打包到一个 war 文件中,servlet 容器可以使用自定义 class 加载器加载
- 部署描述符(
web.xml
文件)告诉 servlet 容器它需要实例化的 servlet(classes)的名称(正如我们上面所做的)
- 那些 class 是
Servlet
,servlet 容器可以这样使用它们
在您的情况下,您不需要编写新的 ClassLoader,因为您唯一想做的就是在运行时扩展 classpath。
为此,您获取当前的 SystemClassLoader 实例,并使用 URLClassLoader.
添加 classpath 条目。
JDK8 的工作示例:
汽车class编译并位于C:\Users\xxxx\Documents\sources\test\target\classes
public class Car {
public String prout() {
return "Test test!";
}
}
主要class
public static void main(String args[]) throws Exception {
addPath("C:\Users\xxxx\Documents\sources\test\target\classes");
Class clazz = ClassLoader.getSystemClassLoader().loadClass("Car");
Object car = clazz.newInstance();
System.out.println(clazz.getMethod("prout").invoke(car));
}
public static void addPath(String s) throws Exception {
File f=new File(s);
URL u=f.toURI().toURL();
URLClassLoader urlClassLoader=(URLClassLoader)ClassLoader.getSystemClassLoader();
Class urlClass=URLClassLoader.class;
Method method=urlClass.getDeclaredMethod("addURL",new Class[]{URL.class});
method.setAccessible(true);
method.invoke(urlClassLoader,new Object[]{u});
}
- 注意我们需要使用反射,因为方法 addURL(URL u) 是 protected
- 还请注意,由于我们将 classpath 条目添加到 SystemClassloader,因此您无需在每次需要时都添加 classpath 条目,只需一次就足够了,然后使用
ClassLoader.getSystemClassLoader().loadClass(String name)
从先前添加的 class 路径条目加载 class。
如果您以后不需要那个 class 路径条目,您可以实例化您自己的 URLClassLoader
实例并相应地加载 classes,而不是设置 classSystemClassLoader 上的路径条目。
即:
public static void main(String[] args) {
try {
File file = new File("c:\other_classes\");
//convert the file to URL format
URL url = file.toURI().toURL();
URL[] urls = new URL[]{ url };
//load this folder into Class loader
ClassLoader cl = new URLClassLoader(urls);
//load the Address class in 'c:\other_classes\'
Class cls = cl.loadClass("com.mkyong.io.Address");
} catch (Exception ex) {
ex.printStackTrace();
}
}
来源:
https://www.mkyong.com/java/how-to-load-classes-which-are-not-in-your-classpath/
Question: I want to create a custom ClassLoader to load all jar files
in some path(e.g. /home/custom/lib).
then I expect that every time I use new operator to create a Object,
it will search class in all jar files in that path, then search the
class path defined by parameter (-cp).
Is it possible?
如果要能够使用new
关键字,需要修改class编译器的路径javac -classpath path
否则在编译时它将不知道从哪里加载 class.
编译器正在加载 classes 以进行类型检查。
(更多信息在这里:http://docs.oracle.com/javase/7/docs/technotes/tools/windows/javac.html#searching)
由于 new
关键字的编译器内部实现,无法在运行时将 new
关键字用于自定义类加载器加载的 classes。
编译器和JVM(运行时)都有自己的ClassLoader,你不能自定义javac classloader,据我所知唯一可以从编译器自定义的部分是注解处理。
我想创建一个自定义 ClassLoader 来加载某个路径中的所有 jar 文件(例如 /home/custom/lib)。
那么我希望每次我使用new
操作符创建一个对象时,它会在该路径下的所有jar文件中搜索class,然后搜索定义的class路径按参数 (-cp
).
可能吗?
例如/home/custom/lib/a.jar
主要 Class
public class Main {
public static void main(String[] args) {
// do something here to use custom ClassLoader
// here will search Car in /home/custom/lib/a.jar first then in java class path
Car car = new Car();
}
}
class 加载程序无法完全按照您的预期进行。
引用 another answer 相关问答:
Java will always use the classloader that loaded the code that is executing.
以你的例子为例:
public static void main(String[] args) {
// whatever you do here...
Car car = new Car(); // ← this code is already bound to system class loader
}
最接近的方法是使用 child-first(parent-last)class loader 例如 this one,用你的 jar,然后使用反射从那个 jar 创建一个 Car
的实例。
Car
class a.jar
:
package com.acme;
public class Car {
public String honk() {
return "Honk honk!";
}
}
您的主要应用:
public static void main(String[] args) throws MalformedURLException, ClassNotFoundException, IllegalAccessException, InstantiationException, NoSuchMethodException, InvocationTargetException {
ParentLastURLClassLoader classLoader = new ParentLastURLClassLoader(
Arrays.asList(new File("/home/lib/custom/a.jar").toURI().toURL()));
Class<?> carClass = classLoader.loadClass("com.acme.Car");
Object someCar = carClass.newInstance();
Object result = carClass.getMethod("honk").invoke(someCar);
System.out.println(result); // Honk honk!
}
注意:如果您的 class 路径中也有一个 com.acme.Car
class,那是不一样的 class,因为 class 是由其全名和 class 加载程序标识。
为了说明这一点,假设我使用我的本地 Car
class 如下所示,而 carClass
由我的自定义 class 加载程序如上加载:
Car someCar = (Car) carClass.newInstance();
// java.lang.ClassCastException: com.acme.Car cannot be cast to com.acme.Car
可能会造成混淆,但这是因为名称本身并不能识别 class。该转换无效,因为 2 classes 不同。它们可能具有不同的成员,或者它们可能具有相同的成员但实现不同,或者它们可能逐字节相同:它们不相同 class.
现在,这不是一个很有用的东西。
当您的 jar 中的自定义 classes 实现一个通用 API 时,这些东西变得 有用,主程序知道如何使用 .
例如,假设接口 Vehicle
(具有方法 String honk()
)在公共 class 路径中,而您的 Car
在 a.jar
中并实现 Vehicle
.
ParentLastURLClassLoader classLoader = new ParentLastURLClassLoader(
Arrays.asList(new File("/home/lib/custom/a.jar").toURI().toURL()));
Class<?> carClass = classLoader.loadClass("com.acme.Car");
Vehicle someCar = (Vehicle) carClass.newInstance(); // Now more useful
String result = someCar.honk(); // can use methods as normal
System.out.println(result); // Honk honk!
这类似于 servlet 容器的作用:
- 您的应用程序实现了 servlet API(例如 class 实现了
javax.servlet.Servlet
) - 它被打包到一个 war 文件中,servlet 容器可以使用自定义 class 加载器加载
- 部署描述符(
web.xml
文件)告诉 servlet 容器它需要实例化的 servlet(classes)的名称(正如我们上面所做的) - 那些 class 是
Servlet
,servlet 容器可以这样使用它们
在您的情况下,您不需要编写新的 ClassLoader,因为您唯一想做的就是在运行时扩展 classpath。 为此,您获取当前的 SystemClassLoader 实例,并使用 URLClassLoader.
添加 classpath 条目。JDK8 的工作示例:
汽车class编译并位于C:\Users\xxxx\Documents\sources\test\target\classes
public class Car {
public String prout() {
return "Test test!";
}
}
主要class
public static void main(String args[]) throws Exception {
addPath("C:\Users\xxxx\Documents\sources\test\target\classes");
Class clazz = ClassLoader.getSystemClassLoader().loadClass("Car");
Object car = clazz.newInstance();
System.out.println(clazz.getMethod("prout").invoke(car));
}
public static void addPath(String s) throws Exception {
File f=new File(s);
URL u=f.toURI().toURL();
URLClassLoader urlClassLoader=(URLClassLoader)ClassLoader.getSystemClassLoader();
Class urlClass=URLClassLoader.class;
Method method=urlClass.getDeclaredMethod("addURL",new Class[]{URL.class});
method.setAccessible(true);
method.invoke(urlClassLoader,new Object[]{u});
}
- 注意我们需要使用反射,因为方法 addURL(URL u) 是 protected
- 还请注意,由于我们将 classpath 条目添加到 SystemClassloader,因此您无需在每次需要时都添加 classpath 条目,只需一次就足够了,然后使用
ClassLoader.getSystemClassLoader().loadClass(String name)
从先前添加的 class 路径条目加载 class。
如果您以后不需要那个 class 路径条目,您可以实例化您自己的 URLClassLoader
实例并相应地加载 classes,而不是设置 classSystemClassLoader 上的路径条目。
即:
public static void main(String[] args) {
try {
File file = new File("c:\other_classes\");
//convert the file to URL format
URL url = file.toURI().toURL();
URL[] urls = new URL[]{ url };
//load this folder into Class loader
ClassLoader cl = new URLClassLoader(urls);
//load the Address class in 'c:\other_classes\'
Class cls = cl.loadClass("com.mkyong.io.Address");
} catch (Exception ex) {
ex.printStackTrace();
}
}
来源: https://www.mkyong.com/java/how-to-load-classes-which-are-not-in-your-classpath/
Question: I want to create a custom ClassLoader to load all jar files in some path(e.g. /home/custom/lib).
then I expect that every time I use new operator to create a Object, it will search class in all jar files in that path, then search the class path defined by parameter (-cp).
Is it possible?
如果要能够使用new
关键字,需要修改class编译器的路径javac -classpath path
否则在编译时它将不知道从哪里加载 class.
编译器正在加载 classes 以进行类型检查。 (更多信息在这里:http://docs.oracle.com/javase/7/docs/technotes/tools/windows/javac.html#searching)
由于 new
关键字的编译器内部实现,无法在运行时将 new
关键字用于自定义类加载器加载的 classes。
编译器和JVM(运行时)都有自己的ClassLoader,你不能自定义javac classloader,据我所知唯一可以从编译器自定义的部分是注解处理。