类加载器介绍
类加载器负责加载JAVA类的字节码到JAVA虚拟机中,可理解成JVM和字节码代码的桥梁,可以根据指定的类名(如java.lang.Object)来装载class文件的内容到Runtime data area 中的 method area( 方法区域)。
ClassLoader类型
从JVM的角度讲,主要有两种类型加载器:启动类加载器和所有其它的类加载器。启动类加载器是JVM实现的一部分,使用C++语言实现,其它类加载器都由java语言实现,独立于虚拟机外部,并且全部继承抽象类java.lang.ClassLoader。
1、启动类加载器(Bootstrap ClassLoader)
2、其他类加载器(UrlClassLoader、AppClassLoader、ExtClassLoader……)
获取与设置本线程的ClassLoader
//获取ClassLoader
Thread.currentThread().getContextClassLoader();
//设置ClassLoader
Thread.currentThread().setContextClassLoader(ClassLoader);
示例:如maven插件开发,maven中jar都存放在仓库中,插件启动是不会加载到JVM中,这边将需要加载的路径放入到ClassLoader中
/**
* 将项目的classes加入到插件的ClassLoader中
* @param project maven 工程信息
*/
public static void addClassesToClassLoader(MavenProject project) throws
MojoExecutionException {
try {
Set<String> entries = new HashSet<String>();
//获取所有需编译的元素
entries.addAll(project.getCompileClasspathElements());
//获取新的自定义的ClassLoader
ClassLoader contextClassLoader = getCustomClassloader(new ArrayList<>
(entries));
//设置当前的ClassLoader
Thread.currentThread().setContextClassLoader(contextClassLoader);
} catch (DependencyResolutionRequiredException e) {
throw new MojoExecutionException("Dependency Resolution Required", e);
}
}
private static ClassLoader getCustomClassloader(List<String> entries) {
List<URL> urls = new ArrayList();
if (entries != null) {
//循环获取URL
Iterator i$ = entries.iterator();
while(i$.hasNext()) {
String classPathEntry = (String)i$.next();
File file = new File(classPathEntry);
if (!file.exists()) {
throw new RuntimeException("RuntimeError.9");
}
try {
urls.add(file.toURI().toURL());
} catch (MalformedURLException var6) {
throw new RuntimeException("RuntimeError.9");
}
}
}
//取当前线程的classLoader
ClassLoader parent = Thread.currentThread().getContextClassLoader();
//新建ClassLoader
URLClassLoader ucl = new URLClassLoader((URL[])urls.toArray(new
URL[urls.size()]), parent);
return ucl;
}
两个类是否是同一个类
代码演示,通过自定义的ClassLoader加载类,判断是否为同一个的类
public static void main(String[] args) throws Exception {
ClassLoader myLoader = new ClassLoader() {
@Override
public Class<?> loadClass(String name) throws ClassNotFoundException {
try {
String fileName = name.substring(name.lastIndexOf(".") + 1) +
".class";
InputStream is = this.getClass().getResourceAsStream(fileName);
if (is == null) {
return super.loadClass(name);
}
byte[] b = new byte[is.available()];
is.read(b);
return defineClass(name, b, 0, b.length);
} catch (IOException e) {
throw new ClassNotFoundException(name);
}
}
};
Object customObj = myLoader.loadClass("com.suning.demo.jvm.classloader.ClassLoaderTest").newInstance();
Object newObj = new ClassLoaderTest();
System.out.println("new的class:" + newObj.getClass());
System.out.println("new的class是否为ClassLoaderTest类:"+ (newObj instanceof ClassLoaderTest));
System.out.println("myLoader加载的class:" + customObj.getClass());
System.out.println("myLoader加载的class是否为ClassLoaderTest类:"+ (customObj instanceof ClassLoaderTest));
}
执行结果:
new的class:class com.suning.demo.jvm.classloader.ClassLoaderTest
new的class是否为ClassLoaderTest类:true
myLoader加载的class:class com.suning.demo.jvm.classloader.ClassLoaderTest
myLoader加载的class是否为ClassLoaderTest类:false
可以看出,getClass()返回的值相同,但myLoader加载的class和ClassLoaderTest类不为同一个类
自定义ClassLoader
继承 java.lang.ClassLoader,重写findClass(String)方法
public class OwnDefinedClassLoader extends ClassLoader{
private String classpath;
public OwnDefinedClassLoader(String classpath) {
this.classpath = classpath;
}
@Override
protected Class<?> findClass(String name) throws ClassNotFoundException {
//a.class格式
String classFileDir = name.replaceAll("[.]",
File.separator).concat(".class");
this.classpath = this.getClasspath().endsWith(File.separator) ?
this.getClasspath() : this.getClasspath().concat(File.separator);
//磁盘文件路径
String classFileUrl = this.getClasspath().concat(classFileDir);
//获取文件流
try (InputStream inputStream = new FileInputStream(classFileUrl)) {
byte[] bytes = new byte[inputStream.available()];
inputStream.read(bytes);
//调用父类defineClass方法
return super.defineClass(name, bytes, 0, bytes.length);
} catch (Exception e) {
throw new ClassNotFoundException(name);
}
}
public String getClasspath() {
return classpath;
}
public static void main(String[] args) throws Exception{
ClassLoader classLoader = new OwnDefinedClassLoader("E:\\workspace\\demo\\src\\main\\java\\");
Class<?> aClass1 = classLoader.loadClass("com.suning.demo.entity.Book");
Object instance = aClass1.newInstance();
System.out.println(instance);
}
}
执行结果:
com.suning.demo.entity.Book@5a2e4553
问答
1、“字节码代码”来源渠道有哪些?
“字节码代码”来源并非只能来自磁盘中的.class文件,它是一串二进制的字节流,无论以何种形式存在都可以。 jar、zip、网络传输、jsp文件等都可以是来源。
2、类A中引用了类B,类B将由哪个类加载器来加载?
如果类A中引用了类B,Java虚拟机将使用加载类A的类加载器来加载类B。
3、如何判断两个类相同?
相同类加载器、相同的全限定名(包路径+类名)。
4、如何获取/指定本线程中的类加载器?
Thread#get/setContextClassLoader(ClassLoader cl)。