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

论坛 期权论坛 脚本     
匿名网站用户   2020-12-19 21:37   25   0

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


分享到 :
0 人收藏
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

积分:1136255
帖子:227251
精华:0
期权论坛 期权论坛
发布
内容

下载期权论坛手机APP