这段时间有想法去学习手写Spring源码,在此记录一下自己手写的Spring的迭代版本趴。
1. Spring的启动扫描到容器
@Component
@ComponentScan
@Scope // 判断Bean对象是不是单例,值为singleton是单例,
上述注解需要手写出来来实现简单实现Spring中的功能
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface Component {
/**
* 容器中类的名称
* @return
*/
String value() default "";
}
/**
* 自定义的注解,默认扫描包的路径
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface ComponentScan {
String value() ; // 扫描的路径
}
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface Scope {
String value();
}
上述三个注解里面没有什么价值性的东西,因为注解在我理解就是一个标识嘛。
接下来就要实现ApplicationContext,这玩意和我们使用Spring的ApplicationContext一样,主要的核心都在里面
直接上代码。
/**
* 容器类
*/
public class ZJHApplicationContext {
/**
* 配置类
*/
private Class configClass;
/**
* 单例池,里面存储的是单例对象
*/
private ConcurrentHashMap<String,Object> singletonObjects = new ConcurrentHashMap<>();
/**
* 存放BeanDefinition对象
*/
private ConcurrentHashMap<String,BeanDefinition> beanDefinitionMap = new ConcurrentHashMap<>();
/**
* 构造器
*
* @param configClass
* @throws ClassNotFoundException
*/
public ZJHApplicationContext(Class configClass) {
this.configClass = configClass;
// 初始化Bean容器,其实Scan方法并没有将单例Bean对象加入到单例池中,Scan方法的主要作用就是将BeanDefinition对象加入到BeanDefinitionMap中,为了下面的将单例Bean加入到单例池中做准备
scan(configClass);
// 初始化BeanDefinition容器后,通Beandefinition容器里的元素来一一将扫描路径下的对象的Beandefinition对象取出
for (Map.Entry<String, BeanDefinition> beanDefinitionEntry : beanDefinitionMap.entrySet()) {
// 获取Beandefinition对象的key,也就是Bean的name
String beanName = beanDefinitionEntry.getKey();
// 获取BeandefinitionEntry的value,也就是BeanDefinition对象
BeanDefinition beanDefinition = beanDefinitionEntry.getValue();
// 接下来就是获取BeanDefinition对象的scope属性来判断对象是单例还是多例
if ("singleton".equals(beanDefinition.getScope())){
// 说明是单例Bean,将单例Bean加入到单例池中
Object bean = createBean(beanDefinition);
singletonObjects.put(beanName,bean);
}
// 如果对象是多例,则不需要加入到单例池中
}
}
/**
* 创建Bean对象的方法,
* @param beanDefinition
* @return
*/
public Object createBean(BeanDefinition beanDefinition){
// 通过BeanDefinition的class属性来通过反射创建出对象
Class clazz = beanDefinition.getClazz();
try {
Object instance = clazz.getDeclaredConstructor().newInstance();
return instance;
} catch (InstantiationException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
} catch (NoSuchMethodException e) {
e.printStackTrace();
}
return null;
}
/**
* 初始化Bean容器的方法
* @param configClass
*/
private void scan(Class configClass){
// 拿到配置类后会去解析这个配置类
// 1. 解析@ComponentScan注解的信息 -> 获取扫描路径 -> 扫描
// 查看配置类里有没有ComponentScan注解
ComponentScan componentScanAnnotation = (ComponentScan) configClass.getDeclaredAnnotation(ComponentScan.class);
// 获取扫描路径
String path = componentScanAnnotation.value();
System.out.println(path); // test.service
// 扫描路径的类是否加入了Component注解
// 1. 根据扫描路径来得到路径下的所有类,通过类加载器来获取,这里需要应用类加载器,因为Bootstrap,ext加载器不加载我们自定义的类,通过自己的类来获取对应的类加载器
ClassLoader classLoader = ZJHApplicationContext.class.getClassLoader();
path = path.replace(".", "//");
// 这个resource是一个目录
URL resource = classLoader.getResource(path);
// 通过File类来获取目录下的文件
File file = new File(resource.getFile());
// 如果文件是一个文件夹,则获取文件夹下的所有的文件
if (file.isDirectory()) {
File[] files = file.listFiles();
for (File f : files) {
System.out.println(f);
// 将D:\Tools\Spring_ZJH\target\classes\test\service\UserService.class转为 test.service.UserService
String fileName = f.getAbsolutePath();
// 判断文件是不是class文件,如果是的话在开始截取
if (fileName.endsWith(".class")) {
// 从test开始截取,到.class结束
String className = fileName.substring(fileName.indexOf("test"), fileName.indexOf(".class"));
// 将\转换为.
className = className.replace("\\", ".");
// 得到了类的路径
System.out.println(className);
try {
// 通过类加载器来加载类
Class<?> clazz = classLoader.loadClass(className);
// 判断类上是否有Component注解
if (clazz.isAnnotationPresent(Component.class)) {
// 到这一步就说明扫描类中已经有了Component注解,即它是一个Bean对象
// 到了这一步得判断这个Bean是单例Bean还是Prototype的Bean(即多例Bean)
// 如果是单例Bean则将Bean对象加入到singletonObjects单例池中
// 在加入容器时,我们需要判断Bean是单例还是多例,在getBean方法时,我们也需要判断,但其实没有必要判断两次,Spring中有BeanDefinition的概念,即Bean的定义
// 每解析一个类都会生成一个BeanDefinition对象
// 获取Bean的名称
Component componentAnnotation = clazz.getDeclaredAnnotation(Component.class);
String beanName = componentAnnotation.value();
// 生成BeanDefinitation对象,注意这个不是Bean对象,而是Bean的定义
BeanDefinition beanDefinition = new BeanDefinition();
beanDefinition.setClazz(clazz);
if (clazz.isAnnotationPresent(Scope.class)){
// 设置BeanDefinitation对象的scope属性
Scope scopeAnnotation = clazz.getDeclaredAnnotation(Scope.class);
String value = scopeAnnotation.value();
beanDefinition.setScope(value);
}else {
// Bean中没有scope注解,则将BeanDefinition的scope属性设置为singleton,即单例
beanDefinition.setScope("singleton");
}
// 将BeanDefinition对象加入到BeanDefinitionMap中
beanDefinitionMap.put(beanName,beanDefinition);
}
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
}
}
}
/**
* getBean方法
*
* @param beanName
* @return
*/
public Object getBean(String beanName) {
if (beanDefinitionMap.containsKey(beanName)){
BeanDefinition beanDefinition = beanDefinitionMap.get(beanName);
if ("singleton".equals(beanDefinition.getScope())){
// 说明这是单例Bean,从单例池子中获取Bean对象返回
Object o = singletonObjects.get(beanName);
return o;
}else {
// 说明这是多例Bean 手动创建Bean对象并返回
return createBean