前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >手撸Java Spring

手撸Java Spring

作者头像
SerMs
发布2023-05-18 09:39:46
1430
发布2023-05-18 09:39:46
举报
文章被收录于专栏:SerMsBlogSerMsBlog

首先,我们需要明白什么是BeanFactory和Ioc容器。在Java中,BeanFactory是一种用于创建和管理对象(也称为bean)的机制,而Ioc(Inversion of Control,控制反转)容器则是负责实现BeanFactory的框架。简单来说,BeanFactory就像是一个工厂,根据我们的需求来创建和提供对象。

简介

Spring IOC容器的概念

Spring IOC(控制反转)容器是Spring框架的核心组件之一。它负责管理应用程序中的对象,实现了对象的创建、组装和管理等功能。IOC容器通过反转控制,将对象的创建和依赖注入的责任从应用程序代码转移到容器中,提供了更高的灵活性和可测试性。

DI(依赖注入)

依赖注入(DI)是IOC容器的重要特性之一。通过DI,对象的依赖关系由容器在运行时动态地注入,而不是由对象自己负责创建或查找依赖的实例。这种解耦的方式使得对象之间的协作更加灵活、可扩展和易于维护。

源码分析

获取Bean

我们先来看一下最常用的getBean()方法,在实现上,该方法主要分为三个步骤:

  • 获取BeanDefinition
  • 创建Bean实例
  • 初始化Bean

获取BeanDefinition

前两个步骤非常简单,我们直接来看第一步的实现。获取BeanDefinition主要调用的是DefaultListableBeanFactory类中的getBeanDefinition()方法,该方法返回的就是Bean的定义信息。

代码语言:javascript
复制
@Override
public BeanDefinition getBeanDefinition(String beanName) throws BeansException {
    // 从缓存中获取BeanDefinition
    BeanDefinition bd = this.beanDefinitionMap.get(beanName);
    if (bd == null) {
        throw new NoSuchBeanDefinitionException(beanName);
    }
    // 返回BeanDefinition
    return bd;
}

上述方法中,beanDefinitionMap是一个ConcurrentHashMap,用来缓存BeanDefinition对象,key为Bean的名称,value为BeanDefinition对象,这个容器是Spring IOC管理Bean的核心,后面的初始化Bean和创建Bean都是基于这个容器进行的,我们可以看到这个方法先从容器中获取BeanDefinition对象,如果获取到就直接返回,如果获取不到就抛出一个NoSuchBeanDefinitionException异常。

创建Bean实例

下面是Bean实例化的主要过程(主要包含了Bean的创建,包括构造函数的调用和依赖注入等逻辑):

代码语言:javascript
复制
protected Object createBean(String beanName, RootBeanDefinition mbd, Object[] args) {
    // 第一步,根据BeanDefinition获取Bean的class对象
    Class<?> beanClass = resolveBeanClass(mbd, beanName);

    // 第二步,校验Bean的依赖关系以及循环依赖,这里省略实现细节

    // 第三步,执行BeanDefinition中Scheduler的回调函数,这里省略实现细节

    try {
        Object beanInstance;

        // 第四步,判断是否启用了工厂方法构造Bean
        if (mbd.getFactoryMethodName() != null) {
            return instantiateUsingFactoryMethod(beanName, mbd, args);
        }

        // 第五步,执行构造函数
        Constructor<?>[] ctors = determineConstructorsFromBeanPostProcessors(beanClass, beanName);
        if (ctors != null || mbd.getResolvedAutowireMode() == RootBeanDefinition.AUTOWIRE_CONSTRUCTOR ||
                mbd.hasConstructorArgumentValues() || !ObjectUtils.isEmpty(args)) {
            return instantiateBean(beanName, mbd, args, ctors);
        }

        // 第六步,根据类型查找BeanFactory中是否注册了Bean的实例
        beanInstance = getSingleton(beanName);
        if (beanInstance != null) {
            return beanInstance;
        }

        // 第七步,通过BeanWrapper来修改Bean的属性
        BeanWrapper bw = new BeanWrapperImpl();
        bw.setConversionService(getConversionService());
        initBeanWrapper(bw);

        // 第八步,为Bean属性设置值
        applyPropertyValues(beanName, mbd, bw, mbd.getPropertyValues());

        // 第九步,在设置完Bean属性之后,校验Bean的合法性等信息,这里省略实现细节

        // 第十步,执行Bean的init方法,将Bean初始化完成
        beanInstance = initializeBean(beanName, beanInstance, mbd);
        if (mbd.isSingleton()) {
            // 添加原生的单例Bean
            addSingleton(beanName, beanInstance);
        }
        return beanInstance;
    }
    catch (Throwable e) {
        throw new BeanCreationException(beanName, "Instantiation of bean failed", e);
    }
}

