java中使用URLClassLoader訪問外部jar包的java類

Franson發表於2016-12-08

很多時候 我們寫的Java程式是分模組的,有很好的擴充套件機制,即我們可以為我們自己的java類新增外掛,來執行將來某天我們可能開發出來的類,以下稱這些類為外掛類。

下邊是一種簡單的實現方法:

Class A 作為程式的主入口,其中包含了程式的執行入口(main)函式。然後在main函式中通過外部的配置檔案,然後通過外部的配置檔案,我們可以獲得外掛類的資訊(位於哪個jar包,jar包的具體路徑),然後獲得jar包中某一個類的例項,來完成相應的工作。這個jar包很可能是外部的jar包,是我們自己寫好的,那麼我們放到哪裡,他才能自己找到呢?我嘗試過很多次,除非將其具體目錄,放到class_path中才可以成功執行,否則報的異常只有一個ClassNotFoundException,就是找不到類。不過還有一種方法,就是將該jar包解壓到執行jar包所在的目錄,這樣就可以通過class_path中的.來獲得相應的類了。不過這樣會顯得很不專業,java寫出來的東西都是jar包啊,自己感覺的。放到claspath中,不能每次寫出新的jar包都配置一遍吧!

如此出現瞭如下的解決辦法:

想了解解決辦法的含義,首先要了解java的類載入機制。眾所周知,程式若想執行,必須載入到記憶體當中才能成功執行。java程式並不是可執行檔案,由許多獨立的類檔案來完成。所以java中載入程式是以類為單外來完成的。這也就需要我們來簡單瞭解一下java的class loader載入機制。

java程式開始執行,遇到的第一個classloader是bootstrap classloader,這個classloader是用c++語言編寫,通過他來完成載入java中的核心類。第二個classloader是extension classloader,載入的是jre/lib目錄中的ext目錄中的jar包。然後第三個是system classloader,也被稱為應用載入器,主要負責完成載入-classpath 或者系統中的全域性變數ClassPath中的類。System.out.println(System.getProperty(“java.class.path”));可以獲得classpath的配置,也就是system classloader 載入的類,第四個class loader可能是使用者自定義的載入器,來自定義載入類。通常一個類的載入過程是這樣的通過當前的類載入器的父載入器嘗試查詢,如果沒有再找其父載入器嘗試載入,直到最終的bootstrap classloader為止,如果還沒有找到,那麼就開始從上往下載入類。這樣做的目的是防止自定義的類來覆蓋系統中的類,如果沒有這種機制很容易出現這種笑話,自己寫了一個String類,然後new string的時候是自己寫的String類,這樣就比較好玩了。

1.自己定義URLClassLoader物件載入外部jar包,針對jar包裡面不再出現別的jar包的情況,即只解析.class檔案:

private static void test1() {  
 String path = "D:\\test.jar";//外部jar包的路徑  
 Set<Class<?>> classes = new LinkedHashSet<Class<?>>();//所有的Class物件  
 Map<Class<?>, Annotation[]> classAnnotationMap = new HashMap<Class<?>, Annotation[]>();//每個Class物件上的註釋物件  
 Map<Class<?>, Map<Method, Annotation[]>> classMethodAnnoMap = new HashMap<Class<?>, Map<Method,Annotation[]>>();//每個Class物件中每個方法上的註釋物件  
 try {  
  JarFile jarFile = new JarFile(new File(path));  
  URL url = new URL("file:" + path);  
  ClassLoader loader = new URLClassLoader(new URL[]{url});//自己定義的classLoader類,把外部路徑也加到load路徑裡,使系統去該路經load物件  
  Enumeration<JarEntry> es = jarFile.entries();  
  while (es.hasMoreElements()) {  
   JarEntry jarEntry = (JarEntry) es.nextElement();  
   String name = jarEntry.getName();  
   if(name != null && name.endsWith(".class")){//只解析了.class檔案,沒有解析裡面的jar包  
    //預設去系統已經定義的路徑查詢物件,針對外部jar包不能用  
    //Class<?> c = Thread.currentThread().getContextClassLoader().loadClass(name.replace("/", ".").substring(0,name.length() - 6));  
    Class<?> c = loader.loadClass(name.replace("/", ".").substring(0,name.length() - 6));//自己定義的loader路徑可以找到  
    System.out.println(c);  
    classes.add(c);  
    Annotation[] classAnnos = c.getDeclaredAnnotations();  
    classAnnotationMap.put(c, classAnnos);  
    Method[] classMethods = c.getDeclaredMethods();  
    Map<Method, Annotation[]> methodAnnoMap = new HashMap<Method, Annotation[]>();  
    for(int i = 0;i<classMethods.length;i++){  
     Annotation[] a = classMethods[i].getDeclaredAnnotations();  
     methodAnnoMap.put(classMethods[i], a);  
    }  
    classMethodAnnoMap.put(c, methodAnnoMap);  
   }  
  }  
  System.out.println(classes.size());  
 } catch (IOException e) {  
  e.printStackTrace();  
 } catch (ClassNotFoundException e) {  
  e.printStackTrace();  
 }  
}  

 


