手撸Java Spring

首先,我们需要明白什么是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的定义信息。

1
2
3
4
5
6
7
8
9
10
@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的创建,包括构造函数的调用和依赖注入等逻辑):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
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的依赖来帮助我们解析配置文件内的内容

1
2
3
4
5
<dependency>
<groupId>dom4j</groupId>
<artifactId>dom4j</artifactId>
<version>1.6.1</version>
</dependency>

定义Xml

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

1
2
3
4
5
6
<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 集合的方式。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
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管理的功能。

1
2
3
4
5
6
7
8
9
10
11
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接口的一个默认实现类DefaultSingletonBeanRegistry

    DefaultSingletonBeanRegistry类维护了一个singletons字典,用于存储单例bean的名称和对应的实例。

    registerSingleton方法中,我们使用synchronized关键字来确保在多线程环境下对单例bean的安全管理。我们将指定的singletonObject对象与beanName关联,并将其存储在singletons字典中。

    getSingleton方法根据beanNamesingletons字典中获取相应的单例bean实例。

    containsSingleton方法用于检查singletons字典中是否存在指定名称的单例bean。

    getAllSingletons方法返回singletons字典,其中包含了所有已注册的单例bean的名称和实例。

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
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集合进行缓存,以实现高效的访问。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
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方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
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 开发支持等,可以大大简化应用程序的开发和维护。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
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

最后我们看运行结果

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

以上就是 手撸SpringIOC 的全部内容,看完如果对你有帮助,感谢赞助支持!。

❤️Sponsor

您的支持是我不断前进的动力,如果您恰巧财力雄厚,又感觉本文对您有所帮助的话,可以考虑打赏一下本文,用以维持本博客的运营费用,拒绝白嫖,从你我做起!🥰🥰🥰

支付宝微信