java中使用URLClassLoader访问外部jar包的java类

Posted itboys

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了java中使用URLClassLoader访问外部jar包的java类相关的知识,希望对你有一定的参考价值。

很多时候 我们写的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();  
      

 

以上是关于java中使用URLClassLoader访问外部jar包的java类的主要内容,如果未能解决你的问题,请参考以下文章

URLClassLoader 内存泄漏 Java

java.lang.ClassLoader与java.net.URLClassLoader学习

使用URLClassLoader动态加载jar

使用URLClassLoader动态加载jar

java安全——类加载和Unsafe类(ClassLoader,URLClassLoader)

java安全——类加载和Unsafe类(ClassLoader,URLClassLoader)