在组件的开发过程中,我们可能会用到组件上传功能,这个时候就会将JAR等其它信息都存放到数据库,在应用初使化的时候,将组件的JAR从数据库中读出来,并一起加载到Classpath中。
我们可以分成几以下几布操作:
1、将JAR字节数据从数据库中读出来存到本地JAR文件;
1.1 将JAR字节数据从数据库读到内存中
/**
* Load jar byte data from database into byte array.
*
* @param conn
* @return
* @throws SQLException
*/
private static byte[] getJarBlobDataFromDatabase(Connection conn) throws SQLException {
byte[] allBytesInBlob = null;
PreparedStatement stmnt = conn.prepareStatement("select blobdata from table");
// Execute
ResultSet rs = stmnt.executeQuery();
if (rs.next()) {
// Get as a BLOB
Blob aBlob = rs.getBlob(1);
allBytesInBlob = aBlob.getBytes(1, (int) aBlob.length());
}
// Close resources
rs.close();
stmnt.close();
return allBytesInBlob;
}
1.2 将读出来的字节数据,写为本地文件:
/**
* Write the jar byte data into file
*
* @param strFilePath
* @param bytes
* @throws IOException
*/
public static void writeFile(String strFilePath, byte[] bytes) throws IOException {
FileOutputStream fos = new FileOutputStream(strFilePath);
fos.write(bytes);
fos.flush();
fos.close();
}
2、通过URLClassLoader将文件Jar加载到Classpath中。
URLClassLoader可以将本地JAR文件,网络上的JAR都可以加载到classpath中,实现代码如下:
/**
* Add jar file into classpath.
*
* @param jarFile
* @param addMainfestClasspath
* tell the load if load the class-path in the mainfest file.
* @throws IOException
*/
public static void addJarFileToClasspath(File jarFile, boolean addMainfestClasspath) throws IOException {
List<URL> urls = new ArrayList<URL>(5);
urls.add(jarFile.toURI().toURL());
if (addMainfestClasspath) {
JarFile jf = new JarFile(jarFile);
Manifest mf = jf.getManifest(); // if jar has a class-path in
// manfist
// add it's entries. That means we
// should get it's dependency jar
// before
// add this jar to classpath
if (mf != null) {
String cp = mf.getMainAttributes().getValue("class-path");
if (cp != null) {
for (String cpe : cp.split("\\s+")) {
File lib = new File(jarFile.getParentFile(), cpe);
urls.add(lib.toURI().toURL());
}
}
}
jf.close();
}
URLClassLoader sysloader = (URLClassLoader) ClassLoader.getSystemClassLoader();
Class<? extends URLClassLoader> sysclass = URLClassLoader.class;
try {
Method method = sysclass.getDeclaredMethod("addURL", URL.class);
method.setAccessible(true);
for (URL url : urls) {
method.invoke(sysloader, url);
}
} catch (Throwable t) {
throw new IOException("Error, could not add URL to system classloader", t);
}
}
以上这一段代码,实现将jar文件加载到classpath,以及控制参数可以控制是否加载mainfest文件中class-path指定的jar。
这里需要注意一下的是我们是如何将jar加载到系统的classpath中,通过代码:
URLClassLoader sysloader = (URLClassLoader) ClassLoader.getSystemClassLoader();
获取到系统的Classloader,然后通过反射将要加入到系统classpath的jar的URL加入到系统的Classloader的classpath中,之所以要通过反射才能够加进去,那是因为addURL这个方法是protected,不能允跨包调用:
Method method = sysclass.getDeclaredMethod("addURL", URL.class);
method.setAccessible(true);
for (URL url : urls) {
method.invoke(sysloader, url);
}
这样我们就实现了将自已的JAR加入到了系统的classpath(通常这里是AppClassLoader)中,我们在其它地方就可以通过Class.forName或classLoader.loadClass进行加载了。
3、测试
以下是这个测试是否成功将jar加到了我们的系统的classloader中:
/**
* @param args
* @throws SQLException
*/
public static void main(String[] args) {
//omit conn init
Connection conn = null;
try {
byte[] bytes = getJarBlobDataFromDatabase(conn);
String location = "d:/test.jar";
writeFile(location, bytes);
addJarFileToClasspath(new File(location), true);
// Get a class from the jar
Class clz = Thread.currentThread().getContextClassLoader().loadClass("com.test.TestClass");
// If this can print and no exception. That means we load the jar successfully.
System.out.println(clz);
} catch (Exception e) {
e.printStackTrace();
} finally {
if (conn != null) {
try {
conn.close();
} catch (SQLException e) {
}
}
}
}
补充说明:
1、上面我们提到了通过反射将jar的URL加到系统的的URLClassLoader中,如果我们只是需要jar在当前生命周期之内有效,那我们可以不通过反射这种方式操作,我们可以新起一个classloader:
public static ClassLoader addJarFileToNewClasspath(File jarFile, boolean addMainfestClasspath) throws IOException {
List<URL> urls = new ArrayList<URL>(5);
urls.add(jarFile.toURI().toURL());
if(addMainfestClasspath){
JarFile jf = new JarFile(jarFile);
Manifest mf = jf.getManifest();
if (mf != null) {
String cp = mf.getMainAttributes().getValue("class-path");
if (cp != null) {
for (String cpe : cp.split("\\s+")) {
File lib = new File(jarFile.getParentFile(), cpe);
urls.add(lib.toURI().toURL());
}
}
}
jf.close();
}
// End for
ClassLoader cl = null;
if (urls.size() > 0) {
cl = new URLClassLoader(urls.toArray(new URL[urls.size()]),ClassLoader.getSystemClassLoader());
}
return cl;
}
这里的实现差别就是new了一个classloader,虽然指定了其parent classloader,但是在使用的时候,必须使用当前new出来的class loader才可以找到类,因为新的jar中的class并没有被加到当前系统的classloader中,因而我们在使用的时候,就需要如下使用:
/**
* @param args
* @throws SQLException
*/
public static void main(String[] args) {
//Omit conn init
Connection conn = null;
try {
byte[] bytes = getJarBlobDataFromDatabase(conn);
String location = "d:/test.jar";
writeFile(location, bytes);
ClassLoader cl = addJarFileToNewClasspath(new File(location),true);
// Get a class from the jar
Class clz = cl.loadClass("com.ubs.sae.business.service.starrclient.Good");
// If this can print and no exception. That means we load the jar into classpath successfully.
System.out.println(clz);
} catch (Exception e) {
e.printStackTrace();
} finally {
if (conn != null) {
try {
conn.close();
} catch (SQLException e) {
}
}
}
}
并且我们不能够使用当前classloader的loadClass方法,不能够使用Class.forName方法,因为Class.forName是到当前的系统classloader去查找的,而上面通过反射将jar加到当前系统的classloader中,此时在不考虑类是否会被初使化的情况下,使用classloader的loadClass方法或者是Class.forName都可以找到我们需要的类。
2、说一下classloader的loadClass与Class.forName的区别吧,就是Class.forName会对类进行初使化,如执行类中的静态块,而classloader的loadClass方法不会,根据不同的使用场景进行选择了,如果类中没有需要初使化的静态块,那就是两个都可以随便选择了。