从依赖倒置原则看Spring(二)

简介: 从依赖倒置原则看Spring

扫描指定包下面的类

定义存放类信息的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),所以我们把classMapbeanMap都定义到这里

而依赖注入的过程,其实就是在处理Autowired注解,那它就叫做: AutowiredAnnotationBeanProcessor

我们还在知道,在Spring中,不仅仅只有这种使用方式,还有xml,mvc,SpringBoot的方式,所以我们将ApplicationContext进行抽象,只实现主干流程,原来的注解方式交由AnnotationApplicationContext实现。

借助依赖倒置原则:程序应当依赖于抽象

在未来,类信息不仅仅可以从类信息来,也可以从配置文件而来,所以我们将ConfigurationCalssProcessor抽象

而依赖注入的方式不一定非得是用Autowried注解标识,也可以是别的注解标识,比如Resource,所以我们将AutowiredAnnotationBeanProcessor抽象

Bean的类型也可以有很多,可以是单例的,可以使多例的,也可以是个工厂Bean,所以我们将BeanFactory抽象

现在,我们借助两大设计原则对我们的方案进行了优化,相比于之前可谓是”脱胎换骨“。

目录
相关文章
|
设计模式 Java Spring
【设计模式】依赖倒置原则与工厂方法模式与spring
【设计模式】依赖倒置原则与工厂方法模式与spring
117 0
|
缓存 Java Spring
从依赖倒置原则看Spring(三)
从依赖倒置原则看Spring
170 0
|
JSON Java 数据格式
从依赖倒置原则看Spring(一)
从依赖倒置原则看Spring
49275 0
|
2月前
|
SQL 监控 druid
springboot-druid数据源的配置方式及配置后台监控-自定义和导入stater(推荐-简单方便使用)两种方式配置druid数据源
这篇文章介绍了如何在Spring Boot项目中配置和监控Druid数据源,包括自定义配置和使用Spring Boot Starter两种方法。
|
1月前
|
人工智能 自然语言处理 前端开发
SpringBoot + 通义千问 + 自定义React组件:支持EventStream数据解析的技术实践
【10月更文挑战第7天】在现代Web开发中,集成多种技术栈以实现复杂的功能需求已成为常态。本文将详细介绍如何使用SpringBoot作为后端框架,结合阿里巴巴的通义千问(一个强大的自然语言处理服务),并通过自定义React组件来支持服务器发送事件(SSE, Server-Sent Events)的EventStream数据解析。这一组合不仅能够实现高效的实时通信,还能利用AI技术提升用户体验。
166 2
|
3月前
|
缓存 Java Maven
Java本地高性能缓存实践问题之SpringBoot中引入Caffeine作为缓存库的问题如何解决
Java本地高性能缓存实践问题之SpringBoot中引入Caffeine作为缓存库的问题如何解决
|
12天前
|
缓存 IDE Java
SpringBoot入门(7)- 配置热部署devtools工具
SpringBoot入门(7)- 配置热部署devtools工具
25 2
 SpringBoot入门(7)- 配置热部署devtools工具
|
8天前
|
存储 运维 安全
Spring运维之boot项目多环境(yaml 多文件 proerties)及分组管理与开发控制
通过以上措施,可以保证Spring Boot项目的配置管理在专业水准上,并且易于维护和管理,符合搜索引擎收录标准。
20 2
|
1月前
|
SQL JSON Java
mybatis使用三:springboot整合mybatis,使用PageHelper 进行分页操作,并整合swagger2。使用正规的开发模式:定义统一的数据返回格式和请求模块
这篇文章介绍了如何在Spring Boot项目中整合MyBatis和PageHelper进行分页操作,并且集成Swagger2来生成API文档,同时定义了统一的数据返回格式和请求模块。
54 1
mybatis使用三:springboot整合mybatis,使用PageHelper 进行分页操作,并整合swagger2。使用正规的开发模式:定义统一的数据返回格式和请求模块
|
1月前
|
XML Java 应用服务中间件
【Spring】运行Spring Boot项目,请求响应流程分析以及404和500报错
【Spring】运行Spring Boot项目,请求响应流程分析以及404和500报错
176 2