阶段3: (仍需打磨,静态处有小瑕疵)
// 1.编写自己的Spring容器,实现扫描包,得到bean的class对象.
// 2.扫描将 bean 信息封装到 BeanDefinition对象,并放入到Map.
3.初始化单例池并完成getBean() createBean()方法
思路:
初始化单例池,也就是如果Bean是单例的就实例化,并放入到单例池Map.
Spring容器getBean(name)实现机制:
调用getBean()是先到beanDefinitionMap里根据bean来查信息,查到beanDefinition对象.里面记录了对象是单例的还是多实例的.在这里插入代码片
1.如果这个bean不存在就抛出异常.
2.如果这个bean是singleton,从单例池 单例BeanMap集合中获取即可.
3.如果这个bean是prototype,到BeanDefinition Map中,得到Bean的Clazz对象,使用反射创建bean对象,并返回.
初始化单例池
//通过beanDefinitionMap,初始化singletonObjects单例池
//封装成方法
//遍历所有的beanDefinition,用到集合和枚举的知识
Enumeration<String> keys = beanDefinitionMap.keys();//把所有bean的名字拿到
while (keys.hasMoreElements()){
//得到beanName
String beanName = keys.nextElement();
//通过beanName得到对应的beanDefinition对象
BeanDefinition beanDefinition = beanDefinitionMap.get(beanName);
//判断该bean是singleton还是prototype
if("singleton".equalsIgnoreCase(beanDefinition.getScope())){
//将该bean实例放入到singletonObjects集合
Object bean = createBean(beanDefinition);
singletonObjects.put(beanName,bean);
}
}
System.out.println("singletonObjects 单例池=" + singletonObjects);
System.out.println("beanDefinitionMap=" + beanDefinitionMap);
package com.elf.spring.ioc;
import com.elf.spring.annotation.*;
import org.apache.commons.lang.StringUtils;
import java.io.File;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.net.URL;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.List;
import java.util.concurrent.ConcurrentHashMap;
/**
* @author 45~
* @version 1.0
*/
public class ElfSpringApplicationContext {
//第一步,扫描包,得到bean的class对象,排除包下不是bean的,因此还没有放到容器中
//因为现在写的spring容器比原先的基于注解的,要更加完善,所以不会直接把它放在ConcurrentHashMap
private Class configClass;
//定义属性BeanDefinitionMap -> 存放BeanDefinition对象
private static ConcurrentHashMap<String, BeanDefinition> beanDefinitionMap =
new ConcurrentHashMap<>();
//定义属性SingletonObjects -> 存放单例对象 (跟原生容器的名字保持一致)
//因为将来存放单例池的时候肯定要指定单例对象是对应哪个Bean的,所以k用String来充当
//存放单例对象的类型是不确定的,可能是Dog,Cat,或者其他的对象,所以用Object
private static ConcurrentHashMap<String, Object> singletonObjects =
new ConcurrentHashMap<>();
//构造器
public ElfSpringApplicationContext(Class configClass) {
//完成扫描指定包
beanDefinitionsByScan(configClass);
//通过beanDefinitionMap,初始化singletonObjects单例池
//封装成方法
//遍历所有的beanDefinition,用到集合和枚举的知识
Enumeration<String> keys = beanDefinitionMap.keys();//把所有bean的名字拿到
while (keys.hasMoreElements()) {
//得到beanName
String beanName = keys.nextElement();
//通过beanName得到对应的beanDefinition对象
BeanDefinition beanDefinition = beanDefinitionMap.get(beanName);
//判断该bean是singleton还是prototype
if ("singleton".equalsIgnoreCase(beanDefinition.getScope())) {
//将该bean实例放入到singletonObjects集合
Object bean = createBean(beanDefinition);
singletonObjects.put(beanName, bean);
}
}
System.out.println("singletonObjects 单例池=" + singletonObjects);
System.out.println("beanDefinitionMap=" + beanDefinitionMap);
}//构造器结束
//该方法完成对指定包的扫描,并将Bean信息封装到BeanDefinition对象,在放入到Map
public void beanDefinitionsByScan(Class configClass) {
this.configClass = configClass;
/**获取要扫描的包:
1.先得到ElfSpringConfig配置的 @ComponentScan(value= "com.elf.spring.component")
2.通过 @ComponentScan的value => 即要扫描的包 **/
ComponentScan componentScan =
(ComponentScan) this.configClass.getDeclaredAnnotation(ComponentScan.class);
String path = componentScan.value();
System.out.println("要扫描的包path=" + path);
/**
* 得到要扫描包下的所有资源(类.class)
* 1.得到类的加载器 -> APP类加载器是可以拿到 target目录下的classes所有文件的
* 2.通过类的加载器获取到要扫描的包的资源url => 类似一个路径
* 3.将要加载的资源(.class)路径下的文件进行遍历 => io
*/
ClassLoader classLoader = ElfSpringApplicationContext.class.getClassLoader();
path = path.replace(".", "/"); // 把.替换成 /
URL resource = classLoader.getResource(path);
System.out.println("resource=" + resource);
File file = new File(resource.getFile());
if (file.isDirectory()) {
File[] files = file.listFiles();
for (File f : files) { //把所有的文件都取出来
System.out.println("============================");
System.out.println("f.getAbsolutePath()=" + f.getAbsolutePath());
String fileAbsolutePath = f.getAbsolutePath();//到target的classes目录下了
//这里只处理.class文件
if (fileAbsolutePath.endsWith(".class")) {
//1.获取类名
String className = fileAbsolutePath.substring(fileAbsolutePath.lastIndexOf("\\") + 1,
fileAbsolutePath.indexOf(".class"));
//2.获取类的完整路径(全类名)
String classFullName = path.replace("/", ".") + "." + className;
System.out.println("classFullName=" + classFullName);
//3.判断该类是否需要注入,就看是不是有注解@Component @Service @Contoller @Re....
try {
Class<?> cla = classLoader.loadClass(classFullName);
if (cla.isAnnotationPresent(Component.class) ||
cla.isAnnotationPresent(Controller.class) ||
cla.isAnnotationPresent(Service.class) ||
cla.isAnnotationPresent(Repository.class)) {
//演示机制
//如果该类使用了@Component注解,说明是一个Spring bean
System.out.println("这是一个Spring bean=" + cla + " 类名=" + className);
//先得到beanName(有可能通过经典4注解,例如Component注解的value值来指定)
//1.得到类上的Component注解,此时的clazz已经是当前bean的class对象,通过类加载器得到的 反射知识
Component componentAnnotation = cla.getDeclaredAnnotation(Component.class);
//2.得到配置的value
String beanName = componentAnnotation.value();
if ("".equals(beanName)) {//如果没有写value,空串
//将该类的类名首字母小写作为beanName
//StringUtils其实是在springframework的框架下面的类,而这里本身我就是要自己写所以不用
beanName = StringUtils.uncapitalize(className);
}
//3.将Bean的信息封装到BeanDefinition对象中,然后将其放入到BeanDefinitionMap集合中
BeanDefinition beanDefinition = new BeanDefinition();
//!!!多看看这里多理解
beanDefinition.setClazz(cla);//由类加载器通过反射得到对象,Class<?> cla = classLoader.loadClass(classFullName);
//4.获取Scope值
if (cla.isAnnotationPresent(Scope.class)) {
//如果配置了Scope,就获取它配置的值
Scope scopeAnnotation = cla.getDeclaredAnnotation(Scope.class);
beanDefinition.setScope(scopeAnnotation.value());
} else {
//如果没有配置Scope,就以默认的值singleton
beanDefinition.setScope("singleton");
}
//将beanDefinitionMap对象放入Map
beanDefinitionMap.put(beanName, beanDefinition);
} else {
//如果该类没有使用了@Component注解,说明是一个Spring bean
System.out.println("这不是一个Spring bean" + cla + " 类名=" + className);
}
} catch (Exception e) {
e.printStackTrace();
}
}
System.out.println("~~~~~~~~~~~~~~~~~~~~~~~~~~~~");
}//遍历文件结束
}
}
//完成createBean(BeanDefinition beanDefinition)方法
private static Object createBean(BeanDefinition beanDefinition) {
//得到Bean的Clazz对象
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;
}
//编写getBean(String name)方法,返回容器中的对象.
// 既然是getBean()那么返回类型是Object,因为不管是单例池还是创建的对象也好,类型是不确定的
public static Object getBean(String name) {
//加一个判断,严谨. 传入的beanName是否在beanDefinitonMap中存在..
if(beanDefinitionMap.containsKey(name)) {//如果存在
BeanDefinition beanDefinition = beanDefinitionMap.get(name);
//得到beanDefinition的scope,分别进行处理
if ("singleton".equalsIgnoreCase(beanDefinition.getScope())) {
//说明是单例配置,就直接从单例池获取
return singletonObjects.get(name);
} else {//如果不是单例的,我们就调用createBean, 反射一个对象
return createBean(beanDefinition);
}
} else { //如果不存在,就抛出一个空指针异常,也可自定义
throw new NullPointerException("没有该bean");
}
}
}
MainApp
package com.elf.spring;
import com.elf.spring.component.MonsterDao;
import com.elf.spring.component.MonsterService;
import com.elf.spring.ioc.ElfSpringApplicationContext;
import com.elf.spring.ioc.ElfSpringConfig;
/**
* @author 45~
* @version 1.0
*/
public class AppMain {
public static void main(String[] args) {
//把容器创建起来,在创建的时候传入了配置类的class类型/class对象
//传进去后会根据自己写的容器机制 进行解析
ElfSpringApplicationContext elfSpringApplicationContext =
new ElfSpringApplicationContext(ElfSpringConfig.class);
MonsterService monsterService =
(MonsterService)ElfSpringApplicationContext.getBean("monsterService");
MonsterService monsterService2 =
(MonsterService)ElfSpringApplicationContext.getBean("monsterService");
System.out.println("monsterService" + monsterService);
System.out.println("monsterService2" + monsterService2);
MonsterDao monsterDao =
(MonsterDao)ElfSpringApplicationContext.getBean("monsterDao");
System.out.println("monsterDao" + monsterDao);
MonsterDao monsterDao2 =
(MonsterDao)ElfSpringApplicationContext.getBean("monsterDao");
System.out.println("monsterDao2" + monsterDao2);
System.out.println("ok");
System.out.println("ok");
}
}