Spring手动扫描Class文件并注入到Spring容器中

1. 场景

在很多时候我们会遇到需要将自己写的Class文件以Bean的方式注入到Spring容器中,交由Spring容器管理。

2. 具体做法

如果我们是一两个Class文件的话,我们可能直接指定类名就可以了,但是如果是一个package包需要我们去扫描呢?所以我们可以模拟一个Spring的包扫描原理,搞一个专门处理扫描Class 文件的方法。

2.1 原理

通过 Thread.currentThread().getContextClassLoader().getResource("") 可以获取当前加载Class 文件的绝对路径。然后通过递归扫描文件,获取所有的Class文件。

在这里插入图片描述

2.2 具体代码:

首先需要一个工具类将我们需要加载的包文件中需要加载的Class文件都拿到。

import java.io.File;
import java.util.*;

/**
 * @author long
 */
public class ClassScanUtil {

    public static Map<String, Class> getBeans(String packageName) throws ClassNotFoundException {
        // TODO 将当前报名转为路径格式
        List<String> strList = Arrays.asList(packageName.split("\\."));
        StringBuilder stringBuilder = new StringBuilder();
        strList.stream().forEach(item -> {
            stringBuilder.append(item + "/");
        });
        // 获取当前包的绝对路径
        String file = Thread.currentThread().getContextClassLoader().getResource(stringBuilder.toString().substring(0, stringBuilder.length() - 1)).getFile();
        Map<String, Class> classList = new HashMap<>();
        // 递归获取Class文件
        getAllClassList(packageName, file, classList);
        return classList.isEmpty() ? null : classList;
    }


    /**
     * 递归加载Class文件
     * @param path
     */
    private static void getAllClassList(String packageName, String path, Map<String, Class> classList) throws ClassNotFoundException {
        File files = new File(path);
        File[] listFiles = files.listFiles();
        for(File file : listFiles) {
            // 是目录
            if (file.isDirectory()) {
                String currentPackage = packageName + "." + file.getName();
                getAllClassList(currentPackage, file.getAbsolutePath(), classList);
            }
            // 如果是.class结尾的文件
            if (file.getName().endsWith(".class")) {
                String name = file.getName();
                String fileName = name.substring(0, name.indexOf("."));
                String currentPackage = packageName + "." + name.substring(0, name.indexOf("."));
                Class forName = Class.forName(currentPackage);
                // 通过自定义的一个标志注解类,来标识是否需要被加载到Spring容器。
                if (forName.isAnnotationPresent(SDKComponent.class)) {
                    System.out.println("------- [ 读取到需要注入的Bean类 : " + currentPackage + " ]  ...... ");
                    classList.put(ChangeInitials(fileName), forName);
                }
            }
        }
    }

	// 将 类名的首字母小写
    private static String ChangeInitials(String name) {
        char[] str = name.toCharArray();
        if (str[0] >= 65 && str[0] <= 90) {
            str[0] += 32;
        }
        return String.valueOf(str);
    }

}

SDKComponent自定义注解类

import java.lang.annotation.*;

/**
 * @author long
 */
@Target(value = {ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface SDKComponent {

    String name = "";

}

然后将这需要的注册的类注入到Spring容器。我们需要实现BeanDefinitionRegistryPostProcessor, BeanDefinitionRegistryPostProcessor继承自BeanFactoryPostProcessor,是一种比较特殊的BeanFactoryPostProcessorBeanDefinitionRegistryPostProcessor中定义的``postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry)`方法 可以让我们实现自定义的注册bean定义的逻辑。

在这里插入图片描述

​ 扫描文件的class文件工具类,返回的数据类型结构

import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.beans.factory.support.AbstractBeanDefinition;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.beans.factory.support.BeanDefinitionRegistryPostProcessor;

import java.util.Map;

/**
 * @author long
 */
public class BeanRegistrar implements BeanDefinitionRegistryPostProcessor  {

    @Override
    public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException {
        try {
            // 调用我们刚才的工具类,将扫描到的类注放到Map中
            // 这里的Map是包含 需要注入Bean(class)的小写类名,具体类型,看上图。
            Map<String, Class> classMap = ClassScanUtil.getBeans(URLConstants.PACKAGE_PATH);
            if (!classMap.isEmpty()) {
                classMap.forEach((key, value) -> {
                    System.out.println("=======>>>  正在加载 " + key + " —— " + value + "......  ");
                    BeanDefinitionBuilder beanDefinitionBuilder = BeanDefinitionBuilder.genericBeanDefinition(value);
                    AbstractBeanDefinition beanDefinition = beanDefinitionBuilder.getBeanDefinition();
                    registry.registerBeanDefinition(key, beanDefinition);
                });
            }
        } catch (ClassNotFoundException e) {
            System.out.println("初始化 Beans 异常");
            e.printStackTrace();
        }
    }

    @Override
    public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {

    }
}

最后将这个BeanRegistrar类注入到Spring容器,在项目启动的时候就可以自动启动这个类开始扫描我们自己的包中的Class文件了。

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

/**
 * @author long
 */
@Configuration
public class SSOConfig {

    @Bean
    public BeanRegistrar setBeanRegistrar() {
        return new BeanRegistrar();
    }

}

3. 从容器中拿注入的Bean

上面我们已经注入了Bean,那么如果拿到注入到容器中的Bean呢?我在介绍一个工具类。

那么最简单的就是直接实现一个接口 ApplicationContextAware, 当一个类实现了这个接口(ApplicationContextAware)之后,这个类就可以方便获得ApplicationContext中的所有bean,可以调取spring容器中管理的各个bean

import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.stereotype.Component;

/**
 * @author long
 * @Description
 * @create 2019-04-15 0:01
 */
@SDKComponent
public class SpringUtil implements ApplicationContextAware {

    private static ApplicationContext applicationContext;


    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        if (SpringUtil.applicationContext == null) {
            SpringUtil.applicationContext = applicationContext;
        }
    }

    /**
     * 获取applicationContext
     * @return
     */
    public static ApplicationContext getApplicationContext() {
        return applicationContext;
    }

    /**
     * 通过name获取 Bean.
     * @param name
     * @return
     */
    public static Object getBean(String name){
        return getApplicationContext().getBean(name);
    }

    /**
     * 通过class获取Bean.
     * @param clazz
     * @param <T>
     * @return
     */
    public static <T> T getBean(Class<T> clazz){
        return getApplicationContext().getBean(clazz);
    }

    /**
     * 通过name,以及Clazz返回指定的Bean
     * @param name
     * @param clazz
     * @param <T>
     * @return
     */
    public static <T> T getBean(String name,Class<T> clazz){
        return getApplicationContext().getBean(name, clazz);
    }

}

4. 结束语

有些事情,不去做,陈平安心里不痛快。可有些事情,再不痛快,也只能忍着。

—— 《剑来》

Logo

权威|前沿|技术|干货|国内首个API全生命周期开发者社区

更多推荐