上述代码就是Bean的主要构造过程,其中主要包括了工厂方法构造、构造函数调用、属性注入、初始化等逻辑。

初始化Bean

最后一步就是初始化Bean了,这个过程主要包括以下三个方法:

firstly, applyBeanPostProcessorsBeforeInitialization(ob, beanName);

secondly, invokeInitMethods(beanName, wrappedBean, mbd);

thirdly, applyBeanPostProcessorsAfterInitialization(wrappedBean, beanName);

这个方法可以在BeanDefinitionReaderUtils类中找到。

Bean注入

pring在完成获取Bean和创建Bean的工作之后,需要将需要的Bean注入到需要该Bean的地方。在Spring中,Bean注入主要分为以下几种方式:

  • 构造函数注入
  • setter注入
  • 通过注解实现的注入

对于构造函数注入,Spring主要通过查找构造函数的参数及其类型,然后根据类型及其名称去容器中找到对应的Bean,最终完成注入。

对于setter注入,Spring则是直接对Bean的属性进行注入,setter属性注入其实是Spring IOC容器的一个特殊的语法糖,开发者只需要定义好setter方法,在容器中就可以完成 Bean的注入,非常方便。

对于通过注解实现的注入,所谓的注解指的是@Resource、@Autowired注解。这种方式只需要在需要注入的属性上添加对应的@Autowired或@Resource注解,Spring IOC就会自动完成注入。

在源码中,Spring IOC的关键部分是BeanDefinition和BeanFactory的实现。BeanDefinition定义了Bean的元数据,包括类名、属性、依赖关系等信息。BeanFactory负责管理Bean的生命周期,根据BeanDefinition创建Bean实例,并处理依赖注入。 Spring IOC的核心原理是通过反射机制实现动态创建和初始化Bean对象。当容器启动时,会解析配置文件或注解,将Bean的定义转化为BeanDefinition对象,并缓存在IOC容器中。当需要获取Bean实例时,容器会根据BeanDefinition创建Bean对象,并将依赖注入到对应的属性中。

🔥开始手撕IOC

创建基本的Maven项目咱直接略过。。。。

首先我们通过读取xml配置文件的方式来实现,即需要引入demo4j的依赖来帮助我们解析配置文件内的内容

代码语言:javascript
复制
<dependency>
    <groupId>dom4j</groupId>
    <artifactId>dom4j</artifactId>
    <version>1.6.1</version>
</dependency>

定义Xml

定义⼀个外部的XML,⽤于声明Bean: applicationContext.xml

代码语言:javascript
复制
<beans>
    <bean id="userDao" class="top.serms.dao.impl.UserDaoImpl"></bean>
    <bean id="userService" class="top.serms.service.impl.UserServiceImpl">
        <property name="userDao" ref="userDao"/>
    </bean>
</beans>

编写BeanDefinition

这个类表示一个bean的定义,包含了两个属性:id和className。其中,id是bean的唯一标识符,className是该bean对应的类名property就是属性,因我们的property属性可能有多个,并且对象类型也存在不同,所以这边直接采用List 集合的方式。

代码语言:javascript
复制
public class BeanDefinition {
    private String id;
    private String className;

