在上文《Java中的ClassLoader》中,已经对ClassLoader做了介绍。在那里也提到过了部分关于ClassLoader的扩展,那么下面我将简单的实现一些自定义的ClassLoader。
ClassLoader中提供了三个方法用于子类扩展其行为:
findResource
findResources
findClass
从它们的名字中已经能知道它们的行为了,因而就不做过多的解释了。
需要注意的是这三个方法都只要实现在自己的搜索路径中实现资源和字节码的查找即可,关于ClassLoader的代理模型已经在基类中的getResource、getResources、loadClass实现了,并且loadClass函数也实现已加载类的缓存机制,所以子类的findClass也不需要实现该机制。因此在子类中一般不需要也不建议重写getResource、getResources、loadClass等方法,除非自定义的ClassLoader在加载类时所使用的算法不同于默认的代理模型。
一般findClass可以如下方式实现:
protected Class<?> findClass(String name) throws ClassNotFoundException{
//多数情况下name为类名
String resName = convertNameToResName(name);
URL resURL = getResource(resName);
if(resURL != null) {
byte[] classData = getClassDataFrom(resURL);
return defineClass(name, 0, classData, ClassData.length);
}
throw new ClassNotFoundException();
}
两个简单的自定义ClassLoader实现
假设有这样一个需求,当前已经有一个Employee接口,它的实现根据用户的配置不同而不同。这个需求可以简单的通过不同的配置映射不同的实现类来实现。假设又有一个变态的需求说Employee的实现类不在CLASSPATH的搜索路径中,这时就可以借助扩展ClassLoader的方式来实现。
在一个工程中定义了Employee接口:
public interface Employee {
public String getFirstName();
public void setFirstName(String firstName);
public String getLastName();
public void setLastName(String lastName);
public String getFullName();
public void printFullName();
}
有一个实现类如下:
public class EmployeeImpl implements Employee {
private String firstName;
private String lastName;
@Override
public String getFirstName() {
return firstName;
}
@Override
public void setFirstName(String firstName) {
this.firstName = firstName;
}
@Override
public String getLastName() {
return lastName;
}
@Override
public void setLastName(String lastName) {
this.lastName = lastName;
}
@Override
public String getFullName() {
return firstName + " " + lastName;
}
@Override
public void printFullName() {
System.out.println(getFullName());
}
}
1. LocalFileClassLoader
若该实现类生成的.class文件存放在”E:\\CodeRepository\\Java\\TomcatReading\\DomainImpl \\bin”中
此时,我们可以如下实现LocalFileClassLoader类:
public class LocalFileClassLoader extends ClassLoader {
private String location;
public LocalFileClassLoader(String location, ClassLoader parent) {
super(parent);
this.location = location;
}
public LocalFileClassLoader(String location) {
this(location, LocalFileClassLoader.class.getClassLoader());
}
public String getLocation() {
return location;
}
@Override
public Class<?> findClass(String name)
throws ClassNotFoundException {
String separatorStr = new String(new char[] {File.separatorChar });
if(!location.endsWith(separatorStr)) {
location = location + separatorStr;
}
String filename = location + name.replace('.', File.separatorChar) + ".class";
byte[] classData = null;
try {
classData = loadClassData(filename);
} catch(FileNotFoundException e) {
throw new ClassNotFoundException("Cannot find " + name + " at " + location);
} catch(IOException e) {
throw new ClassNotFoundException("Read class data error");
}
if(null == classData) {
throw new ClassNotFoundException("Unknown reason");
}
return super.defineClass(null, classData, 0, classData.length);
}
private byte[] loadClassData(String filename)
throws FileNotFoundException, IOException {
File inputFile = new File(filename);
byte[] classData = new byte[(int)inputFile.length()];
FileInputStream inputStream = new FileInputStream(filename);
DataInputStream dataInput = new DataInputStream(inputStream);
dataInput.readFully(classData);
dataInput.close();
return classData;
}
}
该类只实现了findClass方法,并没有实现findResource方法。测试代码如下:
public class LocalFileClassLoaderTest {
public static void main(String[] args) {
LocalFileClassLoader classLoader = new LocalFileClassLoader("E:\\CodeRepository\\Java\\TomcatReading\\DomainImpl\\bin");
try {
Class<?> employeeCls = classLoader.loadClass("org.levin.domain.impl.EmployeeImpl");
Employee employee = (Employee)employeeCls.newInstance();
employee.setFirstName("Levin");
employee.setLastName("Ding");
employee.printFullName();
} catch (ClassNotFoundException e) {
e.printStackTrace();
System.err.println(e.getMessage());
} catch(Exception e) {
System.err.println(e.getMessage());
}
}
}
1. LocalJarClassLoader
若该实现类生成的jar文件为"E:\\CodeRepository\\Java\\TomcatReading\\Temp\\ DomainImpl.jar"
此时,我们可以如下实现LocalJarClassLoader类:
public class LocalJarFileClassLoader extends ClassLoader {
private final static LocalJarFileClassLoader instance = new LocalJarFileClassLoader();
private final static int BUFFER_SIZE = 8192;
private final static byte[] buffer = new byte[BUFFER_SIZE];
private String jarFilename;
protected LocalJarFileClassLoader() {
this(null);
}
protected LocalJarFileClassLoader(String jarFilename) {
this(jarFilename, LocalJarFileClassLoader.class.getClassLoader());
}
protected LocalJarFileClassLoader(String jarFilename, ClassLoader parent) {
super(parent);
this.jarFilename = jarFilename;
}
protected void setJarFilename(String jarFilename) {
this.jarFilename = jarFilename;
}
protected static LocalJarFileClassLoader newInstance(String jarFilename) {
instance.setJarFilename(jarFilename);
return instance;
}
public static Class<?> loadClassFrom(String name, String jarFilename)
throws ClassNotFoundException {
if(!jarFilename.toLowerCase().endsWith(".jar")) {
throw new IllegalArgumentException(jarFilename + " is not a jar file");
}
return newInstance(jarFilename).loadClass(name);
}
@Override
public Class<?> findClass(String name)
throws ClassNotFoundException {
byte[] classData = loadClassData(name);
return super.defineClass(null, classData, 0, classData.length);
}
private byte[] loadClassData(String name)
throws ClassNotFoundException {
JarInputStream jarInputStream = null;
try {
jarInputStream = new JarInputStream(new FileInputStream(jarFilename));
} catch (FileNotFoundException e) {
throw new ClassNotFoundException(jarFilename + " doesn't exist", e);
} catch (IOException e) {
throw new ClassNotFoundException("Reading " + jarFilename + " error", e);
}
byte[] classData = null;
String location = name.replace('.', '/') + ".class";
while(true) {
ZipEntry entry = null;
try {
entry = jarInputStream.getNextEntry();
if(entry == null) break;
String entryName = entry.getName();
if(entryName.toLowerCase().endsWith(".class") &&
entryName.equals(location)) {
classData = readClassData(jarInputStream);
}
} catch(IOException e) {
System.err.println(e.getMessage());
}
}
if(classData == null) {
throw new ClassNotFoundException(name + " cannot be found");
}
return classData;
}
private byte[] readClassData(JarInputStream jarInputStream)
throws IOException {
ByteArrayOutputStream classData = new ByteArrayOutputStream();
int readSize = 0;
while(jarInputStream.available() != 0) {
// 注:有些时候,即使jarInputStream有超过BUFFER_SIZE的数据
// 也有可能读到的数据少于BUFFER_SIZE
readSize = jarInputStream.read(buffer, 0, BUFFER_SIZE);
// jarInputStream.available()好像不起作用,因而需要加入一下代码
if(readSize < 0) break;
classData.write(buffer, 0, readSize);
}
return classData.toByteArray();
}
}
该类也只是实现了findClass方法而没有实现findResource方法。测试代码如下:
public class LocalJarFileClassLoaderTest {
public static void main(String[] args) {
try {
Class<?> employeeCls = LocalJarFileClassLoader.loadClassFrom(
"org.levin.domain.impl.EmployeeImpl", "E:\\CodeRepository\\Java\\TomcatReading\\Temp\\DomainImpl.jar");
Employee employee = (Employee)employeeCls.newInstance();
employee.setFirstName("Levin");
employee.setLastName("Ding");
employee.printFullName();
} catch (ClassNotFoundException e) {
System.err.println(e.getMessage());
} catch (Exception e) {
e.printStackTrace();
System.err.println(e.getMessage());
}
}
}
事实上,以上的需求用把字节代码存放在数据库的中的方式实现会更好,因为把字节码存放在数据库中有一个好处就是可以隐藏自己的实现代码而不用担心被反编译,这也是我之前在某篇文章中看到的保护自己知识产权的方法之一,可惜数据库的实现代码我还没写好。另外一个原因是以上两个自定义ClassLoader提供的功能,Java内部已经有一个功能更加强大的类可以提供了:URLClassLoader。实现以上功能的代码如下:
public class URLClassLoaderTest {
public static void main(String[] args) {
File jarFile = new File("E:\\CodeRepository\\Java\\TomcatReading\\Temp\\DomainImpl.jar");
File clsFile = new File("E:\\CodeRepository\\Java\\TomcatReading\\DomainImpl\\bin");
try {
testURLClassLoader(jarFile.toURI().toURL());
testURLClassLoader(clsFile.toURI().toURL());
} catch (Exception e) {
System.err.println(e.getMessage());
}
}
private static void testURLClassLoader(URL url) throws Exception {
URLClassLoader classLoader = new URLClassLoader(new URL[] { url });
Class<?> employeeCls = classLoader.loadClass("org.levin.domain.impl.EmployeeImpl");
Employee employee = (Employee)employeeCls.newInstance();
employee.setFirstName("Levin");
employee.setLastName("Ding");
employee.printFullName();
}
}
查看URLClassLoader的实现代码,会发现它正是实现了findClass、findResource、findResources等方法,另外它也提供了两个newInstance的静态工厂方法以创建相应的URLClassLoader。
分享到:
相关推荐
3. 整合自定义ClassLoader:了解如何将自定义的ClassLoader集成到Java应用程序中,替换或扩展默认的加载行为。 4. 考虑Java 2版本的兼容性:学习如何修改你的ClassLoader以适应Java 2及以上版本的特性,比如支持...
2. Extension ClassLoader:加载JDK扩展目录(如$JAVA_HOME/jre/lib/ext)中的类。 3. System ClassLoader(也称为AppClassLoader):加载`CLASSPATH`环境变量指定的类,以及应用主类路径(class path)上的类。 4. ...
### Java虚拟机中ClassLoader概述与双亲委托机制详解 #### 一、ClassLoader概念与作用 在Java编程语言中,`ClassLoader`是一个非常重要的组件,它负责加载程序运行所需的类文件到Java虚拟机(JVM)中。`ClassLoader`...
接着,Extension ClassLoader加载扩展类库,然后是App ClassLoader加载应用程序的类路径下的类。每个ClassLoader都有其父ClassLoader,它们形成了一个层次结构,子ClassLoader可以委托父ClassLoader去加载类,避免了...
Java ClassLoader机制是Java虚拟机(JVM)中一个至关重要的组成部分,它的主要任务是将类的.class文件加载到JVM中...理解并掌握ClassLoader的工作原理和在OSGi中的应用,对于开发高效、可扩展的Java应用程序至关重要。
2. Extension ClassLoader:扩展类加载器。其主要任务是加载JRE的扩展目录下的JAR包,如JAVA_HOME/jre/lib/ext目录或由java.ext.dirs系统属性指定的路径。这种方式允许开发者添加JVM扩展功能,而无需修改核心类库。...
Extension ClassLoader则负责加载`<JAVA_HOME>\lib\ext`目录下的扩展类库。Application ClassLoader则负责加载用户类路径(ClassPath)中的类。 ClassLoader的工作流程主要包括三个阶段:加载、验证和初始化。在...
Extension ClassLoader则负责加载`jre/lib/ext`目录下的扩展类库。最后,App ClassLoader加载的是应用类路径(ClassPath)中的类。 ClassLoader的工作流程主要包含以下步骤: 1. **查找类**:当JVM需要加载一个类...
通过扩展ClassLoader,我们可以控制加载过程,实现加密资源的解密逻辑。这种机制使得攻击者即使获取了加密后的JAR,也无法直接阅读或反编译其中的源码,从而提高了代码的安全性。 在提供的压缩包文件列表中,`core-...
- ClassLoader Hook:通过替换或扩展ClassLoader,实现类的动态加载。 - System Service Hook:对系统服务如ActivityManagerService进行Hook,控制插件的生命周期。 **6. 示例项目:HookPlugin** "HookPlugin"很...
Extension ClassLoader接着加载`<JAVA_HOME>/jre/lib/ext`目录下的扩展类库。最后,AppClassLoader加载应用的`classpath`指定的类。 ClassLoader的工作流程包括查找、加载和验证。当一个类被请求加载时,...
2. Extension ClassLoader:扩展类加载器,负责加载JRE的扩展目录(jre/lib/ext目录下的jar文件)。 3. AppClassLoade:应用程序类加载器,也叫系统类加载器,负责加载用户类路径(classpath)上的类。 在Java中,...
该项目是一款基于ClassLoader扩展的Spring Boot JAR安全加密运行工具源码,包含74个文件,其中69个为Java源文件,1个为Git忽略文件,1个为LICENSE文件,1个为Markdown文件,1个为XML文件,以及1个Go语言文件。...
总结,理解ClassLoader的加载机制和原理对于优化程序性能、设计可扩展的系统至关重要。通过自定义ClassLoader,开发者可以实现许多高级功能,如动态加载、插件机制等,从而增强软件的灵活性和可维护性。同时,掌握...
默认情况下,Java使用系统ClassLoader(Bootstrap ClassLoader)加载JDK核心库,然后是Extension ClassLoader加载扩展库,最后是App ClassLoader加载应用类路径(ClassPath)下的类。当这些默认ClassLoader无法满足...
总之,理解并掌握如何使用ClassLoader动态加载Class是Java开发中的重要技能,它能帮助我们构建更灵活、可扩展的系统。在实现过程中,要兼顾性能、安全和可维护性,合理设计类加载策略,确保代码的高效运行。