前言
本文原載于我的博客边败,地址:https://blog.guoziyang.top/archives/53/
最近閱讀了Spring Framework中的IOC容器部分的實(shí)現(xiàn),手癢,決定自己實(shí)現(xiàn)一個(gè)比較簡單的版本瓢棒。
具體代碼可以查看我的Github的倉庫:https://github.com/CN-GuoZiyang/My-Spring-IOC
目前實(shí)現(xiàn)的功能有:
- xml配置文件讀取
- 屬性注入
- 引用依賴注入
- 遞歸引用注入
- singleton與prototype模式注入
- 注解配置
- 基于該容器的SpringMVC的實(shí)現(xiàn)(下一篇)
待實(shí)現(xiàn):
- AOP實(shí)現(xiàn)
- 循環(huán)依賴
基于xml配置文件的注入
該部分對應(yīng)的提交在:
https://github.com/CN-GuoZiyang/My-Spring-IOC/tree/82967670e52fe66ad55a6b2a539dbb4d48b46805
最終效果
主要過程按自頂向下的方式實(shí)現(xiàn)祷膳,最終實(shí)現(xiàn)的是將以下的配置文件讀取后,在容器中注入Bean:
<?xml version="1.0" encoding="UTF-8"?>
<beans>
<bean id="helloWorldService" class="top.guoziyang.main.service.HelloWorldServiceImpl" scope="prototype">
<property name="text" value="Hello World"></property>
</bean>
<bean id="wrapService" class="top.guoziyang.main.service.WrapService">
<property name="helloWorldService" ref="helloWorldService"></property>
</bean>
</beans>
該配置文件仿照Spring的配置文件格式乃戈,注入以下的兩個(gè)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的實(shí)現(xiàn)
ApplicationContext褂痰,即應(yīng)用程序上下文,是Spring框架中最為核心的類症虑,也是Spring的入口類缩歪。該接口繼承自BeanFactory接口,實(shí)現(xiàn)了BeanFactory(實(shí)例工廠)的所有功能谍憔,還支持資源訪問(如URL和文件)匪蝙、事務(wù)傳播等功能。但是我們還是只實(shí)現(xiàn)其核心的功能习贫。
我們首先定義ApplicationContext接口:
package top.guoziyang.springframework.context;
/**
* 應(yīng)用程序上下文接口
*
* @author ziyang
*/
public interface ApplicationContext {
Object getBean(Class clazz) throws Exception;
Object getBean(String beanName) throws Exception;
}
這個(gè)接口只定義了兩個(gè)方法逛球,分別通過類對象和實(shí)例的名稱從容器中獲取對象。
我們接著仿照Spring沈条,編寫一個(gè)抽象類AbstractApplicationContext需忿,來實(shí)現(xiàn)ApplicationContext接口,書寫一些通用的方法蜡歹。注意屋厘,在Spring中,ApplicationContext實(shí)現(xiàn)BeanFactory的方式月而,是在ApplicationContext對象的內(nèi)部汗洒,保存了一個(gè)BeanFactory對象的實(shí)例,實(shí)質(zhì)上類似一種代理模式:
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);
}
}
那么現(xiàn)在父款,從ApplicationContext中取出對象的方法都實(shí)現(xiàn)完了溢谤,那么ApplicationContext的具體實(shí)現(xiàn)類的工作瞻凤,就是用某種方式讀取配置,然后把對象信息存入到BeanFactory中世杀,等待用戶來取阀参。
那么在我們查看ApplicationContext的具體實(shí)現(xiàn)類之前,我們先來看看BeanFactory瞻坝,這個(gè)實(shí)例工廠蛛壳。
從AbstractApplicationContext中,我們可以知道所刀,這個(gè)接口衙荐,有g(shù)etBean這兩種方法,除此以外浮创,我還定義了一個(gè)方法:void registerBeanDefinition(String name, BeanDefinition beanDefinition) throws Exception;
忧吟,表示像工廠中注冊Bean的定義,至于BeanDefinition的實(shí)現(xiàn)斩披,后面再說溜族。
BeanFactory的實(shí)現(xiàn)
BeanFactory,毫無疑問就是一個(gè)工廠雏掠,而且ApplicationContext就是從它這兒拿Bean的斩祭。根據(jù)名字來拿Bean,顯而易見是一個(gè)類似Map的結(jié)構(gòu)乡话,這里我們采用ConcurrentHashMap來存儲這個(gè)結(jié)構(gòu)摧玫。那么這樣,兩個(gè)getBean的實(shí)現(xiàn)也就很顯然了绑青,仿照Spring的結(jié)構(gòu)诬像,我們還是先創(chuàng)建一個(gè)抽象類來實(shí)現(xiàn)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<String, 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<String, 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);
}
/**
* 創(chuàng)建Bean實(shí)例
* @param beanDefinition Bean定義對象
* @return Bean實(shí)例對象
* @throws Exception 可能出現(xiàn)的異常
*/
abstract Object doCreateBean(BeanDefinition beanDefinition) throws Exception;
public void populateBeans() throws Exception {
for(Map.Entry<String, BeanDefinition> entry : beanDefinitionMap.entrySet()) {
doCreateBean(entry.getValue());
}
}
}
這里,我們留了一個(gè)doCreateBean方法作為抽象方法闸婴,表示真正創(chuàng)建Bean實(shí)例對象的操作坏挠,留給具體的實(shí)現(xiàn)類來實(shí)現(xiàn)。
我們要實(shí)現(xiàn)的BeanFactory邪乍,是一個(gè)可以自動注入屬性的BeanFactory降狠,可以創(chuàng)建完成實(shí)例對象后,注入其中的屬性庇楞,如果屬性是一個(gè)對象引用榜配,那么就去創(chuàng)建那個(gè)被引用的實(shí)例對象,并遞歸地完成屬性注入吕晌。在Spring中蛋褥,這個(gè)實(shí)現(xiàn)類叫做AutowiredCapableBeanFactory。于是睛驳,我們的AutowiredCapableBeanFactory的實(shí)現(xiàn)是這樣的:
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;
}
/**
* 為新創(chuàng)建了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 = 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);
}
}
}
這里主要還是使用了反射來創(chuàng)建對象實(shí)例烙心,原理比較簡單膜廊,就不過多說明。
那么說了這么多淫茵,BeanDefinition到底是什么呢爪瓜,又從哪里來呢?
BeanDefinition的定義如下:
public class BeanDefinition {
private Object bean; // 實(shí)例化后的對象
private Class beanClass;
private String beanClassName;
private Boolean singleton; // 是否是單例模式
private PropertyValues propertyValues; // Bean的屬性
}
PropertyValues實(shí)際上是一個(gè)List匙瘪,表示一組屬性的定義钥勋,內(nèi)部存儲的對象是PropertyValue對象,表示一個(gè)屬性定義和其對應(yīng)的注入屬性:
public class PropertyValue {
private final String name;
private final Object value;
}
注意這里的value辆苔,如果是引用其他對象的話,value就是一個(gè)BeanReference實(shí)例扼劈,表示對一個(gè)對象的引用驻啤,而不是立即初始化,因?yàn)锽eanDefinition是在讀取配置文件時(shí)就被創(chuàng)建的荐吵,這時(shí)還沒有任何Bean被初始化骑冗,BeanReference僅僅是一個(gè)記錄而已:
public class BeanReference {
private String name;
private Object bean;
}
BeanDefinitionReader的實(shí)現(xiàn)
回到正題,BeanDefinition從哪里來先煎?目前是從文件中讀取的贼涩,定義一個(gè)抽象的AbstractBeanDefinitionReader,如下:
/**
* BeanDefinitionReader實(shí)現(xiàn)的抽象類
*
* @author ziyang
*/
public abstract class AbstractBeanDefinitionReader implements BeanDefinitionReader {
private Map<String, BeanDefinition> registry;
private ResourceLoader resourceLoader;
public AbstractBeanDefinitionReader(ResourceLoader resourceLoader) {
this.registry = new HashMap<>();
this.resourceLoader = resourceLoader;
}
public Map<String, BeanDefinition> getRegistry() {
return registry;
}
public ResourceLoader getResourceLoader() {
return resourceLoader;
}
}
registry也是一個(gè)Map薯蝎,用于暫存Bean的名稱和BeanDefinition的映射遥倦。
最終,最后的具體實(shí)現(xiàn)類實(shí)現(xiàn)了對配置文件的讀取占锯,由于我們讀取的是Xml配置文件袒哥,所以我們的實(shí)現(xiàn)類名叫XmlBeanDefinitionReader,使用Java內(nèi)置的XML解析器消略,可以將其解析為Document堡称,具體的解析過程較長,不貼代碼了艺演,文件參考這里却紧。
回到ApplicationContext
這就是完整的,一個(gè)Bean從配置文件到被實(shí)例化的過程胎撤。那么晓殊,第一節(jié)的ApplicationContext的具體實(shí)現(xiàn)類所要做的,就很簡單了哩照,只需要?jiǎng)?chuàng)建一個(gè)BeanDefinitionReader讀取配置文件挺物,并且將讀取到的配置存到BeanFactory中,并且由BeanFactory創(chuàng)建對應(yīng)的實(shí)例對象即可飘弧。由于我們是讀取xml文件识藤,那么這個(gè)ApplicationContext的實(shí)現(xiàn)類砚著,就叫ClassPathXmlApplicationContext,具體的邏輯在obtainBeanFactory()方法中:
private AbstractBeanFactory obtainBeanFactory() throws Exception {
XmlBeanDefinitionReader beanDefinitionReader = new XmlBeanDefinitionReader(new ResourceLoader());
beanDefinitionReader.loadBeanDefinitions(location);
AbstractBeanFactory beanFactory = new AutowiredCapableBeanFactory();
for (Map.Entry<String, 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驗(yàn)證:" + (helloWorldService == helloWorldService2));
WrapService wrapService2 = (WrapService) applicationContext.getBean("wrapService");
System.out.println("singleton驗(yàn)證:" + (wrapService == wrapService2));
}
}
運(yùn)行結(jié)果如下:
Hello World
prototype驗(yàn)證:false
singleton驗(yàn)證:true
這里驗(yàn)證了一下prototype和singleton,這里首先獲取了兩次HelloWorldService的實(shí)例赶撰,由于這個(gè)Bean在配置文件中被標(biāo)為prototype舌镶,所以兩次獲取到的都不是同一個(gè)對象,使用等號比較時(shí)得到了false豪娜。而后面獲取的wrapService餐胀,和第一次獲取的WrapService比較,由于是singleton的瘤载,所以使用等號比較時(shí)返回true否灾。
基于注解的注入
該部分對應(yīng)的提交在
https://github.com/CN-GuoZiyang/My-Spring-IOC/tree/8a3a9c640e532c5d4aa8d62f18b42fa336c94f2e
聲明注解
首先我們需要自定義一些注解,仿照Spring鸣奔,我們聲明一下五個(gè)注解:Autowired墨技、Component、Qualifier挎狸、Scope和Value扣汪,用過Spring的人應(yīng)該都知道以下注解的作用。
@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锨匆,我們?nèi)匀恍枰谂渲梦募袝鴮懽詣幼⑷氲膾呙璺秶副穑渲梦募缦拢?/p>
<?xml version="1.0" encoding="UTF-8"?>
<beans>
<component-scan base-package="top.guoziyang.main"></component-scan>
</beans>
啟動后,會自動掃描該包及其子包下所有使用注解標(biāo)明的Bean统刮,并注入容器紊遵。
掃描注解
由于配置文件發(fā)生了改變,自然我們需要改變xml文件的解析方式侥蒙,在XmlBeanDefinitionReader的parseBeanDefinitions()方法中暗膜,一旦我們發(fā)現(xiàn)了component-scan標(biāo)簽,說明我們是使用注解來注入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方法鞭衩,來對目標(biāo)包進(jìn)行注解掃描学搜,實(shí)質(zhì)上需要遞歸地掃描到該包下的所有類,并使用反射來查看該類是否使用了@Component注解论衍,并獲取相關(guān)的信息瑞佩,如屬性注入或者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);
}
}
具體的實(shí)現(xiàn)可以看本文件
實(shí)際上坯台,由于產(chǎn)生的結(jié)果一致(產(chǎn)生beanDefinition存入registry)炬丸,可以仿照Spring的實(shí)現(xiàn)使用委托模式,這樣耦合度就不會太高。但是由于使用注解同樣還需要讀取配置文件稠炬,較為繁瑣焕阿,就沒有解耦(實(shí)際上是我偷懶了)。
看看效果首启!
這時(shí)暮屡,我們就可以去測試一下。測試所用的兩個(gè)類加上相應(yīng)的注解即可:
@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驗(yàn)證:相等" + (helloWorldService == helloWorldService2));
WrapService wrapService2 = (WrapService) applicationContext.getBean("wrapService");
System.out.println("singleton驗(yàn)證:相等" + (wrapService == wrapService2));
}
}
結(jié)果和第一次測試一致毅桃。
最后
到這里褒纲,自己手?jǐn)]的Spring的控制反轉(zhuǎn)容器的簡單實(shí)現(xiàn)就完成了!還是挺有成就感的钥飞。使用體驗(yàn)和Spring基本沒啥差別(誤)莺掠。
下一篇文章,會基于已經(jīng)實(shí)現(xiàn)的IOC容器读宙,在其上層手?jǐn)]一個(gè)SpringMVC的簡單實(shí)現(xiàn)汁蝶。
挺晚了,睡覺论悴!