• 欢迎访问 winrains 的个人网站!
  • 本网站主要从互联网整理和收集了与Java、网络安全、Linux等技术相关的文章,供学习和研究使用。如有侵权,请留言告知,谢谢!

手写 Spring IOC 框架

Spring winrains 来源:周鑫磊 8个月前 (03-31) 85次浏览

1 SpringIOC原理

指的是控制反转,IOC容器负责实例化、定位、配置应用程序中的对象及建立这些对象间的依赖。交由Spring容器统一进行管理,从而实现松耦合

使用反射机制+XML技术

1.1 实现原理

springIOC

1.2 spring bean的一生

spring的生命周期

2 手写springIOC框架

2.1 手写SpringIOCXML版本

思路

  • 读取配置文件
  • 使用beanId查找对应的class地址
  • 使用反射机制初始化,对象
public class ClassPathXmlApplicationContext {
  // xml路径地址
  private String xmlPath;
  public ClassPathXmlApplicationContext(String xmlPath) {
    this.xmlPath = xmlPath;
  }
  public Object getBean(String beanId) throws Exception {
    // 1. 读取配置文件
    List<Element> elements = readerXml();
    if (elements == null) {
      throw new Exception("该配置文件没有子元素");
    }
    // 2. 使用beanId查找对应的class地址
    String beanClass = findXmlByIDClass(elements, beanId);
    if (StringUtils.isEmpty(beanClass)) {
      throw new Exception("未找到对应的class地址");
    }
    // 3. 使用反射机制初始化,对象
    Class<?> forName = Class.forName(beanClass);
    return forName.newInstance();
  }
  // 读取配置文件信息
  public List<Element> readerXml() throws DocumentException {
    SAXReader saxReader = new SAXReader();
    if (StringUtils.isEmpty(xmlPath)) {
      new Exception("xml路径为空...");
    }
    Document read = saxReader.read(getClassXmlInputStream(xmlPath));
    // 获取根节点信息
    Element rootElement = read.getRootElement();
    // 获取子节点
    List<Element> elements = rootElement.elements();
    if (elements == null || elements.isEmpty()) {
      return null;
    }
    return elements;
  }
  // 使用beanid查找该Class地址
  public String findXmlByIDClass(List<Element> elements, String beanId) throws Exception {
    for (Element element : elements) {
      // 读取节点上是否有value
      String beanIdValue = element.attributeValue("id");
      if (beanIdValue == null) {
        throw new Exception("使用该beanId为查找到元素");
      }
      if (!beanIdValue.equals(beanId)) {
        continue;
      }
      // 获取Class地址属性
      String classPath = element.attributeValue("class");
      if (!StringUtils.isEmpty(classPath)) {
        return classPath;
      }
    }
    return null;
  }
  // 读取xml配置文件
  public InputStream getClassXmlInputStream(String xmlPath) {
    InputStream resourceAsStream = getClass().getClassLoader().getResourceAsStream(xmlPath);
    return resourceAsStream;
  }
}

2.2 手写SpringIOC注解版本

思路

  • 使用java反射机制扫包,获取当前包下的所有类
  • 判断类上是否存在注入bean的注解
  • 使用java反射机制进行初始化
