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
,是一種比較特殊的BeanFactoryPostProcessor
。BeanDefinitionRegistryPostProcessor
中定義的``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. 結束語
有些事情,不去做,陳平安心裏不痛快。可有些事情,再不痛快,也只能忍着。
—— 《劍來》