在上文《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。