java-解决jar包反射获取不到类

2023-10-20 10:44:19 浏览数 (2)

前言

在开发一个基础工具包给业务组的小伙伴们使用的时候,发现一个小问题,就是在反射的时候在自己电脑上运行的正常,但是打成jar包后,就class not fuond,有点奇怪。 如果能借助Spring这个都不是事,关键是不能用。

场景复现

下面的目的是,找到这个指定包下的所有类,对我指定了注解的类进行加载,其实就是一个可以灵活装配、拆卸的策略模式。这样业务的类可以通过指定注解来选择是否成为业务的一部分。 下面这段代码在IDE中运行正常。

代码语言:javascript复制

import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.net.JarURLConnection;
import java.net.URL;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Map;
import java.util.Properties;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;

/**
 * 类加载工厂
 *
 * @author liukai
 * @since 2015/9/9.
 */
public class GeneratorFactory {

  public static Map<String, TransactionCreator> creators = new HashMap<>();

  static {
    // 基于 IDE 运行
    reflectByClass();
  }

  public static void reflectByClass() {
    try {
      ArrayList<Class> creatorsClass = new ArrayList<>();
      ArrayList<File> classFiles = new ArrayList<>();
      // 拿到关键接口
      Class<?> interfaceClass = Class.forName("com.liukai.test.generate.TransactionCreator");
      String packageName = interfaceClass.getPackage().getName();
      // 拿到 ClassLoader
      ClassLoader contextClassLoader = Thread.currentThread().getContextClassLoader();
      String path = packageName.replace(".", "/");
      System.out.println("path: "   path);
      Enumeration<URL> resources = contextClassLoader.getResources(path);
      System.out.println("Enumeration<URL> resources: "   resources);
      while (resources.hasMoreElements()) {
        URL url = resources.nextElement();
        System.out.println("url.getFile(): "   url.getFile());
        classFiles.add(new File(url.getFile()));
      }
      for (File file : classFiles) {
        creatorsClass.addAll(findClass(file, packageName));
      }

      for (Class clazz : creatorsClass) {
        System.out.println("Class clazz : creatorsClass");
        Creator declaredAnnotation = (Creator) clazz.getAnnotation(Creator.class);
        if (declaredAnnotation != null) {
          System.out.println("creators: add"   declaredAnnotation.type());
          creators.put(declaredAnnotation.type(), (TransactionCreator) clazz.newInstance());
        }
      }
    } catch (ClassNotFoundException | IOException | IllegalAccessException | InstantiationException e) {
      e.printStackTrace();
    }
  }

  public static TransactionCreator getGenerator(String type) {
    System.out.println("creators: "   creators.size());
    return creators.get(type);
  }

  private static ArrayList<Class> findClass(File file, String packagename) {
    ArrayList<Class> list = new ArrayList<>();
    if (!file.exists()) {
      return list;
    }
    File[] files = file.listFiles();
    for (File file2 : files) {
      if (file2.isDirectory()) {
        assert !file2.getName().contains(".");
        ArrayList<Class> arrayList = findClass(file2, packagename   "."   file2.getName());
        list.addAll(arrayList);
      } else if (file2.getName().endsWith(".class")) {
        try {
          list.add(Class.forName(packagename   '.'   file2.getName().substring(0,
                  file2.getName().length() - 6)));
        } catch (ClassNotFoundException e) {
          e.printStackTrace();
        }
      }
    }
    return list;
  }

}

解决方式: 由于打成jar包后,类路径多了一层jar,所以加载时,要考虑到jar包路径,以下这个demo,实际可以通过获取运行路径变量替换写死的路径。

代码语言:javascript复制
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.net.JarURLConnection;
import java.net.URL;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Map;
import java.util.Properties;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;

/**
 * 类加载工厂
 *
 * @author liukai
 * @since 2015/9/9.
 */
public class GeneratorFactory {

  public static Map<String, TransactionCreator> creators = new HashMap<>();

  static {
    // 基于 jar 包反射
    reflectByJar();
  }

  public static void reflectByJar() {
    String path = "/Users/liukai/workspaces/java/test/build/libs/";
    String fileName = "test.jar";
    try {
      JarFile jarFile = new JarFile(path   fileName);
      Enumeration<JarEntry> e = jarFile.entries();
      JarEntry entry;
      while (e.hasMoreElements()) {
        entry = (JarEntry) e.nextElement();
        if (entry.getName().indexOf("META-INF") < 0 && entry.getName().indexOf(".class") >= 0) {
          String classFullName = entry.getName();
          //去掉后缀.class
          String className = classFullName.substring(0, classFullName.length() - 6).replace("/", ".");
          Class<?> clazz;
          try {
            try {
              if (!className.contains("com.liukai.test")) {
                continue;
              }
              System.out.println("Class clazz : creatorsClass:"   className);
              clazz = Class.forName(className);
              Creator declaredAnnotation = (Creator) clazz.getAnnotation(Creator.class);
              if (declaredAnnotation != null) {
                System.out.println("creators: add"   declaredAnnotation.type());
                creators.put(declaredAnnotation.type(), (TransactionCreator) clazz.newInstance());
              }
            } catch (ClassNotFoundException e1) {
              e1.printStackTrace();
            }

          } catch (Exception e1) {

          }

        }
      }
    } catch (IOException e) {
      e.printStackTrace();
    }
  }

总结

感觉还是不智能,应该有一个通用的方法,无论我是什么场景,只需要一套代码就解决,而不是用户来抽像,这个JDK应该得提供。

0 人点赞