    private List propertyValues = new ArrayList();

    public BeanDefinition(String id, String className) {
        this.id = id;
        this.className = className;
    }

    public BeanDefinition() {
    }

    public String getId() {
        return id;
    }

    public void setId(String id) {
        this.id = id;
    }

    public String getClassName() {
        return className;
    }

    public void setClassName(String className) {
        this.className = className;
    }

    public List getPropertyValues() {
        return propertyValues;
    }

    public void setPropertyValues(List propertyValues) {
        this.propertyValues = propertyValues;
    }
}

编写Bean工厂接口

BeanFactory接口是一个定义了创建和获取bean对象的机制的接口。

在Java中,我们可以把bean想象成应用程序中的各种对象,例如服务、工具、数据对象等等。而BeanFactory就像是一个工厂,负责根据我们的需求来创建和提供这些对象。

下面是BeanFactory接口中定义的方法:

  1. getBean(String beanName):根据指定的beanName获取对应的bean对象。通过调用这个方法,我们可以根据bean的名称来获取具体的对象实例。
  2. containsBean(String name):检查是否存在指定名称的bean。通过调用这个方法,我们可以判断某个特定名称的bean是否已经被创建和注册。
  3. registerBean(String beanName, Object obj):手动注册一个bean。通过调用这个方法,我们可以将一个对象注册为bean,并指定它的名称。

在下述代码中,我们创建了一个名为SimpleBeanFactory的类,它是BeanFactory接口的一个简单实现。

SimpleBeanFactory类继承了DefaultSingletonBeanRegistry类,这个类实现了SingletonBeanRegistry接口,提供了单例bean的管理功能。

在后面的SimpleBeanFactory类中,我们将要重写getBean方法。当我们调用getBean方法时,它会首先检查单例bean的管理器,即DefaultSingletonBeanRegistry,看看是否存在指定名称的bean。如果存在,则返回对应的单例bean实例;如果不存在,则抛出异常。

这样,通过SimpleBeanFactory创建的bean默认是单例的,因为它继承了单例bean管理的功能。

代码语言:javascript
复制
public interface BeanFactory {

    /*获取bean*/
    Object getBean(String beanName) throws BeansException;

    /*判断包含Bean*/
    boolean containsBean(String name);

    /*注册Bean*/
    void registerBean(String beanName, Object obj);
}

创建SingletonBeanRegistry接口

SingletonBeanRegistry接口用于管理单例bean的注册和获取。

首先,让我们了解一下什么是单例bean。在Java中,单例bean是指只有一个实例存在的对象。在整个应用程序中,无论我们从何处获取该bean,都会得到同一个实例。

接下来,我们来解释SingletonBeanRegistry接口的方法:

  1. registerSingleton(String beanName, Object singletonObject):这个方法用于注册单例bean。我们可以通过指定的beanName将一个对象注册为单例bean。注册后,我们可以使用beanName来获取该单例bean的实例。
  2. getSingleton(String beanName):这个方法用于获取指定beanName对应的单例bean实例。如果存在该单例bean,则返回其实例;如果不存在,则返回null
  3. containsSingleton(String beanName):这个方法用于检查是否存在指定名称的单例bean。如果存在,返回true;如果不存在,返回false
  4. getAllSingletons():这个方法用于获取所有已注册的单例bean的映射关系。返回一个Map,其中键是单例bean的名称,值是对应的单例bean实例。

SingletonBeanRegistry接口主要用于管理单例Bean的注册和获取。它定义了两个方法:

  1. registerSingleton(String name, Object singleton): 将单例Bean注册到单例Bean容器中,其中name是该Bean的名称,singleton是该Bean的实例。
  2. getSingleton(String name): 获取指定名称的单例Bean实例。
  3. 在下面代码中,我们实现了SingletonBeanRegistry接口的一个默认实现类DefaultSingletonBeanRegistryDefaultSingletonBeanRegistry类维护了一个singletons字典,用于存储单例bean的名称和对应的实例。 在registerSingleton方法中,我们使用synchronized关键字来确保在多线程环境下对单例bean的安全管理。我们将指定的singletonObject对象与beanName关联,并将其存储在singletons字典中。 getSingleton方法根据beanNamesingletons字典中获取相应的单例bean实例。 containsSingleton方法用于检查singletons字典中是否存在指定名称的单例bean。 getAllSingletons方法返回singletons字典,其中包含了所有已注册的单例bean的名称和实例。

