前言 本文原载于我的博客,地址:https://blog.guoziyang.top/archives/53/
最近阅读了Spring Framework中的IOC容器部分的实现,手痒,决定自己实现一个比较简单的版本。
和我一起手写一个,面试的时候丝毫不虚!
具体代码可以查看我的Github的仓库:https://github.com/CN-GuoZiyang/My-Spring-IOC
目前实现的功能有:
- xml配置文件读取
- 属性注入
- 引用依赖注入
- 递归引用注入
- singleton与prototype模式注入
- 注解配置
- 基于该容器的SpringMVC的实现(下一篇)
- AOP实现
- 循环依赖
https://github.com/CN-GuoZiyang/My-Spring-IOC/tree/82967670e52fe66ad55a6b2a539dbb4d48b46805
最终效果
主要过程按自顶向下的方式实现,最终实现的是将以下的配置文件读取后,在容器中注入Bean:
该配置文件仿照Spring的配置文件格式,注入以下的两个Bean:
package top.guoziyang.main.service;
public class HelloWorldServiceImpl implements HelloWorldService {
private String text;
@Override
public void saySomething() {
System.out.println(text);
}
}
package top.guoziyang.main.service;
public class WrapService {
private HelloWorldService helloWorldService;
public void say() {
helloWorldService.saySomething();
}
}
ApplicationContext的实现
ApplicationContext,即应用程序上下文,是Spring框架中最为核心的类,也是Spring的入口类。该接口继承自BeanFactory接口,实现了BeanFactory(实例工厂)的所有功能,还支持资源访问(如URL和文件)、事务传播等功能。但是我们还是只实现其核心的功能。
我们首先定义ApplicationContext接口:
package top.guoziyang.springframework.context;
/**
* 应用程序上下文接口
*
* @author ziyang
*/
public interface ApplicationContext {
Object getBean(Class clazz) throws Exception;
Object getBean(String beanName) throws Exception;
}
这个接口只定义了两个方法,分别通过类对象和实例的名称从容器中获取对象。
我们接着仿照Spring,编写一个抽象类AbstractApplicationContext,来实现ApplicationContext接口,书写一些通用的方法。注意,在Spring中,ApplicationContext实现BeanFactory的方式,是在ApplicationContext对象的内部,保存了一个BeanFactory对象的实例,实质上类似一种代理模式:
package top.guoziyang.springframework.context;
import top.guoziyang.springframework.factory.BeanFactory;
public abstract class AbstractApplicationContext implements ApplicationContext {BeanFactory beanFactory;
@Override
public Object getBean(Class clazz) throws Exception {
return beanFactory.getBean(clazz);
}
@Override
public Object getBean(String beanName) throws Exception {
return beanFactory.getBean(beanName);
}
}
那么现在,从ApplicationContext中取出对象的方法都实现完了,那么ApplicationContext的具体实现类的工作,就是用某种方式读取配置,然后把对象信息存入到BeanFactory中,等待用户来取。
那么在我们查看ApplicationContext的具体实现类之前,我们先来看看BeanFactory,这个实例工厂。
从AbstractApplicationContext中,我们可以知道,这个接口,有getBean这两种方法,除此以外,我还定义了一个方法:
void registerBeanDefinition(String name, BeanDefinition beanDefinition) throws Exception;
,表示像工厂中注册Bean的定义,至于BeanDefinition的实现,后面再说。BeanFactory的实现
BeanFactory,毫无疑问就是一个工厂,而且ApplicationContext就是从它这儿拿Bean的。根据名字来拿Bean,显而易见是一个类似Map的结构,这里我们采用ConcurrentHashMap来存储这个结构。那么这样,两个getBean的实现也就很显然了,仿照Spring的结构,我们还是先创建一个抽象类来实现BeanFactory接口:
package top.guoziyang.springframework.factory;
import top.guoziyang.springframework.entity.BeanDefinition;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
public abstract class AbstractBeanFactory implements BeanFactory {ConcurrentHashMap, BeanDefinition> beanDefinitionMap = new ConcurrentHashMap<>();
@Override
public Object getBean(String name) throws Exception {
BeanDefinition beanDefinition = beanDefinitionMap.get(name);
if(beanDefinition == null) return null;
if(!beanDefinition.isSingleton() || beanDefinition.getBean() == null) {
return doCreateBean(beanDefinition);
} else {
return doCreateBean(beanDefinition);
}
}@Override
public Object getBean(Class clazz) throws Exception {
BeanDefinition beanDefinition = null;
for(Map.Entry, BeanDefinition> entry : beanDefinitionMap.entrySet()) {
Class tmpClass = entry.getValue().getBeanClass();
if(tmpClass == clazz || clazz.isAssignableFrom(tmpClass)) {
beanDefinition = entry.getValue();
}
}
if(beanDefinition == null) {
return null;
}
if(!beanDefinition.isSingleton() || beanDefinition.getBean() == null) {
return doCreateBean(beanDefinition);
} else {
return beanDefinition.getBean();
}
}@Override
public void registerBeanDefinition(String name, BeanDefinition beanDefinition) {
beanDefinitionMap.put(name, beanDefinition);
}/**
* 创建Bean实例
* @param beanDefinition Bean定义对象
* @return Bean实例对象
* @throws Exception 可能出现的异常
*/
abstract Object doCreateBean(BeanDefinition beanDefinition) throws Exception;
public void populateBeans() throws Exception {
for(Map.Entry, BeanDefinition> entry : beanDefinitionMap.entrySet()) {
doCreateBean(entry.getValue());
}
}
}
这里,我们留了一个doCreateBean方法作为抽象方法,表示真正创建Bean实例对象的操作,留给具体的实现类来实现。
我们要实现的BeanFactory,是一个可以自动注入属性的BeanFactory,可以创建完成实例对象后,注入其中的属性,如果属性是一个对象引用,那么就去创建那个被引用的实例对象,并递归地完成属性注入。在Spring中,这个实现类叫做AutowiredCapableBeanFactory。于是,我们的AutowiredCapableBeanFactory的实现是这样的:
package top.guoziyang.springframework.factory;
import top.guoziyang.springframework.entity.BeanDefinition;
import top.guoziyang.springframework.entity.BeanReference;
import top.guoziyang.springframework.entity.PropertyValue;
import java.lang.reflect.Field;
public class AutowiredCapableBeanFactory extends AbstractBeanFactory {@Override
Object doCreateBean(BeanDefinition beanDefinition) throws Exception {
if(beanDefinition.isSingleton() && beanDefinition.getBean() != null) {
return beanDefinition.getBean();
}
Object bean = beanDefinition.getBeanClass().newInstance();
if(beanDefinition.isSingleton()) {
beanDefinition.setBean(bean);
}
applyPropertyValues(bean, beanDefinition);
return bean;
}/**
* 为新创建了bean注入属性
* @param bean 待注入属性的bean
* @param beanDefinition bean的定义
* @throws Exception 反射异常
*/
void applyPropertyValues(Object bean, BeanDefinition beanDefinition) throws Exception {
for(PropertyValue propertyValue : beanDefinition.getPropertyValues().getPropertyValues()) {
Field field = bean.getClass().getDeclaredField(propertyValue.getName());
Object value = https://www.it610.com/article/propertyValue.getValue();
if(value instanceof BeanReference) {
BeanReference beanReference = (BeanReference) propertyValue.getValue();
BeanDefinition refDefinition = beanDefinitionMap.get(beanReference.getName());
if(refDefinition.getBean() == null) {
value = doCreateBean(refDefinition);
}
}
field.setAccessible(true);
field.set(bean, value);
}
}
}
这里主要还是使用了反射来创建对象实例,原理比较简单,就不过多说明。
那么说了这么多,BeanDefinition到底是什么呢,又从哪里来呢?
BeanDefinition的定义如下:
public class BeanDefinition {private Object bean;
// 实例化后的对象
private Class beanClass;
private String beanClassName;
private Boolean singleton;
// 是否是单例模式
private PropertyValues propertyValues;
// Bean的属性}
PropertyValues实际上是一个List,表示一组属性的定义,内部存储的对象是PropertyValue对象,表示一个属性定义和其对应的注入属性:
public class PropertyValue {private final String name;
private final Object value;
}
注意这里的value,如果是引用其他对象的话,value就是一个BeanReference实例,表示对一个对象的引用,而不是立即初始化,因为BeanDefinition是在读取配置文件时就被创建的,这时还没有任何Bean被初始化,BeanReference仅仅是一个记录而已:
public class BeanReference {
private String name;
private Object bean;
}
BeanDefinitionReader的实现
回到正题,BeanDefinition从哪里来?目前是从文件中读取的,定义一个抽象的AbstractBeanDefinitionReader,如下:
/**
* BeanDefinitionReader实现的抽象类
*
* @author ziyang
*/
public abstract class AbstractBeanDefinitionReader implements BeanDefinitionReader {private Map, BeanDefinition> registry;
private ResourceLoader resourceLoader;
public AbstractBeanDefinitionReader(ResourceLoader resourceLoader) {
this.registry = new HashMap<>();
this.resourceLoader = resourceLoader;
}public Map, BeanDefinition> getRegistry() {
return registry;
}public ResourceLoader getResourceLoader() {
return resourceLoader;
}
}
registry也是一个Map,用于暂存Bean的名称和BeanDefinition的映射。
最终,最后的具体实现类实现了对配置文件的读取,由于我们读取的是Xml配置文件,所以我们的实现类名叫XmlBeanDefinitionReader,使用Java内置的XML解析器,可以将其解析为Document,具体的解析过程较长,不贴代码了,文件参考这里。
回到ApplicationContext
这就是完整的,一个Bean从配置文件到被实例化的过程。那么,第一节的ApplicationContext的具体实现类所要做的,就很简单了,只需要创建一个BeanDefinitionReader读取配置文件,并且将读取到的配置存到BeanFactory中,并且由BeanFactory创建对应的实例对象即可。由于我们是读取xml文件,那么这个ApplicationContext的实现类,就叫ClassPathXmlApplicationContext,具体的逻辑在obtainBeanFactory()方法中:
private AbstractBeanFactory obtainBeanFactory() throws Exception {
XmlBeanDefinitionReader beanDefinitionReader = new XmlBeanDefinitionReader(new ResourceLoader());
beanDefinitionReader.loadBeanDefinitions(location);
AbstractBeanFactory beanFactory = new AutowiredCapableBeanFactory();
for (Map.Entry, BeanDefinition> beanDefinitionEntry : beanDefinitionReader.getRegistry().entrySet()) {
beanFactory.registerBeanDefinition(beanDefinitionEntry.getKey(), beanDefinitionEntry.getValue());
}
return beanFactory;
}
看看效果!
让我们书写一些测试代码,看看效果:
public class Main {public static void main(String[] args) throws Exception {
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("application.xml");
WrapService wrapService = (WrapService) applicationContext.getBean("wrapService");
wrapService.say();
HelloWorldService helloWorldService = (HelloWorldService) applicationContext.getBean("helloWorldService");
HelloWorldService helloWorldService2 = (HelloWorldService) applicationContext.getBean("helloWorldService");
System.out.println("prototype验证:" + (helloWorldService == helloWorldService2));
WrapService wrapService2 = (WrapService) applicationContext.getBean("wrapService");
System.out.println("singleton验证:" + (wrapService == wrapService2));
}}
运行结果如下:
Hello World
prototype验证:false
singleton验证:true
这里验证了一下prototype和singleton,这里首先获取了两次HelloWorldService的实例,由于这个Bean在配置文件中被标为prototype,所以两次获取到的都不是同一个对象,使用等号比较时得到了false。而后面获取的wrapService,和第一次获取的WrapService比较,由于是singleton的,所以使用等号比较时返回true。
基于注解的注入 该部分对应的提交在
https://github.com/CN-GuoZiyang/My-Spring-IOC/tree/8a3a9c640e532c5d4aa8d62f18b42fa336c94f2e
声明注解
首先我们需要自定义一些注解,仿照Spring,我们声明一下五个注解:Autowired、Component、Qualifier、Scope和Value,用过Spring的人应该都知道以下注解的作用。
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface Autowired{}
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface Component {
String name() default "";
}
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface Qualifier {
String value();
}
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface Scope {
String value() default "singleton";
}
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface Value {
public String value();
}
由于不是SpringBoot,我们仍然需要在配置文件中书写自动注入的扫描范围,配置文件如下:
启动后,会自动扫描该包及其子包下所有使用注解标明的Bean,并注入容器。
扫描注解
由于配置文件发生了改变,自然我们需要改变xml文件的解析方式,在XmlBeanDefinitionReader的parseBeanDefinitions()方法中,一旦我们发现了component-scan标签,说明我们是使用注解来注入Bean的:
protected void parseBeanDefinitions(Element root) {
...
for(int i = 0;
i < nodeList.getLength();
i ++) {
if(nodeList.item(i) instanceof Element) {
Element ele = (Element)nodeList.item(i);
if(ele.getTagName().equals("component-scan")) {
basePackage = ele.getAttribute("base-package");
break;
}
}
}
if(basePackage != null) {
parseAnnotation(basePackage);
return;
}
...
}
我们增加了parseAnnotation方法,来对目标包进行注解扫描,实质上需要递归地扫描到该包下的所有类,并使用反射来查看该类是否使用了@Component注解,并获取相关的信息,如属性注入或者singleton或者prototype之类的信息。并将beanDefinition存入registry中:
protected void processAnnotationBeanDefinition(Class> clazz) {
if(clazz.isAnnotationPresent(Component.class)) {
String name = clazz.getAnnotation(Component.class).name();
if(name == null || name.length() == 0) {
name = clazz.getName();
}
String className = clazz.getName();
boolean singleton = true;
if(clazz.isAnnotationPresent(Scope.class) && "prototype".equals(clazz.getAnnotation(Scope.class).value())) {
singleton = false;
}
BeanDefinition beanDefinition = new BeanDefinition();
processAnnotationProperty(clazz, beanDefinition);
beanDefinition.setBeanClassName(className);
beanDefinition.setSingleton(singleton);
getRegistry().put(name, beanDefinition);
}
}
具体的实现可以看本文件
实际上,由于产生的结果一致(产生beanDefinition存入registry),可以仿照Spring的实现使用委托模式,这样耦合度就不会太高。但是由于使用注解同样还需要读取配置文件,较为繁琐,就没有解耦(实际上是我偷懒了)。
看看效果!
这时,我们就可以去测试一下。测试所用的两个类加上相应的注解即可:
@Component(name = "helloWorldService")
@Scope("prototype")
public class HelloWorldServiceImpl implements HelloWorldService {
@Value("Hello, world")
private String text;
@Override
public void saySomething() {
System.out.println(text);
}
}
@Component(name = "wrapService")
public class WrapService {
@Autowired
private HelloWorldService helloWorldService;
public void say() {
helloWorldService.saySomething();
}
}
测试代码如下:
public class Main() {
public static void annotationTest() throws Exception {
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("application-annotation.xml");
WrapService wrapService = (WrapService) applicationContext.getBean("wrapService");
wrapService.say();
HelloWorldService helloWorldService = (HelloWorldService) applicationContext.getBean("helloWorldService");
HelloWorldService helloWorldService2 = (HelloWorldService) applicationContext.getBean("helloWorldService");
System.out.println("prototype验证:相等" + (helloWorldService == helloWorldService2));
WrapService wrapService2 = (WrapService) applicationContext.getBean("wrapService");
System.out.println("singleton验证:相等" + (wrapService == wrapService2));
}
}
结果和第一次测试一致。
最后 到这里,自己手撸的Spring的控制反转容器的简单实现就完成了!还是挺有成就感的。使用体验和Spring基本没啥差别(误)。
下一篇文章,会基于已经实现的IOC容器,在其上层手撸一个SpringMVC的简单实现。
【java|手撸一个Spring IOC容器——渐进式实现】挺晚了,睡觉!
推荐阅读
- Android|2022-02-26 AndroidR 11 调用文件管理器并返回选中文件的路径
- java|节后上班第一天公司要你用SpringBoot实现万能文件在线预览
- spring|我撸了一个 Spring 容器
- java|撸一个springIoc容器
- java基础|JAVA基础之超详细面向对象程序设计一|CSDN创作打卡
- 人工智能|名校硕士苦攻5年AI无论文痛苦吐槽,导师放养怎么办()
- 数据库|数据库(基础SQL)
- mysql|MYSQL数据库主从同步设置
- mysql|【MYSQL数据库】事件(定时器)和触发器