类加载器
Java 预定义了三种类加载器:
- 启动类(Bootstrap)加载器: 是一个由C++代码实现的类加载器, 它将负责
JRE/lib
的大部分核心类库的加载. 因为是由C++编写的非字节码类加载器, 所以无法进行调用. - 标准扩展(Extension)类加载器: sun.misc.Launcher$ExtClassLoader实现(单例), 负责
JRE/lib/ext
或者由系统变量java.ext.dir
指定位置中的类库加载到内存中. 可以进行调用. - 系统(System)类加载器: sun.misc.Launcher$AppClassLoader实现, 负责系统类路径(CLASSPATH)中指定的类库加载到内存中. 可以进行调用.
还有一种线程上下文类加载器. 可以通过java.util.Thread
中的getContextClassLoader()
和setContextClassLoader(ClassLoader cl)
进行获取和设置. 如果不进行设置, 默认使用系统类加载器.
双亲委派模型
类加载器在收到加载类请求时, 会先将加载任务委托给他的父类加载器, 并且依次进行递归, 也就是能用”最顶层”的类加载器进行加载的类, 绝不会让子类加载器进行加载.
这里当然你可以书写自己的类加载器进行破坏双亲委派模型.
双亲委派模型的好处
- 防止重复进行加载.类A加载一个
com.Test
类的字节码, 类B也要加载com.Test
, 双亲委派模型会保证他们两个使用的是同一个Test类, 并且类加载器相同.(注意, 两个类是否类型相同, 不仅类全名相同, 也要保证是同一个类加载器进行加载) - 安全. 防止恶意加载核心类.
自定义类加载器
自定义的类加载器
package com.classLoader;
import java.io.*;
public class MyClassLoader extends ClassLoader
{
private String classpath;
public MyClassLoader(String classpath)
{
this.classpath = classpath;
}
@Override
protected Class<?> findClass(String name) throws ClassNotFoundException
{
try
{
byte[] classDate = getClassBinaryData(name);
if (classDate == null)
{
}
else
{
return defineClass(name, classDate, 0, classDate.length);
}
}
catch (IOException e)
{
e.printStackTrace();
}
//如果找不到, 就由委托父类加载器
return super.findClass(name);
}
//获取指定的字节码
private byte[] getClassBinaryData(String className) throws IOException
{
InputStream in = null;
ByteArrayOutputStream out = null;
String path = classpath + File.separatorChar + className.replace('.', File.separatorChar) + ".class";
try
{
in = new FileInputStream(path);
out = new ByteArrayOutputStream();
byte[] buffer = new byte[2048];
int len = 0;
while ((len = in.read(buffer)) != -1)
{
out.write(buffer, 0, len);
}
return out.toByteArray();
}
catch (FileNotFoundException e)
{
e.printStackTrace();
}
finally
{
in.close();
out.close();
}
return null;
}
}
要加载的类
package com.classLoader.test;
public class MyString {
private String str;
public MyString() {
}
public String getStr() {
return str;
}
private void setStr(String str) {
this.str = str;
}
}
package com.classLoader;
import com.classLoader.test.MyString;
import java.lang.reflect.Method;
public class Main {
public static void main(String[] args) throws Exception{
MyClassLoader classLoader = new MyClassLoader("E:\\JavaGrowthRoad\\Base\\target\\classes");
Class stringClass = classLoader.loadClass("com.classLoader.test.MyString");
MyString str = (MyString)stringClass.newInstance();
Method setStr = stringClass.getDeclaredMethod("setStr", String.class);
setStr.setAccessible(true);
setStr.invoke(str, "hello");
System.out.println(str.getStr());
System.out.println(str.toString());
}
}
自定义类加载器最主要是重写findClass(String name)
方法, 来控制类加载的逻辑, 当然破坏双亲委派模型也从这里进行.
同时还有一个重点, 指定用一个类加载器去加载一个类的时候, 会使用该类加载器去加载这个类的父类.
破坏双亲委派模型也是由限度的, 如果你的类名是java
开头, 那么即使你自己编写类加载器去加载这个类, 这个类也是始终无法使用的, 因为defineClass(String name, byte[]b, int off, int len)
是final方法, 无法进行重写, 与此同时他会调用一个preDefineCLass(String name, ProtectionDomain pd)
去检查类名, 不合格的类名, 和以java
开头的类名都将会被它弹出异常.
private ProtectionDomain preDefineClass(String name,
ProtectionDomain pd)
{
if (!checkName(name))
throw new NoClassDefFoundError("IllegalName: " + name);
// Note: Checking logic in java.lang.invoke.MemberName.checkForTypeAlias
// relies on the fact that spoofing is impossible if a class has a name
// of the form "java.*"
if ((name != null) && name.startsWith("java.")) {
throw new SecurityException
("Prohibited package name: " +
name.substring(0, name.lastIndexOf('.')));
}
if (pd == null) {
pd = defaultDomain;
}
if (name != null) checkCerts(name, pd.getCodeSource());
return pd;
}
PREVIOUS二叉搜索树的后序遍历序列
NEXTgit将远程仓库拉取到本地