类加载实践:读取jar包(在B站上视频学习记录)
视频链接,没怎么接触过的可以看下视频。https://www.bilibili.com/video/BV16T4y1P79h?p=9&t=628
类加载机制实际上调用了 loadClass() -> findClass()
1、从特定路径jar包中读取class(没有打破双亲委派机制,只是读取其他位置的jar包):
public class test {
public static void main(String[] args) throws MalformedURLException {
Double salary = 2000.0;
Double money;
URL jarPath = new URL("file:F:\\temp\\com.hzy\\salary.jar");
URLClassLoader urlClassLoader = new URLClassLoader(new URL[]{jarPath});
while (true) {
try {
Thread.sleep(1000);
money = calSalary(salary, urlClassLoader);
System.out.println("实际到手工资 :" + money);
} catch (Exception e) {
e.printStackTrace();
}
}
}
private static Double calSalary(Double salary, ClassLoader urlClassLoader) throws ClassNotFoundException, IllegalAccessException, InstantiationException, NoSuchMethodException, InvocationTargetException {
Class<?> clazz = urlClassLoader.loadClass("com.hzy.Salary");
Object obj = clazz.newInstance();
return (Double) clazz.getMethod("cal", Double.class).invoke(obj, salary);
}
}
jar包生成方法,使用eclipse可以直接生成,idea建议直接用maven生成
2、读取jar包中class,并打破双亲委派机制(重写loadClass方法)
public class OADemo {
public static void main(String[] args) throws MalformedURLException {
Double salary = 2000.0;
Double money;
// 自定义jar包路径,jar包中包含了要调用的salary.class
String path = "F:\\temp\\hzy\\salary.jar";
// 自定义ClassLoader,打破双亲委派机制,重写loadClass方法
MyClassLoader myClassLoader = new MyClassLoader(path);
while (true) {
try {
Thread.sleep(1000);
money = calSalary(salary, myClassLoader);
System.out.println("实际到手工资 :" + money);
} catch (InterruptedException e) {
e.printStackTrace();
} catch (InstantiationException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
} catch (NoSuchMethodException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
}
// 通过类加载器,加载要调用的类中的方法()
private static Double calSalary(Double salary, ClassLoader urlClassLoader) throws ClassNotFoundException, IllegalAccessException, InstantiationException, NoSuchMethodException, InvocationTargetException {
Class<?> clazz = urlClassLoader.loadClass("com.hzy.Salary");
Object obj = clazz.newInstance();
return (Double) clazz.getMethod("cal", Double.class).invoke(obj, salary);
}
// 静态内部类,自定的classLoder
private static class MyClassLoader extends SecureClassLoader {
private String jarFile;
public MyClassLoader(String jarFile) {
this.jarFile = jarFile;
}
// 打破双亲委派机制,优先加载本地类
@Override
protected Class<?> loadClass(String name, boolean resolve)
throws ClassNotFoundException {
// 读取缓存中的类
Class<?> c = findLoadedClass(name);
if (c != null) {
return c;
}
if (name.indexOf("Salary") > 0) {
return this.findClass(name);
}
return super.loadClass(name, resolve);
}
// 重写的原因是为了清晰class文件的读取流程
@Override
public Class findClass(String name) {
InputStream inputStream;
URL fileUrl;
byte[] b;
int code;
// 需要先对class路径进行转换,获取jar包中的class文件路径
String classPath = name.replace(".", "/").concat(".class");
StringBuilder sb = new StringBuilder();
ByteArrayBuffer buffer = new ByteArrayBuffer();
try {
// 读取jar包中的class
fileUrl = new URL("jar:file:\\" + this.jarFile + "!/" + classPath);
inputStream = fileUrl.openStream();
while ((code = inputStream.read()) != -1) {
buffer.write(code);
}
} catch (MalformedURLException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
b = buffer.toByteArray();
return super.defineClass(name, b, 0, b.length);
}
}
}
3、java的SPI(service Provider interface)
方法:
-
在resources/META-INF/ 下新建services文件
-
新建接口方法的文件 com.hzy.SalaryCalService.txt
- 在接口方法 文件中写入具体的实现类,可以有多个
在前面的例子中都是使用反射的方式调用方法,java的SPI实际上体现了多态的思想,一个接口,多个实现
public class OADemo {
public static void main(String[] args) throws MalformedURLException {
Double salary = 2000.0;
Double money;
// 同样是读取自定义的jar包
String path = "F:\\temp\\hzy\\salary.jar";
MyClassLoader myClassLoader = new MyClassLoader(path);
// 分析源码可以看出实际上调用的线程上下文的 classLoader
ClassLoader cl = Thread.currentThread().getContextClassLoader();
try {
// 设置自定义类加载器为当前线程上下文类加载器
Thread.currentThread().setContextClassLoader(myClassLoader);
// 加载实现类
ServiceLoader<SalaryCalService> services = ServiceLoader.load(SalaryCalService.class);
// 配置文件中只写了一个,所以迭代器中只有一个
Iterator<SalaryCalService> iterator = services.iterator();
if (iterator.hasNext()) {
// 调用实现类中方法,(而不是通过反射的方式)
SalaryCalService service = iterator.next();
System.out.println(service.cal(salary));
}
} finally {
// 调用完之后重置线程上下文类加载器(不重置不知道会不会有啥问题)
Thread.currentThread().setContextClassLoader(cl);
}
}
private static Double calSalary(Double salary, ClassLoader urlClassLoader) throws ClassNotFoundException, IllegalAccessException, InstantiationException, NoSuchMethodException, InvocationTargetException {
Class<?> clazz = urlClassLoader.loadClass("com.hzy.Salary");
Object obj = clazz.newInstance();
return (Double) clazz.getMethod("cal", Double.class).invoke(obj, salary);
}
// 自定义类加载器
private static class MyClassLoader extends SecureClassLoader {
private String jarFile;
public MyClassLoader(String jarFile) {
this.jarFile = jarFile;
}
@Override
public Class findClass(String name) {
InputStream inputStream;
URL fileUrl;
byte[] b;
int code;
String classPath = name.replace(".", "/").concat(".class");
StringBuilder sb = new StringBuilder();
ByteArrayBuffer buffer = new ByteArrayBuffer();
try {
fileUrl = new URL("jar:file:\\" + this.jarFile + "!/" + classPath);
inputStream = fileUrl.openStream();
while ((code = inputStream.read()) != -1) {
buffer.write(code);
}
} catch (MalformedURLException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
b = buffer.toByteArray();
return super.defineClass(name, b, 0, b.length);
}
}
}