从数据库读取JAR并加载到Classpath中

来源:互联网 发布:清除session php 编辑:程序博客网 时间:2024/06/05 18:30

在组件的开发过程中,我们可能会用到组件上传功能,这个时候就会将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");// ExecuteResultSet rs = stmnt.executeQuery();if (rs.next()) {// Get as a BLOBBlob aBlob = rs.getBlob(1);allBytesInBlob = aBlob.getBytes(1, (int) aBlob.length());}// Close resourcesrs.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 classpathif (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 initConnection 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 jarClass 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 forClassLoader 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 initConnection 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 jarClass 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方法不会,根据不同的使用场景进行选择了,如果类中没有需要初使化的静态块,那就是两个都可以随便选择了。


原创粉丝点击