此接口主要目的就是在整个Bean生命周期中只创建一次Bean,使用SingletonBeanRegistry可以方便地管理和获取单例Bean,保证每个单例Bean在整个应用中只存在一份,避免了重复创建和浪费资源的问题。同时,它还可以提供单例Bean之间的依赖注入和解耦的方式,使系统更加灵活、可维护和可扩展。

代码语言:javascript
复制
public interface SingletonBeanRegistry {

    /*注册Bean*/
    void registerSingleton(String beanName, Object singletonObject);

    /*通过名称获取对象*/
    Object getSingleton(String beanName);

    /*判断是否包含Bean*/
    boolean containsSingleton(String beanName);

    String[] getSingletonNames();
			
    Object createBean(BeanDefinition beanDefinition, List beanDefinitions);
}

实现单例接口

创建DefaultSingletonBeanRegistry类,实现SingletonBeanRegistry接口的所有方法,用于管理单例Bean的注册和获取。它维护了一个ConcurrentHashMap用于存储单例Bean,由名称和Bean对象组成,还维护了一个ConcurrentHashSet用于存储单例Bean名称。它提供了以下方法:

  • registerSingleton(String beanName, Object singletonObject):用于注册单例Bean。如果集合中已经包含相同名称的Bean,则抛出异常。
  • getSingleton(String beanName):用于根据名称获取单例Bean的实例。
  • containsSingleton(String beanName):用于判断是否存在指定名称的Bean。
  • getSingletonNames():用于获取所有单例Bean的名称。
  • createBean(BeanDefinition beanDefinition, List beanDefinitions):用于根据BeanDefinition对象创建Bean的实例。其中,BeanDefinition对象是从beanDefinitions参数中获取,是bean的配置信息。
  • getBeanDefinitionById(String id, List beanDefinitions):用于通过Bean的唯一标识符id获取对应的BeanDefinition对象。

在registerSingleton()方法中,使用synchronized锁定了singletons集合,以确保线程安全。在createBean()方法中,使用反射机制和依赖注入的方式创建和管理所有的Bean,若依赖对象未实例化,则会递归创建,并通过singletons集合进行缓存,以实现高效的访问。

代码语言:javascript
复制
class DefaultSingletonBeanRegistry implements SingletonBeanRegistry {

    // 存储单例Bean的ConcurrentHashMap
    private final Map<String, Object> singletons = new ConcurrentHashMap<>(256);

    // 存储单例Bean的名称的ConcurrentHashSet
    private final Set<String> beanNames = Collections.synchronizedSet(new HashSet<>());

    // 注册单例Bean
    @Override
    public void registerSingleton(String beanName, Object singletonObject) {
        synchronized (singletons) {
            System.out.println(beanName + " : " + singletonObject);
            if (singletons.containsKey(beanName)) {
                throw new IllegalStateException("Could not register object [" + singletonObject + "] under bean name '" + beanName + "': there is already object [" + singletons.get(beanName) + "] bound");
            }
            singletons.put(beanName, singletonObject);
            beanNames.add(beanName);
        }
    }

    // 获取Bean的名称
    @Override
    public Object getSingleton(String beanName) {
        return singletons.get(beanName);
    }

    //判断是否包含单例bean
    @Override
    public boolean containsSingleton(String beanName) {
        return singletons.containsKey(beanName);
    }

    //获取所有单例bean的名称
    @Override
    public String[] getSingletonNames() {
        return beanNames.toArray(new String[0]);
    }