以上的這種情況可以在別的project專案裡寫test方法,是平時最常用的,如果當.class檔案裡有依賴別的jar包裡的物件的時候,就要把該jar包拷貝到寫此測試方法的project並buildPath,不然的話執行的時候會報找不到Class物件的異常。

2.第二種情況是針對載入jar包裡面的jar包的Class物件,還有讀取某一個properties檔案的方法。

private static void test2() {  
  String path = "D:\\test.jar";//此jar包裡還有別的jar包  
  try {  
   JarFile jarfile = new JarFile(new File(path));  
   Enumeration<JarEntry> es = jarfile.entries();  
   while (es.hasMoreElements()) {  
    JarEntry je = es.nextElement();  
    String name = je.getName();  
    if(name.endsWith(".jar")){//讀取jar包裡的jar包  
     File f = new File(name);  
     JarFile j = new JarFile(f);  
     Enumeration<JarEntry> e = j.entries();  
     while (e.hasMoreElements()) {  
      JarEntry jarEntry = (JarEntry) e.nextElement();  
      System.out.println(jarEntry.getName());  
      //.........接下去和上面的方法類似  
     }  
    }  
//    System.out.println(je.getName());  
    if(je.getName().equals("entity_pk.properties")){  
     InputStream inputStream = jarfile.getInputStream(je);  
     Properties properties = new Properties();  
     properties.load(inputStream);  
     Iterator<Object> ite = properties.keySet().iterator();  
     while (ite.hasNext()) {  
      Object key = ite.next();  
      System.out.println(key + " : " +properties.get(key));  
     }  
    }  
   }  
  } catch (IOException e) {  
   e.printStackTrace();  
  }  
 }  

 


3.第三種情況是在該專案下獲取某個包的Class物件,當然了,測試方法是在該專案下寫的(這樣classLoader就直接可以知道物件了,不需要再自定義URLClassLoader了,用Thread.currentThread().getContextClassLoader().loadClass(.....)就可以直接獲得Class物件了,回去ClassPath下找,System.out.print(System.getProperty("java.class.path"))就可以找到classPath路徑)。

private static Set<Class<?>>  getclass() {  
  Set<Class<?>> classes = new LinkedHashSet<Class<?>>();  
  boolean flag = true;//是否迴圈迭代  
   
  String packName = "com.yk.framework.db";  
//  String packName = "org.jdom";  
  String packDir = packName.replace(".", "/");  
  Enumeration<URL> dir;  
  try {  
   dir = Thread.currentThread().getContextClassLoader().getResources(packDir);  
   while(dir.hasMoreElements()){  
    URL url = dir.nextElement();  
    System.out.println("url:***" + url);  
    String protocol = url.getProtocol();//獲得協議號  
    if("file".equals(protocol)){  
     System.err.println("file型別的掃描");  
     String filePath = URLDecoder.decode(url.getFile(), "UTF-8");  
     System.out.println("filePath :" + filePath);  
     findAndAddClassesInPackageByFile(packName, filePath,flag,classes);  
    }else if("jar".equals(protocol)){  
     System.err.println("jar型別掃描");  
     JarFile jar;  
     jar = ((JarURLConnection)url.openConnection()).getJarFile();  
     Enumeration<JarEntry> entries = jar.entries();  
     while(entries.hasMoreElements()){  
      JarEntry entry = entries.nextElement();  
      String name = entry.getName();  
      System.out.println(">>>>:" + name);  
      //......  
     }  
       
    }  
      
   }  
     
  } catch (IOException e) {  
   e.printStackTrace();  
  }  
  System.out.println(classes.size());  
  return classes;  
    
 }  

 