public class ClassPathXmlApplicationContext {
  // 扫包范围
  private String packageName;
  ConcurrentHashMap<String, Object> initBean = null;
  public ClassPathXmlApplicationContext(String packageName) {
    this.packageName = packageName;
  }
  // 使用beanID查找对象
  public Object getBean(String beanId) throws Exception {
    // 1.使用反射机制获取该包下所有的类已经存在bean的注解类
    List<Class> listClassesAnnotation = findClassExisService();
    if (listClassesAnnotation == null || listClassesAnnotation.isEmpty()) {
      throw new Exception("没有需要初始化的bean");
    }
    // 2.使用Java反射机制初始化对象
    initBean = initBean(listClassesAnnotation);
    if (initBean == null || initBean.isEmpty()) {
      throw new Exception("初始化bean为空!");
    }
    // 3.使用beanID查找查找对应bean对象
    Object object = initBean.get(beanId);
    // 4.使用反射读取类的属性,赋值信息
    attriAssign(object);
    return object;
  }
  // 使用反射读取类的属性,赋值信息
  public void attriAssign(Object object) throws IllegalArgumentException, IllegalAccessException {
    // 1.获取类的属性是否存在 获取bean注解
    Class<? extends Object> classInfo = object.getClass();
    Field[] declaredFields = classInfo.getDeclaredFields();
    for (Field field : declaredFields) {
      // 属性名称
      String name = field.getName();
      // 2.使用属性名称查找bean容器赋值
      Object bean = initBean.get(name);
      if (bean != null) {
        // 私有访问允许访问
        field.setAccessible(true);
        // 给属性赋值
        field.set(object, bean);
        continue;
      }
    }
  }
  // 使用反射机制获取该包下所有的类已经存在bean的注解类
  public List<Class> findClassExisService() throws Exception {
    // 1.使用反射机制获取该包下所有的类
    if (StringUtils.isEmpty(packageName)) {
      throw new Exception("扫包地址不能为空!");
    }
    // 2.使用反射技术获取当前包下所有的类
    List<Class<?>> classesByPackageName = ClassUtil.getClasses(packageName);
    // 3.存放类上有bean注入注解
    List<Class> exisClassesAnnotation = new ArrayList<Class>();
    // 4.判断该类上属否存在注解
    for (Class classInfo : classesByPackageName) {
      ExtService extService = (ExtService) classInfo.getDeclaredAnnotation(ExtService.class);
      if (extService != null) {
        exisClassesAnnotation.add(classInfo);
        continue;
      }
    }
    return exisClassesAnnotation;
  }
  // 初始化bean对象
  public ConcurrentHashMap<String, Object> initBean(List<Class> listClassesAnnotation)
      throws InstantiationException, IllegalAccessException {
    ConcurrentHashMap concurrentHashMap = new ConcurrentHashMap<String, Object>();
    for (Class classInfo : listClassesAnnotation) {
      // 初始化对象
      Object newInstance = classInfo.newInstance();
      // 获取父类名称
      String beanId = toLowerCaseFirstOne(classInfo.getSimpleName());
      concurrentHashMap.put(beanId, newInstance);
    }
    return concurrentHashMap;
  }
  // 首字母转小写
  public static String toLowerCaseFirstOne(String s) {
    if (Character.isLowerCase(s.charAt(0)))
      return s;
    else
      return (new StringBuilder()).append(Character.toLowerCase(s.charAt(0))).append(s.substring(1)).toString();
  }
}

2.3 依赖注入原理实现

  • 使用反射机制,获取当前类的所有属性
  • 判断属性是否存在注解
  • 默认使用属性名称,查找bean容器对象

3 Spring循环依赖及解决方式

3.1 Spring循环依赖

循环依赖其实就是循环引用,也就是两个或者两个以上的bean互相持有对方,最终形成闭环。比如A依赖于B,B依赖于C,C又依赖于A。如下图

循环依赖

注意:这里不是函数的循环调用,是对象的相互依赖关系。循环调用其实就是一个死循环,除非有终结条件

3.2 Spring中循环依赖场景

  • 构造器的循环依赖
  • field属性的循环依赖

注意构造器的循环依赖问题无法解决,只能拋BeanCurrentlyInCreationException异常,在解决属性循环依赖时,spring采用的是提前暴露对象的方法

3.3 Spring怎么解决循环依赖

解决循环依赖

步骤

  • createBeanInstance:实例化,其实也就是调用对象的构造方法实例化对象
  • populateBean:填充属性,这一步主要是多bean的依赖属性进行填充
  • initializeBean:调用spring xml中的init 方法。

从上面单例bean的初始化可以知道:循环依赖主要发生在第一、二步,也就是构造器循环依赖和field循环依赖。那么我们要解决循环引用也应该从初始化过程着手,对于单例来说,在Spring容器整个生命周期内,有且只有一个对象,所以很容易想到这个对象应该存在Cache中,Spring为了解决单例的循环依赖问题,使用了三级缓存

singletonFactories : 单例对象工厂的cache
earlySingletonObjects :提前暴光的单例对象的Cache
singletonObjects:单例对象的cache

作者:周鑫磊

来源:https://www.sparksys.top/archives/18


版权声明:文末如注明作者和来源,则表示本文系转载,版权为原作者所有 | 本文如有侵权,请及时联系,承诺在收到消息后第一时间删除 | 如转载本文,请注明原文链接。
喜欢 (2)