    @Override
    public Object createBean(BeanDefinition beanDefinition, List beanDefinitions) {
        Object bean = null;
        try {
            bean = Class.forName(beanDefinition.getClassName()).newInstance();
            List<PropertyValue> propertyValues = beanDefinition.getPropertyValues();
            for (PropertyValue propertyValue : propertyValues) {
                Field declaredField = bean.getClass().getDeclaredField(propertyValue.getName());
                Object dependencyBean = singletons.get(propertyValue.getValue());
                if (dependencyBean == null) {
                    dependencyBean = createBean(getBeanDefinitionById((String) propertyValue.getValue(), beanDefinitions), (List) singletons);
                    singletons.put((String) propertyValue.getValue(), dependencyBean);
                }
                declaredField.setAccessible(true);
                declaredField.set(bean, dependencyBean);
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        return bean;
    }

    private BeanDefinition getBeanDefinitionById(String id, List<BeanDefinition> beanDefinitions) {
        for (BeanDefinition beanDefinition : beanDefinitions) {
            if (beanDefinition.getId().equals(id)) {
                return beanDefinition;
            }
        }
        return null;
    }
}	

创建IOC容器

ClassPathXmlApplicationContext类实现了简单的IoC容器,它的作用是读取xml文件中的Bean配置信息,将其构建成BeanDefinition对象,然后使用反射创建Bean对象,并将其注册为单例对象,最后根据Bean的名称从容器中获取Bean实例。

在构造方法中,调用了readXml方法,该方法解析xml文件获取Bean的定义信息,并保存到beanDefinitions集合中。

instanceBeans方法用于创建Bean对象。它遍历beanDefinitions集合,先获取到一个Bean的定义信息,然后通过反射机制构建Bean对象,并将其添加到容器中。

getBean方法用于获取Bean对象。它内部调用了getSingleton方法,如果该Bean在容器中已经存在,就直接返回该Bean。否则,就抛出异常Bean is not defined: + beanName。

containsBean方法用于判断是否包含某个Bean对象,它内部调用了containsSingleton方法。

registerBean方法用于注册对象,它内部调用了registerSingleton方法。

代码语言:javascript
复制
public class ClassPathXmlApplicationContext extends DefaultSingletonBeanRegistry implements BeanFactory {

    // 存储Bean的定义信息
    private List<BeanDefinition> beanDefinitions = new ArrayList<>();

    // 构造方法,读取xml文件并构建BeanDefinition对象,实例化Bean对象
    public ClassPathXmlApplicationContext(String xmlFileName) {
        readXml(xmlFileName);
        instanceBeans();
    }

    // 读取xml文件,并将读取到的配置信息构建成BeanDefinition对象,并将其加入到beanDefinitions中
    private void readXml(String xmlFileName) {
        try {
            SAXReader reader = new SAXReader();
            Document document = reader.read(ClassPathXmlApplicationContext2.class.getClassLoader().getResourceAsStream(xmlFileName));
            Element rootElement = document.getRootElement();
            List<Element> beanElements = rootElement.elements("bean");
            for (Element beanElement : beanElements) {
                String id = beanElement.attributeValue("id");
                String className = beanElement.attributeValue("class");
                BeanDefinition beanDefinition = new BeanDefinition(id, className);
                List<Element> propertyElements = beanElement.elements("property");
                for (Element propertyElement : propertyElements) {
                    String name = propertyElement.attributeValue("name");
                    String ref = propertyElement.attributeValue("ref");
                    PropertyValue propertyValue = new PropertyValue(name, ref);
                    System.out.println(propertyValue);
                    beanDefinition.getPropertyValues().add(propertyValue);
                }
                beanDefinitions.add(beanDefinition);
            }
        } catch (DocumentException e) {
            e.printStackTrace();
        }
    }


    // 使用反射来创建Bean对象,并将其存储到singletons中
    private void instanceBeans() {
        for (BeanDefinition beanDefinition : beanDefinitions) {
            try {
//                Object bean = Class.forName(beanDefinition.getClassName()).newInstance();
                Object bean = createBean(beanDefinition, beanDefinitions);
                registerSingleton(beanDefinition.getId(), bean);
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }

    // 获取Bean对象
    @Override
    public Object getBean(String beanName) throws BeansException {
        Object bean = getSingleton(beanName);
        if (bean == null) {
//            throw new BeansException("Bean is not defined: " + beanName);
        }
        return bean;
    }

    // 判断是否包含Bean对象
    @Override
    public boolean containsBean(String name) {
        return containsSingleton(name);
    }

    // 注册Bean对象
    @Override
    public void registerBean(String beanName, Object obj) {
        registerSingleton(beanName, obj);
    }
}

创建工厂实现类

接下来我们对Bean工厂做一个简单的实现,基于 Map 存储 Bean 定义和 Bean 实例的容器。它实现了 BeanFactory 接口,可以通过 getBean 方法获取 Bean 的实例,并且实现了 SingletonBeanRegistry 接口,可以注册和存储 Bean 的单例实例。

SimpleBeanFactory 主要用于演示 Spring IoC 容器的基本原理和实现方式,对于真正的应用场景来说,它的功能和扩展性都远远不足。在实际项目中,我们通常会使用更为强大和灵活的容器,如 Spring 容器或其他开源容器,这些容器提供了很多高级特性,如 AOP、事务管理、Web 开发支持等,可以大大简化应用程序的开发和维护。

代码语言:javascript
复制
public class SimpleBeanFactory extends DefaultSingletonBeanRegistry implements BeanFactory {
    @Override
    public void registerSingleton(String beanName, Object singletonObject) {
        super.registerSingleton(beanName, singletonObject);
    }

    @Override
    public Object getBean(String beanName) throws BeansException {
        Object bean = getSingleton(beanName);
        if (bean == null) {
//            throw new BeansException("Bean is not defined: " + beanName);
        }
        return bean;
    }

    @Override
    public boolean containsBean(String name) {
        return containsSingleton(name);
    }

    @Override
    public void registerBean(String beanName, Object obj) {
        registerSingleton(beanName, obj);
    }
}

测试自定义IOC容器

创建Dao层以及Service层

在UserDao中创建接口,Dao实现类中实现具体的业务,Service在进行调用,由于代码过于简单就不做展示了。

创建Test测试类

创建容器对象传入applicationContext.xml文件。。。。。。。省略。。。。。

image-20230516152302642
image-20230516152302642

最后我们看运行结果

可以看到Serivce对象和Dao都以及分别进行了创建,实现了单例对象,并也实现了DI注入,如上就是所有代码过程,在整个SpringIOC的源码中实现的功能,远不止上述这些,上述只是简单的实现了一个IOC容器。如有写的不对的地方,还请指点!

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2023-05-11,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 简介
    • Spring IOC容器的概念
      • DI(依赖注入)
      • 源码分析
        • 获取Bean
          • 获取BeanDefinition
            • 创建Bean实例
              • 初始化Bean
                • Bean注入
                • 🔥开始手撕IOC
                  • 定义Xml
                    • 编写BeanDefinition
                      • 编写Bean工厂接口
                        • 创建SingletonBeanRegistry接口
                          • 实现单例接口
                            • 创建IOC容器类
                              • 创建工厂实现类
                              • 测试自定义IOC容器
                                • 创建Dao层以及Service层
                                  • 创建Test测试类
                                  相关产品与服务
                                  容器服务
                                  腾讯云容器服务(Tencent Kubernetes Engine, TKE)基于原生 kubernetes 提供以容器为核心的、高度可扩展的高性能容器管理服务,覆盖 Serverless、边缘计算、分布式云等多种业务部署场景,业内首创单个集群兼容多种计算节点的容器资源管理模式。同时产品作为云原生 Finops 领先布道者,主导开源项目Crane,全面助力客户实现资源优化、成本控制。
                                  领券
                                  问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档