下面上第二段程式碼

private static void findAndAddClassesInPackageByFile(String packName, String filePath, final boolean flag, Set<Class<?>> classes) {  
 File dir = new File(filePath);  
 if( !dir.exists() || !dir.isDirectory()){  
  System.out.println("此路徑下沒有檔案");  
  return;  
 }  
 File[] dirfiles = dir.listFiles(new FileFilter(){  
  @Override  
  public boolean accept(File pathname) {  
   return flag && pathname.isDirectory() || pathname.getName().endsWith(".class");  
  }  
 });  
 for (File file : dirfiles) {  
  if(file.isDirectory()){//如果是目錄,繼續掃描  
   findAndAddClassesInPackageByFile(packName + "." + file.getName(),file.getAbsolutePath(),flag,classes);  
  }else{//如果是檔案  
   String className = file.getName().substring(0,file.getName().length() - 6);  
   System.out.println("類名:" +className);  
   try {  
    classes.add(Thread.currentThread().getContextClassLoader().loadClass(packName + "." + className));  
   } catch (ClassNotFoundException e) {  
    e.printStackTrace();  
   }  
  }  
 }  
}  

 


好了,大致就是這樣,好好學習,天天向上!哈哈~~

補充:

4.讀取jar包中的entity_pk.properties鍵值對到專案本地entity_pk.properties檔案中

/** 
 * 讀取jar包中的entity_pk.properties鍵值對到專案本地entity_pk.properties檔案中 
 */  
private static void test2() {  
    String path = "D:\\test.jar";  
    try {  
        JarFile jarFile = new JarFile(new File(path));  
        Enumeration<JarEntry> es = jarFile.entries();  
        while (es.hasMoreElements()) {  
            JarEntry jarEntry = (JarEntry) es.nextElement();  
            String name = jarEntry.getName();  
            if(name.equals("entity_pk.properties")){  
                InputStream inputStream = new FileInputStream(name);  
                Properties prop = new Properties();  
                prop.load(inputStream);//先load本地的entity_pk.propertoes檔案  
                InputStream inputStream2 = jarFile.getInputStream(jarEntry);//獲得jar包entity_pk.propertoes檔案流  
                Properties prop2 = new Properties();  
                prop2.load(inputStream2);  
                Enumeration<Object> ks = prop2.keys();  
                OutputStream out = new FileOutputStream(name);  
                while (ks.hasMoreElements()) {  
                    String key =  (String)ks.nextElement();  
                    System.out.println(key + " : " +prop2.getProperty(key));  
                    prop.setProperty(key, prop2.getProperty(key));//把jar包entity_pk.properties鍵值對放入本地  
                }  
                prop.store(out,"");  
            }  
        }  
          
    } catch (IOException e) {  
        e.printStackTrace();  
    }  
      
}  

 


 5.讀寫db_config.xml檔案

/** 
 * 讀寫db_config.xml檔案 
 */  
private static void test3() {  
    String path = "D:\\test.jar";  
    try {  
        JarFile jarFile = new JarFile(new File(path));  
        Enumeration<JarEntry> es = jarFile.entries();  
        while (es.hasMoreElements()) {  
            JarEntry jarEntry = (JarEntry) es.nextElement();  
            String name = jarEntry.getName();  
            if(name.equals("db_config.xml")){  
                InputStream inputStream = jarFile.getInputStream(jarEntry);  
                SAXBuilder builder = new SAXBuilder();  
                Document doc = null;  
                try {  
                    doc = builder.build(inputStream);  
                    if(doc != null){  
                        Element e = doc.getRootElement();  
                        Element proxool = e.getChild("proxool");  
                        List<Element> children = proxool.getChildren();  
                        for (Element element : children) {  
                            System.out.println(element.getText());  
                        }  
                        Element s = new Element("test");  
                        s.addContent("test");  
                        proxool.addContent(s);  
                        XMLOutputter outputter = new XMLOutputter();  
                        outputter.output(doc, new FileOutputStream("config/db_config.xml"));  
                    }  
                } catch (JDOMException e) {  
                    e.printStackTrace();  
                }finally{  
                    if(inputStream != null){  
                        inputStream.close();  
                    }  
                }  
                  
            }  
              
        }  
          
    } catch (IOException e) {  
        e.printStackTrace();  
    }  
}  

 


相關文章