手写Spring工具类开发

2023-09-22 源码探究Spring

回忆Spring Boot项目,我们可以通过入口类找到其所在包下的所有类。首先我们需要使用反射来获取该包下的所有类,然后才能筛选出使用了注解修饰的类,并将它们添加到容器中作为bean

# ClassUtil工具类实现对类的操作

为了实现这个功能,我们可以创建一个ClassUtil类,它提供了一些实用的方法来从给定的包中提取类。这个类中的方法使用反射来加载类,并将它们添加到一个集合中,注解反射的知识可以查看注解与反射实战。核心步骤如下:

  1. extractPackageClass:获取包下类集合,本类中最核心的方法,从这个方法开始往下看;

    • 首先获取到类加载器,拿到编译后的包所在的真实路径,如果没找到则返回null并打印日志
    • 如果类加载器判断是file文件类型,则获取到真实的文件地址,递归获取文件夹下的.class文件,存放至set集合
  2. extractClassFile:获取packageName包下所有的class文件(包括子package的class文件)

    • 该方法是一个递归方法,递归的出口是判断如果拿到的是文件而不是文件夹则停止递归
    • 第二步是找“当前目录”下所有的.class文件存放至set集合,并筛选出所有的文件夹
    • 根据第二步找到的文件夹,继续递归,直至找到所有.class文件存放到Set集合
  3. addToClassSet:根据class文件的绝对值路径获取并生成class对象,并放入classSet集合中

    • extractClassFile第二步就是调用本方法,将class存入Set对象,主要分为两步
    1. 从class文件的绝对值路径,提取出包含了package的类名,将D:/simple/target/classes/com/xk857/entity/Main.class改成com.xk857.Main
    2. 通过反射机制获取对应的Class对象并加入到classSet里
  4. newInstance:是一个供外部使用的,实例化对象的类,默认实例化空参构造;accessible设置为真则代表即使是private修饰的构造函数,也仍能创建成功。

@Slf4j
public class ClassUtil {

    private static final String FILE_PROTOCOL = "file";

    /**
     * 获取包下类集合
     * @param packageName 包名的称
     * @return 类集合cccc
     */
    public static Set<Class<?>> extractPackageClass(String packageName) {
        // 1.获取到类的加载器
        ClassLoader classLoader = getClassLoader();

        // 2.通过类加载器获取到加载的资源
        URL url = classLoader.getResource(packageName.replace(".", "/"));
        if (url == null) {
            log.warn("unable to retrieve anything from package: {}", packageName);
            return null;
        }

        // 3.依据不同的资源类型,采用不同的方式获取资源的集合
        Set<Class<?>> classSet = null;
        if (FILE_PROTOCOL.equalsIgnoreCase(url.getProtocol())) {
            classSet = new HashSet<>();
            // 获取真实的文件地址
            File packageDirectory = new File(url.getPath());
            extractClassFile(classSet, packageDirectory, packageName);
        }
        return classSet;
    }

    /**
     * 获取packageName包下所有的class文件(包括子package的class文件)
     * @param emptyClassSet 获取到的class文件,存入classSet集合中
     * @param fileSource    包路径,真实的文件地址
     * @param packageName   包的名称
     */
    private static void extractClassFile(Set<Class<?>> emptyClassSet, File fileSource, String packageName) {
        // 1.递归出口,如果是文件则停止递归
        if (!fileSource.isDirectory()) {
            return;
        }
        // 2.如果是文件夹,筛选出文件夹内的文件夹,如果是文件则直接处理
        File[] directoryList = fileSource.listFiles(pathname -> {
            if (pathname.isDirectory()) {
                return true;
            } else {
                // 2.1 获取文件的绝对值路径
                String absoluteFilePath = pathname.getAbsolutePath();
                // 2.2 如果是class文件则直接加载
                if (absoluteFilePath.endsWith(".class")) {
                    addToClassSet(emptyClassSet, absoluteFilePath, packageName);
                }
            }
            return false;
        });

        // 3.遍历文件夹,递归处理文件夹内的文件
        if (directoryList != null) {
            for (File file : directoryList) {
                extractClassFile(emptyClassSet, file, packageName);
            }
        }
    }

    /**
     * 根据class文件的绝对值路径获取并生成class对象,并放入classSet集合中
     * @param classSet         生成的class对象存放于该集合
     * @param absoluteFilePath 文件的绝对路径
     * @param packageName      包名称
     */
    private static void addToClassSet(Set<Class<?>> classSet, String absoluteFilePath, String packageName) {
        // 1.从class文件的绝对值路径,提取出包含了package的类名,将com/xk857/Main.class改成com.xk857.Main
        absoluteFilePath = absoluteFilePath.replace(File.separator, ".");
        String className = absoluteFilePath.substring(absoluteFilePath.indexOf(packageName), absoluteFilePath.lastIndexOf("."));
        // 2.通过反射机制获取对应的Class对象并加入到classSet里
        Class targetClass = loadClass(className);
        classSet.add(targetClass);
    }

    /**
     * 根据类路径名称获取Class对象
     */
    private static Class<?> loadClass(String className) {
        try {
            return Class.forName(className);
        } catch (ClassNotFoundException e) {
            log.error("load class error:", e);
            throw new RuntimeException(e);
        }
    }

    /**
     * 获取类加载器
     */
    public static ClassLoader getClassLoader() {
        return Thread.currentThread().getContextClassLoader();
    }
    
  /**
     * 实例化Class对象
     * @param clazz Class对象
     * @param accessible 是否支持创建出私有class对象的实例
     * @return 类的实例化
     */
    public static <T> T newInstance(Class<?> clazz, boolean accessible) {
        try {
            Constructor<?> constructor = clazz.getDeclaredConstructor();
            constructor.setAccessible(accessible);
            return (T) constructor.newInstance();
        } catch (Exception e) {
            log.error("实例化对象失败:{}", e.getMessage());
            throw new RuntimeException(e);
        }
    }
}

# 判空工具类

public class ValidationUtil {

    /**
     * String是否为null或""
     * @param obj String
     * @return 是否为空
     */
    public static boolean isEmpty(String obj) {
        return (obj == null || obj.isEmpty());
    }

    /**
     * Array是否为null或者size为0
     * @param obj Array
     * @return 是否为空
     */
    public static boolean isEmpty(Object[] obj) {
        return obj == null || obj.length == 0;
    }
    
    /**
     * Collection是否为null或size为0
     * @param obj Collection
     * @return 是否为空
     */
    public static boolean isEmpty(Collection<?> obj){
        return obj == null || obj.isEmpty();
    }
    
    /**
     * Map是否为null或size为0
     * @param obj Map
     * @return 是否为空
     */
    public static boolean isEmpty(Map<?, ?> obj) {
        return obj == null || obj.isEmpty();
    }
}

上次更新: 5 个月前