目录
前言
说在前面
首先必须得声明的是,下面的流程并不代表Spring源码中真正的流程。而是,我们通过现在的Spring提供给我们的某些基础功能,反推过来的流程。所以,并不全面,但是会具有一点参考性。
阅读准备
由于Spring源码分析是一个前后联系比较强的过程,而且这边分析,也是按照代码顺序讲解的,所以不了解前置知识的情况下,大概率没办法看懂当前的内容。所以,特别推荐看看我前面的文章(自上而下次序):
- 【Spring专题】Spring底层核心原理解析【学习难度:★★☆☆☆】
思路整理
我们在上一节《【Spring专题】Spring底层核心原理解析》课里面有简单分析过一个Spring容器的一般流程,所以,本节课我们这里尝试写一下简易的Spring容器。
手写源码示例
一、手写前的准备
1.1 注解
既然是需要手写Spring容器,那我们肯定需要自定义一个MyApplicationContext
类,以及自定义注解@ComponentScan
,@Component
,@Autowired
和@Scope
代码如下:
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface Component {
String value() default "";
}
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface ComponentScan {
String value() default "";
}
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface Scope {
String value() default "";
}
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface Autowired {
}
其中@Scope
是为了验证多例Bean跟单例Bean的。当然,需要准备的接口不止于此,后面我会随着方法的完善,逐渐引出其余需要的接口或者注解。
1.2 测试Bean
另外就是测试用的Bean,代码如下:
/**
* @author zhangshen
* @tag 【手写】
* @Date 2023/8/7 19:57
* @slogan 编码即学习
**/
@Component("userService")
public class MyUserService implements InitializingBean, BeanPostProcessor {
private int afterPropertiesSet = 0;
private int postProcessBeforeInitialization = 0;
private int postProcessAfterInitialization = 0;
private static int count = 0;
public void showYourFace() {
System.out.println("手写的spring。。。。。 test一下");
System.out.println("初始化是第几步,答案是:" + afterPropertiesSet);
System.out.println("初始化前是第几步,答案是:" + postProcessBeforeInitialization);
System.out.println("初始化后是第几步,答案是:" + postProcessAfterInitialization);
}
@Override
public void afterPropertiesSet() throws Exception {
System.out.println("这里是初始化,是在aware回调之后");
count++;
afterPropertiesSet = count;
}
@Override
public Object postProcessBeforeInitialization(Object bean, String beanName) {
System.out.println("这里是初始化前");
count++;
postProcessBeforeInitialization = count;
return bean;
}
@Override
public Object postProcessAfterInitialization(Object bean, String beanName) {
System.out.println("这里是初始化后");
count++;
postProcessAfterInitialization = count;
return bean;
}
}
1.3 调用实例
调用实例:
/**
* 手写Spring容器测试
*
* @author zhangshen
* @tag 【手写】
* @Date 2023/8/7 19:55
* @slogan 编码即学习
**/
public class MyApplicationContextTest {
public static void main(String[] args) {
MyApplicationContext context = new MyApplicationContext(MyAppConfig.class);
MyUserService userService = (MyUserService) context.getBean("userService");
userService.showYourFace();
}
}
二、构造方法(构建基本流程)
在上节课中,我们说过,在容器启动的构造方法里面,大致的流程可以分为三步:
所以,我们代码起手式,是新建一个MyApplicationContext
类,然后一个默认的无参构造方法,代码如下:
public class MyApplicationContext {
public MyApplicationContext(Class clazz) {
// 步骤一:扫描
scan(clazz);
// 步骤二:IOC
ioc();
// 步骤三:AOP
aop();
}
}
但事实上,我们前面说过,虽然看似大过程是3步,不过AOP是需要发生在IOC内部的(Bean放入单例池之前),所以,后面,我们会把这个AOP的过程写在IOC里面,如下:
public class MyApplicationContext {
public MyApplicationContext(Class clazz) {
// 步骤一:扫描
scan(clazz);
// 步骤二:IOC
ioc();
}
private void doIOC() {
// 步骤三:AOP
aop();
}
}
三、实现scan()方法
先上扫描的简易流程图:
接下来,我们只需要在doScan()
方法里面实现这些步骤就好了。
/**
* 扫描方法
*
* @param clazz 配置类
*/
private void scan(Class clazz) {
// 步骤一:获取扫描路径
String basePackage = doGetScanPackage(clazz);
if (StrUtil.isEmpty(basePackage)) {
return;
}
// 步骤二、三:从电脑磁盘中加载文件,并且扫描
doLoadClassFromDiskAndScan(basePackage, clazz);
}
3.1 doGetScanPackage():获取扫描路径
/**
* 获取包扫描路径
*
* @param clazz 配置类
* @return 扫描路径
*/
private String doGetScanPackage(Class clazz) {
if (clazz.isAnnotationPresent(ComponentScan.class)) {
ComponentScan componentScanAnnotation = (ComponentScan) clazz.getAnnotation(ComponentScan.class);
return componentScanAnnotation.value();
}
return null;
}
3.2 doLoadClassFromDiskAndScan():从电脑磁盘中加载文件,并且扫描
/**
* 从磁盘中加载class,并且扫描
*
* @param basePackage 扫描路径
*/
private void doLoadClassFromDiskAndScan(String basePackage) {
// 将Java包名转换为电脑路径名
basePackage = basePackage.replace(".", "/");
// 加载
ClassLoader classLoader = MyApplicationContext.class.getClassLoader();
URL resource = classLoader.getResource(basePackage);
File file = new File(resource.getFile());
if (file.isDirectory()) {
for (File listFile : file.listFiles()) {
// 获取全限定名
String fullyQualifiedClassName = transferToFullyQualifiedClassName(listFile);
try {
Class<?> beanClass = classLoader.loadClass(fullyQualifiedClassName);
if (!beanClass.isAnnotationPresent(Component.class)) {
continue;
}
// 记录Bean信息
doRecordBeanInfo(beanClass);
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
}
}
方法解读:
- 在上一步,我们已经获取到了扫描路径,但是扫描路径是我们Java包名,现在需要从磁盘重读取的话,定然是需要转换成电脑磁盘重的路径的。
- 大家可能对classLoader这一块比较难理解,这是属于JVM那一块的。不过这么说,我们自己写的业务代码,除非特别指定,不然都是使用的同一个类加载器
- 这里还有2个关键方法调用,一个是
transferToFullyQualifiedClassName()
,是将文件名转换为类加载器能识别的类全限定名;另一个是doRecordBeanInfo()
方法,记录Bean定义信息的。代码会在后面
3.3 transferToFullyQualifiedClassName() :获取全限定名
/**
* 将电脑磁盘上的文件,转换为AppClassLoader能识别的类全限定名(包 + 类名)
*
* <p>
* 由于JVM的类加载器有三种,默认加载用户自定义class文件的,其实是ApplicationClassLoader
* 该类加载器所能识别的,其实是包名
* </p>
*
* @param listFile 电脑磁盘上的文件
*/
private String transferToFullyQualifiedClassName(File listFile) {
String absolutePath = listFile.getAbsolutePath();
absolutePath = absolutePath.substring(absolutePath.indexOf("org"), absolutePath.indexOf(".class"));
absolutePath = absolutePath.replace("\\", ".");
return absolutePath;
}
*3.4 doRecordBeanInfo() :记录Bean信息
/**
* 过滤,并且记录Bean信息
*/
private void doRecordBeanInfo(Class<?> beanClass) throws NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException {
// 获取bean的名字
String beanName = doGetBeanName(beanClass);
// 记录BeanPostProcessor
if (BeanPostProcessor.class.isAssignableFrom(beanClass)) {
BeanPostProcessor instance = (BeanPostProcessor) beanClass.getConstructor().newInstance();
beanPostProcessorList.add(instance);
}
BeanDefinition beanDefinition = new BeanDefinition();
beanDefinition.setType(beanClass);
if (beanClass.isAnnotationPresent(Scope.class)) {
Scope scopeAnnotation = beanClass.getAnnotation(Scope.class);
String value = scopeAnnotation.value();
beanDefinition.setScope(value);
}
// 记录BeanDefinition
beanDefinitionMap.put(beanName, beanDefinition);
}
这里,引入了一个新的类BeanDefinition
类,源码如下:
/**
* 手写Bean定义
*
* @author zhanghuitong
* @tag 【手写】 【Bean定义】 【图纸】
* @Date 2023/8/8 10:54
* @slogan 编码即学习
**/
@Getter
@Setter
public class BeanDefinition {
private static final String SINGLETON = "singleton";
/**
* Bean类型
*/
private Class type;
/**
* 作用域
* 是原型,还是单例
*/
private String scope = SINGLETON;
}
也许大家会很奇怪,为什么需要引入这么一个东西呢?我都已经扫描好了,我直接根据类信息生成不就行了吗?如果你也有这个想法,那么我反问你一个问题:如果我后面想修改怎么办?还别说,真的有可能修改!Spring提供了那么多拓展机制,其中就有对类信息修改的拓展点(至于具体应用场景,我还没彻底学清楚,我后面学到了我会回来更新的)。但是为了方便大家理解这个玩意的存在,我举个通俗的例子,如下:
BeanDefinition
的存在更像是一份家具定制的图纸,Bean
是具体的某个家具。而Spring里面,在后面我们会学到的ApplicationContext
,则是生产家具的厂家。这样类比的话,你应该能想明白,为什么需要BeanDefinition
了吧。
总结一句话:ApplicationContext
厂家根据BeanDefinition
图纸生成具体的某个家具Bean
。(PS:ApplicationContext
包含BeanFactory
,都是Bean工厂)
四、实现ioc()方法
先看看ioc简易流程图:
(PS:下面的手写源码,没有实现【推断构造方法】的逻辑)
/**
* 进行IOC过程
*/
private void ioc() {
// 循环遍历beanDefinitionMap
Set<Map.Entry<String, BeanDefinition>> entries = beanDefinitionMap.entrySet();
for (Map.Entry<String, BeanDefinition> entry : entries) {
String beanName = entry.getKey();
BeanDefinition beanDefinition = entry.getValue();
if (beanDefinition.getScope().equals(BeanDefinition.SINGLETON)) {
// 创建Bean
Object bean = createBean(beanName, beanDefinition);
// AOP
aop();
singletonPool.put(beanName, bean);
}
}
}
方法解读:
- 在这里,就是遍历我们上一步获得的
beanDefinitionMap
图纸Map。 - 接着就开始根据图纸生成Bean了(
createBean()
方法) - 接着看看是否需要AOP了(在放入单例池之前)
- 最后就是放入单例池里面了
*4.1 createBean() :创建Bean
/**
* 创建bean
*
* @param beanName bean名称
* @param beanDefinition 对应的bean定义
*/
private Object createBean(String beanName, BeanDefinition beanDefinition) {
Class clazz = beanDefinition.getType();
Object instance = null;
try {
// 实例化。本应该实现推断构造方法,但是这里从简使用默认的
instance = clazz.getConstructor().newInstance();
// 依赖注入
doDependecyInjection(clazz, instance);
// 各种Aware回调
doAwareCallBack(beanName, instance);
// 初始化前
instance = doInitBefore(beanName, instance);
// 初始化
doInit(instance);
// 初始化后
instance = doInitAfter(beanName, instance);
} catch (InstantiationException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
} catch (NoSuchMethodException e) {
e.printStackTrace();
} catch (Exception e) {
e.printStackTrace();
}
// 返回
return instance;
}
方法解读:
虽然上面的代码是简单的实现,但也稍微能窥见一点IOC
的生命周期了,大家稍微理解下就好。(PS:这里只是简单推理,事实上Spring源码中Bean的声明周期不止于此,还有:实例化前、实例化后呢)
实例化 -> 依赖注入 -> 各种Aware回调 -> 初始化前 -> 初始化 -> 初始化后
因为是简单实现,所以上面这个流程不全,应该是缺了点其他拓展点的实现的。但是局部上的顺序是没啥问题,而且这不是我吹的,而是在Spirng接口上注释的。
4.2 doDependecyInjection() :依赖注入
/**
* 依赖注入
*
* @param clazz 需要被注入的对象的类信息
* @param instance 需要被注入的对象
*/
private void doDependecyInjection(Class clazz, Object instance) throws IllegalAccessException {
Field[] declaredFields = clazz.getDeclaredFields();
for (Field declaredField : declaredFields) {
if (declaredField.isAnnotationPresent(Autowired.class)) {
declaredField.setAccessible(true);
declaredField.set(instance, getBean(declaredField.getName()));
}
}
}
4.3 doAwareCallBack() :各种Aware回调
/**
* 实现各种Aware的回调处理
*
* @param beanName bean名称
* @param instance bean对象
*/
private void doAwareCallBack(String beanName, Object instance) {
if (instance instanceof BeanNameAware) {
((BeanNameAware) instance).setBeanName(beanName);
}
}
方法解读:
在这里,引入了一个新的接口BeanNameAware
,它实现自Aware
接口。其实除了这个我们用来展示的BeanNameAware
还有很多其他的XxxAware
,主要的目的是在实例化之后,让我们获得感知一些Spring组件等等能力。下面是BeanNameAware
接口的代码示例(其中注释源于Spring源码):
/**
* 手写Spring的BeanNameAware接口
*
* @author zhangshen
* @tag 【手写】
* @Date 2023/8/8 19:04
* @slogan 编码即学习
**/
public interface BeanNameAware {
/**
* 在创建该bean的bean工厂中设置该bean的名称。
* 在填充普通bean属性之后,但在初始化回调(如InitializingBean.afterPropertiesSet())或自定义初始化方法之前调用。
* 参数:
* 名称-工厂中bean的名称。请注意,这个名称是工厂中使用的实际bean名称,它可能与最初指定的名称不同:特别是对于内部bean名称,实际bean名称可能通过附加“#…”后缀来使其唯一。如果需要,使用BeanFactoryUtils.originalBeanName(String)方法提取原始bean名称(不带后缀)。
*/
void setBeanName(String name);
}
4.4 doInitBefore() :初始化前
/**
* 实施初始化之前
*
* @param beanName bean名称
* @param instance bean对象
*/
private Object doInitBefore(String beanName, Object instance) {
for (BeanPostProcessor beanPostProcessor : beanPostProcessorList) {
instance = beanPostProcessor.postProcessBeforeInitialization(instance, beanName);
}
return instance;
}
方法解读:
- 初始化前这是属于SpringIOC提供的一个拓展点,需要一个非常非常非常重要的接口,
BeanPostProcessor
,它的定义如下:(其中注释源于Spring源码)
/**
* 手写Spring的ABeanPostProcessor接口
*
* @author zhangshen
* @tag 【手写】
* @Date 2023/8/8 19:04
* @slogan 编码即学习
**/
public interface BeanPostProcessor {
/**
* 在任何bean初始化回调(如InitializingBean的afterPropertiesSet或自定义初始化方法)之前,将此BeanPostProcessor应用于给定的新bean实例。这个bean已经被属性值填充了。返回的bean实例可能是原始bean实例的包装器。
* 默认实现按原样返回给定的bean。
* 参数:
* Bean——新的Bean实例
* beanName—bean的名称
* 返回:
* 要使用的bean实例,无论是原始的还是包装的;如果为空,则不会调用后续的BeanPostProcessors
* 抛出:
* BeansException -在错误的情况下
*/
default Object postProcessBeforeInitialization(Object bean, String beanName) {
return bean;
}
/**
* 在任何bean初始化回调(如InitializingBean的afterPropertiesSet或自定义init-method)之后,将此BeanPostProcessor应用于给定的新bean实例。这个bean已经被属性值填充了。返回的bean实例可能是原始bean实例的包装器。
* 对于FactoryBean,将为FactoryBean实例和由FactoryBean创建的对象调用这个回调(从Spring 2.0开始)。后处理器可以通过相应的FactoryBean instanceof检查来决定是应用于FactoryBean还是已创建的对象,或者两者都应用。
* 这个回调也将在由InstantiationAwareBeanPostProcessor触发的短路之后被调用。postProcessBeforeInstantiation方法,与所有其他BeanPostProcessor回调相反。
* 默认实现按原样返回给定的bean。
* 参数:
* Bean——新的Bean实例
* beanName—bean的名称
* 返回:
* 要使用的bean实例,无论是原始的还是包装的;如果为空,则不会调用后续的BeanPostProcessors
* 抛出:
* BeansException -在错误的情况下
* 参见:
* org.springframework.beans.factory.InitializingBean。afterPropertiesSet, org.springframework.beans.factory.FactoryBean
* 以上翻译结果来自有道神经网络翻译(YNMT)· 通用场景
*/
default Object postProcessAfterInitialization(Object bean, String beanName) {
return bean;
}
}
从上面的注释希望大家在后面真的阅读源码的时候能知道,BeanPostProcessor
这个拓展点就是作用在初始化前后的
4.5 doInit() :初始化
/**
* 实施初始化方法
*
* @param instance bean对象
*/
private void doInit(Object instance) throws Exception {
if (instance instanceof InitializingBean) {
((InitializingBean) instance).afterPropertiesSet();
}
}
方法解读:
- 初始化方法,在这里又是需要一个新的接口,
InitializingBean
,在工作使用频率挺高的。当然也可以通过自定义初始化方法,不一定就是这个。接口代码如下:(其中注释源于Spring源码)
/**
* 手写Spring的ABeanPostProcessor接口
*
* @author zhangshen
* @tag 【手写】
* @Date 2023/8/8 19:04
* @slogan 编码即学习
**/
public interface InitializingBean {
/**
* 在设置了所有bean属性并满足BeanFactoryAware、ApplicationContextAware等要求后,由包含bean的BeanFactory调用。
* 此方法允许bean实例在设置了所有bean属性后对其总体配置和最终初始化执行验证。
* 抛出:
* 异常-在配置错误的情况下(例如设置基本属性失败),或者由于任何其他原因导致初始化失败
*/
void afterPropertiesSet() throws Exception;
}
4.6 doInitAfter() :初始化后
/**
* 实施初始化之后
*
* @param beanName bean名称
* @param instance bean对象
*/
private Object doInitAfter(String beanName, Object instance) {
for (BeanPostProcessor beanPostProcessor : beanPostProcessorList) {
instance = beanPostProcessor.postProcessAfterInitialization(instance, beanName);
}
return instance;
}
方法解读:
- 初始化后,跟初始化前一样,是属于SpringIOC提供的一个拓展点。并且使用的也是
BeanPostProcessor
接口
五、实现aop()方法
这个就不实现了,大家知道有这个东西就好
六、MyApplicationContext完整源码
package org.tuling.spring.handwriten;
import cn.hutool.core.util.StrUtil;
import org.springframework.beans.factory.BeanNameAware;
import org.springframework.beans.factory.NoSuchBeanDefinitionException;
import java.beans.Introspector;
import java.io.File;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.net.URL;
import java.util.*;
/**
* 手写一个简易的Spring容器
*
* @author zhangshen
* @tag 【Spring】 【手写】
* @Date 2023/8/7 19:37
* @slogan 编码即学习
**/
public class MyApplicationContext {
private Map<String, BeanDefinition> beanDefinitionMap = new HashMap<>();
private Map<String, Object> singletonPool = new HashMap<>();
private List<BeanPostProcessor> beanPostProcessorList = new ArrayList<>();
public MyApplicationContext(Class clazz) {
// 扫描
scan(clazz);
// IOC
ioc();
}
/**
* 进行IOC过程
*/
private void ioc() {
// 循环遍历beanDefinitionMap
Set<Map.Entry<String, BeanDefinition>> entries = beanDefinitionMap.entrySet();
for (Map.Entry<String, BeanDefinition> entry : entries) {
String beanName = entry.getKey();
BeanDefinition beanDefinition = entry.getValue();
if (beanDefinition.getScope().equals(BeanDefinition.SINGLETON)) {
// 创建Bean
Object bean = createBean(beanName, beanDefinition);
// AOP
aop();
singletonPool.put(beanName, bean);
}
}
}
/**
* 创建bean
*
* @param beanName bean名称
* @param beanDefinition 对应的bean定义
*/
private Object createBean(String beanName, BeanDefinition beanDefinition) {
Class clazz = beanDefinition.getType();
Object instance = null;
try {
// 实例化。本应该实现推断构造方法,但是这里从简使用默认的
instance = clazz.getConstructor().newInstance();
// 依赖注入
doDependecyInjection(clazz, instance);
// 各种Aware回调
doAwareCallBack(beanName, instance);
// 初始化前
instance = doInitBefore(beanName, instance);
// 初始化
doInit(instance);
// 初始化后
instance = doInitAfter(beanName, instance);
} catch (InstantiationException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
} catch (NoSuchMethodException e) {
e.printStackTrace();
} catch (Exception e) {
e.printStackTrace();
}
// 返回
return instance;
}
/**
* 实施初始化之后
*
* @param beanName bean名称
* @param instance bean对象
*/
private Object doInitAfter(String beanName, Object instance) {
for (BeanPostProcessor beanPostProcessor : beanPostProcessorList) {
instance = beanPostProcessor.postProcessAfterInitialization(instance, beanName);
}
return instance;
}
/**
* 实施初始化方法
*
* @param instance bean对象
*/
private void doInit(Object instance) throws Exception {
if (instance instanceof InitializingBean) {
((InitializingBean) instance).afterPropertiesSet();
}
}
/**
* 实施初始化之前
*
* @param beanName bean名称
* @param instance bean对象
*/
private Object doInitBefore(String beanName, Object instance) {
for (BeanPostProcessor beanPostProcessor : beanPostProcessorList) {
instance = beanPostProcessor.postProcessBeforeInitialization(instance, beanName);
}
return instance;
}
/**
* 实现各种Aware的回调处理
*
* @param beanName bean名称
* @param instance bean对象
*/
private void doAwareCallBack(String beanName, Object instance) {
if (instance instanceof BeanNameAware) {
((BeanNameAware) instance).setBeanName(beanName);
}
}
/**
* 依赖注入
*
* @param clazz 需要被注入的对象的类信息
* @param instance 需要被注入的对象
*/
private void doDependecyInjection(Class clazz, Object instance) throws IllegalAccessException {
Field[] declaredFields = clazz.getDeclaredFields();
for (Field declaredField : declaredFields) {
if (declaredField.isAnnotationPresent(Autowired.class)) {
declaredField.setAccessible(true);
declaredField.set(instance, getBean(declaredField.getName()));
}
}
}
/**
* 进行AOP过程
*/
private void aop() {
}
/**
* 根据beanName获取Bean
*
* @param beanName bean名称
* @return bean对象
*/
public Object getBean(String beanName) {
if (StrUtil.isEmpty(beanName)) {
return null;
}
// 不存在beanDefinition,则肯定不是bean
if (!beanDefinitionMap.containsKey(beanName)) {
throw new NoSuchBeanDefinitionException("没有对应的bean定义");
}
BeanDefinition beanDefinition = beanDefinitionMap.get(beanName);
// 创建对象
if (beanDefinition.getScope().equals(BeanDefinition.SINGLETON)) {
// 获取单例
Object singletonBean = singletonPool.get(beanName);
if (singletonBean == null) {
singletonBean = createBean(beanName, beanDefinition);
singletonPool.put(beanName, singletonBean);
}
return singletonBean;
} else {
// 获取多例
Object prototype = createBean(beanName, beanDefinition);
return prototype;
}
}
/**
* 扫描方法
*
* @param clazz 配置类
*/
private void scan(Class clazz) {
// 获取扫描路径
String basePackage = doGetScanPackage(clazz);
if (StrUtil.isEmpty(basePackage)) {
return;
}
// 从电脑磁盘中加载文件,并且扫描
doLoadClassFromDiskAndScan(basePackage);
}
/**
* 从磁盘中加载class,并且扫描
*
* @param basePackage 扫描路径
*/
private void doLoadClassFromDiskAndScan(String basePackage) {
// 将Java包名转换为电脑路径名
basePackage = basePackage.replace(".", "/");
// 加载
ClassLoader classLoader = MyApplicationContext.class.getClassLoader();
URL resource = classLoader.getResource(basePackage);
File file = new File(resource.getFile());
if (file.isDirectory()) {
for (File listFile : file.listFiles()) {
// 获取全限定名
String fullyQualifiedClassName = transferToFullyQualifiedClassName(listFile);
try {
Class<?> beanClass = classLoader.loadClass(fullyQualifiedClassName);
if (!beanClass.isAnnotationPresent(Component.class)) {
continue;
}
// 记录Bean信息
try {
doRecordBeanInfo(beanClass);
} catch (NoSuchMethodException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
} catch (InstantiationException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
}
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
}
}
/**
* 过滤,并且记录Bean信息
*/
private void doRecordBeanInfo(Class<?> beanClass) throws NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException {
// 获取bean的名字
String beanName = doGetBeanName(beanClass);
// 记录BeanPostProcessor
if (BeanPostProcessor.class.isAssignableFrom(beanClass)) {
BeanPostProcessor instance = (BeanPostProcessor) beanClass.getConstructor().newInstance();
beanPostProcessorList.add(instance);
}
BeanDefinition beanDefinition = new BeanDefinition();
beanDefinition.setType(beanClass);
if (beanClass.isAnnotationPresent(Scope.class)) {
Scope scopeAnnotation = beanClass.getAnnotation(Scope.class);
String value = scopeAnnotation.value();
beanDefinition.setScope(value);
}
// 记录BeanDefinition
beanDefinitionMap.put(beanName, beanDefinition);
}
/**
* 获取当前Bean的名字
*
* @param aClass 要加载的类
* @return bean的名字
*/
private String doGetBeanName(Class<?> aClass) {
Component componentAnnotaion = aClass.getAnnotation(Component.class);
String beanName = componentAnnotaion.value();
if (StrUtil.isEmpty(beanName)) {
beanName = Introspector.decapitalize(aClass.getSimpleName());
}
return beanName;
}
/**
* 将电脑磁盘上的文件,转换为AppClassLoader能识别的类全限定名(包 + 类名)
*
* <p>
* 由于JVM的类加载器有三种,默认加载用户自定义class文件的,其实是ApplicationClassLoader
* 该类加载器所能识别的,其实是包名
* </p>
*
* @param listFile 电脑磁盘上的文件
*/
private String transferToFullyQualifiedClassName(File listFile) {
String absolutePath = listFile.getAbsolutePath();
absolutePath = absolutePath.substring(absolutePath.indexOf("org"), absolutePath.indexOf(".class"));
absolutePath = absolutePath.replace("\\", ".");
return absolutePath;
}
/**
* 获取包扫描路径
*
* @param clazz 配置类
* @return 扫描路径
*/
private String doGetScanPackage(Class clazz) {
if (clazz.isAnnotationPresent(ComponentScan.class)) {
ComponentScan componentScanAnnotation = (ComponentScan) clazz.getAnnotation(ComponentScan.class);
return componentScanAnnotation.value();
}
return null;
}
}