手撸SpringIOC
首先,我们需要明白什么是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的定义信息。
@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的创建,包括构造函数的调用和依赖注入等逻辑):
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的依赖来帮助我们解析配置文件内的内容
<dependency>
<groupId>dom4j</groupId>
<artifactId>dom4j</artifactId>
<version>1.6.1</version>
</dependency>
定义Xml
定义⼀个外部的XML,⽤于声明Bean: applicationContext.xml
<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 集合的方式。
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
接口中定义的方法:
getBean(String beanName)
:根据指定的beanName
获取对应的bean对象。通过调用这个方法,我们可以根据bean的名称来获取具体的对象实例。containsBean(String name)
:检查是否存在指定名称的bean。通过调用这个方法,我们可以判断某个特定名称的bean是否已经被创建和注册。registerBean(String beanName, Object obj)
:手动注册一个bean。通过调用这个方法,我们可以将一个对象注册为bean,并指定它的名称。
在下述代码中,我们创建了一个名为SimpleBeanFactory
的类,它是BeanFactory
接口的一个简单实现。
SimpleBeanFactory
类继承了DefaultSingletonBeanRegistry
类,这个类实现了SingletonBeanRegistry
接口,提供了单例bean的管理功能。
在后面的SimpleBeanFactory
类中,我们将要重写getBean
方法。当我们调用getBean
方法时,它会首先检查单例bean的管理器,即DefaultSingletonBeanRegistry
,看看是否存在指定名称的bean。如果存在,则返回对应的单例bean实例;如果不存在,则抛出异常。
这样,通过SimpleBeanFactory
创建的bean默认是单例的,因为它继承了单例bean管理的功能。
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
接口的方法:
registerSingleton(String beanName, Object singletonObject)
:这个方法用于注册单例bean。我们可以通过指定的beanName
将一个对象注册为单例bean。注册后,我们可以使用beanName
来获取该单例bean的实例。getSingleton(String beanName)
:这个方法用于获取指定beanName
对应的单例bean实例。如果存在该单例bean,则返回其实例;如果不存在,则返回null
。containsSingleton(String beanName)
:这个方法用于检查是否存在指定名称的单例bean。如果存在,返回true
;如果不存在,返回false
。getAllSingletons()
:这个方法用于获取所有已注册的单例bean的映射关系。返回一个Map
,其中键是单例bean的名称,值是对应的单例bean实例。
SingletonBeanRegistry接口主要用于管理单例Bean的注册和获取。它定义了两个方法:
registerSingleton(String name, Object singleton): 将单例Bean注册到单例Bean容器中,其中name是该Bean的名称,singleton是该Bean的实例。
getSingleton(String name): 获取指定名称的单例Bean实例。
在下面代码中,我们实现了
SingletonBeanRegistry
接口的一个默认实现类DefaultSingletonBeanRegistry
。DefaultSingletonBeanRegistry
类维护了一个singletons
字典,用于存储单例bean的名称和对应的实例。在
registerSingleton
方法中,我们使用synchronized
关键字来确保在多线程环境下对单例bean的安全管理。我们将指定的singletonObject
对象与beanName
关联,并将其存储在singletons
字典中。getSingleton
方法根据beanName
从singletons
字典中获取相应的单例bean实例。containsSingleton
方法用于检查singletons
字典中是否存在指定名称的单例bean。getAllSingletons
方法返回singletons
字典,其中包含了所有已注册的单例bean的名称和实例。
此接口主要目的就是在整个Bean生命周期中只创建一次Bean,使用SingletonBeanRegistry可以方便地管理和获取单例Bean,保证每个单例Bean在整个应用中只存在一份,避免了重复创建和浪费资源的问题。同时,它还可以提供单例Bean之间的依赖注入和解耦的方式,使系统更加灵活、可维护和可扩展。
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集合进行缓存,以实现高效的访问。
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方法。
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 开发支持等,可以大大简化应用程序的开发和维护。
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
文件。。。。。。。省略。。。。。
最后我们看运行结果
可以看到Serivce对象和Dao都以及分别进行了创建,实现了单例对象,并也实现了DI注入,如上就是所有代码过程,在整个SpringIOC的源码中实现的功能,远不止上述这些,上述只是简单的实现了一个IOC容器。如有写的不对的地方,还请指点!
以上就是 手撸SpringIOC 的全部内容,看完如果对你有帮助,感谢赞助支持!。
❤️Sponsor
您的支持是我不断前进的动力,如果您恰巧财力雄厚,又感觉本文对您有所帮助的话,可以考虑打赏一下本文,用以维持本博客的运营费用,拒绝白嫖,从你我做起!🥰🥰🥰
- 感谢你赐予我前进的力量