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

在组件的开发过程中,我们可能会用到组件上传功能,这个时候就会将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方法不会,根据不同的使用场景进行选择了,如果类中没有需要初使化的静态块,那就是两个都可以随便选择了。


  • 1
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 5
    评论
评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值