手動注入獲取Spring容器中的Bean

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. 結束語

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

—— 《劍來》

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章