扫描指定包下面的类
定义存放类信息的map
private final Map<String, Class<?>> classMap = new ConcurrentHashMap<>(16);
具体流程,下面同样附上代码实现:
代码实现,可以与流程图结合观看:
扫描类信息
private void scan(Class<?> configClass) { // 解析配置类,获取到扫描包路径 String basePackages = this.getBasePackages(configClass); // 使用扫描包路径进行文件遍历操作 this.doScan(basePackages); }
private String getBasePackages(Class<?> configClass) { // 从ComponentScan注解中获取扫描包路径 ComponentScan componentScan = configClass.getAnnotation(ComponentScan.class); return componentScan.basePackages(); }
private void doScan(String basePackages) { // 获取资源信息 URI resource = this.getResource(basePackages); File dir = new File(resource.getPath()); for (File file : dir.listFiles()) { if (file.isDirectory()) { // 递归扫描 doScan(basePackages + "." + file.getName()); } else { // com.my.spring.example + . + Boy.class -> com.my.spring.example.Boy String className = basePackages + "." + file.getName().replace(".class", ""); // 将class存放到classMap中 this.registerClass(className); } } }
private void registerClass(String className){ try { // 加载类信息 Class<?> clazz = classLoader.loadClass(className); // 判断是否标识Component注解 if(clazz.isAnnotationPresent(Component.class)){ // 生成beanName com.my.spring.example.Boy -> boy String beanName = this.generateBeanName(clazz); // car: com.my.spring.example.Car classMap.put(beanName, clazz); } } catch (ClassNotFoundException ignore) {} }
实例化
现在已经把所有适合的类都解析好了,接下来就是实例化的过程了
定义存放Bean的Map
private final Map<String, Object> beanMap = new ConcurrentHashMap<>(16);
具体流程,下面同样给出代码实现:
代码实现,可以与流程图结合观看:
遍历classMap
进行实例化Bean
public void instantiateBean() { for (String beanName : classMap.keySet()) { getBean(beanName); } }
public Object getBean(String beanName){ // 先从缓存中获取 Object bean = beanMap.get(beanName); if(bean != null){ return bean; } return this.createBean(beanName); }
private Object createBean(String beanName){ Class<?> clazz = classMap.get(beanName); try { // 创建bean Object bean = this.doCreateBean(clazz); // 将bean存到容器中 beanMap.put(beanName, bean); return bean; } catch (IllegalAccessException e) { throw new RuntimeException(e); } }
private Object doCreateBean(Class<?> clazz) throws IllegalAccessException { // 实例化bean Object bean = this.newInstance(clazz); // 填充字段,将字段设值 this.populateBean(bean, clazz); return bean; }
private Object newInstance(Class<?> clazz){ try { // 这里只支持默认构造器 return clazz.getDeclaredConstructor().newInstance(); } catch (InstantiationException | IllegalAccessException | InvocationTargetException | NoSuchMethodException e) { throw new RuntimeException(e); } }
private void populateBean(Object bean, Class<?> clazz) throws IllegalAccessException { // 解析class信息,判断类中是否有需要进行依赖注入的字段 final Field[] fields = clazz.getDeclaredFields(); for (Field field : fields) { Autowired autowired = field.getAnnotation(Autowired.class); if(autowired != null){ // 获取bean Object value = this.resolveBean(field.getType()); field.setAccessible(true); field.set(bean, value); } } }
private Object resolveBean(Class<?> clazz){ // 先判断clazz是否为一个接口,是则判断classMap中是否存在子类 if(clazz.isInterface()){ // 暂时只支持classMap只有一个子类的情况 for (Map.Entry<String, Class<?>> entry : classMap.entrySet()) { if (clazz.isAssignableFrom(entry.getValue())) { return getBean(entry.getValue()); } } throw new RuntimeException("找不到可以进行依赖注入的bean"); }else { return getBean(clazz); } }
public Object getBean(Class<?> clazz){ // 生成bean的名称 String beanName = this.generateBeanName(clazz); // 此处对应最开始的getBean方法 return this.getBean(beanName); }
组合
两个核心方法已经写好了,接下把它们组合起来,我把它们实现在自定义的ApplicationContext
类中,构造方法如下:
public ApplicationContext(Class<?> configClass) { // 1.扫描配置信息中指定包下的类 this.scan(configClass); // 2.实例化扫描到的类 this.instantiateBean(); }
UML类图:
测试
代码结构与案例相同,这里展示一下我们自己的Spring是否可以正常运行
运行正常
源码会在文末给出
回顾
现在,我们已经根据设想的方案进行了实现,运行的情况也达到了预期的效果。但如果仔细研究一下,再结合我们平常使用Spring的场景,就会发现这一份代码的不少问题:
1、无法支持构造器注入,当然也没有支持方法注入,这是属于功能上的缺失。
2、加载类信息的问题,加载类时我们使用的是classLoader.loadClass
的方式,虽然这避免了类的初始化(可千万别用Class.forName
的方式),但还是不可避免的把类元信息加载到了元空间中,当我们扫描包下有不需要的类时,这就浪费了我们的内存。
3、无法解决bean之间的循环依赖,比如有一个A对象依赖了B对象, B对象又依赖了A对象,这个时候我们再来看看代码逻辑,就会发现此时会陷入死循环。
4、扩展性很差,我们把所有的功能都写在一个类里,当想要完善功能(比如以上3个问题)时,就需要频繁修改这个类,这个类也会变得越来越臃肿,别说迭代新功能,维护都会令人头疼。
优化方案
对于前三个问题都类似于功能上的问题,功能嘛,改一改就好了。
我们需要着重关注的是第四个问题,一款框架想要变得优秀,那么它的迭代能力一定要好,这样功能才能变得丰富,而迭代能力的影响因素有很多,其中之一就是它的扩展性。
那么应该如何提高我们的方案的扩展性呢,六大设计原则给了我们很好的指导作用。
在方案中,ApplicationContext
做了很多事情, 主要可以分为两大块
1、扫描指定包下的类
2、实例化Bean
借助单一职责原则的思想:一个类只做一种事,一个方法只做一件事。
我们把扫描指定包下的类这件事单独使用一个处理器进行处理,因为扫描配置是从配置类而来,那我们就叫他配置类处理器:ConfigurationCalssProcessor
实例化Bean这件事情也同样如此,实例化Bean又分为了两件事:实例化和依赖注入
实例化Bean就是相当于一个生产Bean的过程,我们就把这件事使用一个工厂类进行处理,它就叫做:BeanFactory,既然是在生产Bean,那就需要原料(Class),所以我们把classMap
和beanMap
都定义到这里
而依赖注入的过程,其实就是在处理Autowired
注解,那它就叫做: AutowiredAnnotationBeanProcessor
我们还在知道,在Spring中,不仅仅只有这种使用方式,还有xml,mvc,SpringBoot的方式,所以我们将ApplicationContext
进行抽象,只实现主干流程,原来的注解方式交由AnnotationApplicationContext
实现。
借助依赖倒置原则:程序应当依赖于抽象
在未来,类信息不仅仅可以从类信息来,也可以从配置文件而来,所以我们将ConfigurationCalssProcessor抽象
而依赖注入的方式不一定非得是用Autowried
注解标识,也可以是别的注解标识,比如Resource
,所以我们将AutowiredAnnotationBeanProcessor抽象
Bean的类型也可以有很多,可以是单例的,可以使多例的,也可以是个工厂Bean,所以我们将BeanFactory抽象
现在,我们借助两大设计原则对我们的方案进行了优化,相比于之前可谓是”脱胎换骨“。