这篇文章我将一步一步实现一个简单的spring框架

首先,我先简单的介绍下Spring的核心思想,IOC和AOP。

本文主要实现的是ioc

什么是IOC?


IoC Inversion of Control (控制反/反转控制),它是一个思想,而不是一个技术实现,对于传统开发,比如类A依赖于类B,往往会在类A中new一个B的对象。



IoC思想下开发方式:我们不用自己去new对象了,而是由IoC容器(Spring框架)去帮助我们实例化对象并且管理它,我们需要使用哪个对象,去问IoC容器要即可。


控制:其实就是对象的创建权利;反,就是把对象的创建权利交给了外部环境。 



IoC解决了什么问题?


IoC解决对象之间的耦合问题


当类B依赖于类A时,我们只让A来实现一个接口Interface,而B类中,我们只引用这个接口Interface(我们的容器将这个接口引用指向A的实现),当A类的业务功能有变动时,我们只需要修改A类的实现,

或者重新创建一个C类来实现这个接口Interface(我们的容器将这个接口引用指向C的实现),而我们的B类是不需要进行修改的。



IoC和DI的区别


DI:Dependancy Injection(依赖注入)

其实IOC与DI描述的是同一个事情,IOC是站在对象的角度,对象实例化及其管理的权利反给了容器;DI是站在容器的角度,容器会把对象A依赖的对象B送到对象A中。


什么是AOP



AOP: Aspect oriented Programming 面向切面编程/面向方面编程


首先我们要知道什么叫横切逻辑代码,在多个纵向流程中出现的相同子流程代码就是横切逻辑代码。简单的说就是在不同的方法中存在重复代码,比如事务控制,权限校验,日志记录等。


横切逻辑代码存在什么问题:

       1.横切代码重复问题

       2.横切逻辑代码和业务代码混杂在一起,代码臃肿,维护不方便


而AOP解决的就是这些问题,它将横切逻辑代码和业务逻辑代码分离。它可以在不改变原有业务逻辑情况下,增强横切逻辑代码,根本上解耦合,避免横切逻辑代码重复。



为什么叫做面向切面编程


    切:指的是横切逻辑,原有业务逻辑代码我们不能动,只能操作横切逻辑代码,所以面向横切逻辑

    面:横切逻辑代码往往要影响的是很多个方法,每一个方法都如同一个点,多个点构成面,有一个面的概念在里面


接下来,开始编写一个简单的SpringDemo

与Mybatis相同,我们要创建一个beans.xml来记录我们的依赖关系,然后通过工厂去进行解析创建并存储对象,供我们后续的调用(这一步其实就是实现一个IOC的功能)

创建beans.xml


<?xml version="1.0" encoding="UTF-8" ?>

<beans>

<bean id="accountDao" class="com.hg.dao.impl.JdbcAccountDaoImpl">
<property name="ConnectionUtils" ref="connectionUtils"/>
</bean>
<bean id="transferService" class="com.hg.service.impl.TransferServiceImpl">

<property name="AccountDao" ref="accountDao"></property>
</bean>

<bean id="connectionUtils" class="com.hg.utils.ConnectionUtils"></bean>


<bean id="transactionManager" class="com.hg.utils.TransactionManager">
<property name="ConnectionUtils" ref="connectionUtils"/>
</bean>

<bean id="proxyFactory" class="com.hg.factory.ProxyFactory">
<property name="TransactionManager" ref="transactionManager"/>
</bean>
</beans>


其中,每个bean标签都代表一个对象,id做为唯一标识来定位一个bean,class是用来标识我们将要创建哪一个类,也就是类的类路径,方便后面通过反射去创建这个类;而property代表对象中的一个属性,property中的 name是为了去创建对象的时候,通过set方法将属性注入,ref用来

解析属性需要哪一个具体的bean来赋值,这样我们就可以通过一个工厂类来进行beans.xml的解析。

创建BeanFactory


public class BeanFactory {

/**
* 任务一:读取解析xml,通过反射技术实例化对象并且存储待用(map集合)
* 任务二:对外提供获取实例对象的接口(根据id获取)
*/

private static Map<String,Object> map = new HashMap<>(); // 存储对象


static {
// 任务一:读取解析xml,通过反射技术实例化对象并且存储待用(map集合)
// 加载xml
InputStream resourceAsStream = BeanFactory.class.getClassLoader().getResourceAsStream("beans.xml");
// 解析xml
SAXReader saxReader = new SAXReader();
try {
Document document = saxReader.read(resourceAsStream);
Element rootElement = document.getRootElement();
List<Element> beanList = rootElement.selectNodes("//bean");
for (int i = 0; i < beanList.size(); i++) {
Element element = beanList.get(i);
// 处理每个bean元素,获取到该元素的id 和 class 属性
String id = element.attributeValue("id"); // accountDao
String clazz = element.attributeValue("class"); // com.hg.dao.impl.JdbcAccountDaoImpl
// 通过反射技术实例化对象
Class<?> aClass = Class.forName(clazz);
Object o = aClass.newInstance(); // 实例化之后的对象

// 存储到map中待用
map.put(id,o);

}

// 实例化完成之后维护对象的依赖关系,检查哪些对象需要传值进入,根据它的配置,我们传入相应的值
// 有property子元素的bean就有传值需求
List<Element> propertyList = rootElement.selectNodes("//property");
// 解析property,获取父元素
for (int i = 0; i < propertyList.size(); i++) {
Element element = propertyList.get(i); //<property name="AccountDao" ref="accountDao"></property>
String name = element.attributeValue("name");
String ref = element.attributeValue("ref");

// 找到当前需要被处理依赖关系的bean
Element parent = element.getParent();

// 调用父元素对象的反射功能
String parentId = parent.attributeValue("id");
Object parentObject = map.get(parentId);
// 遍历父对象中的所有方法,找到"set" + name
Method[] methods = parentObject.getClass().getMethods();
for (int j = 0; j < methods.length; j++) {
Method method = methods[j];
if(method.getName().equalsIgnoreCase("set" + name)) { // 该方法就是 setAccountDao(AccountDao accountDao)
method.invoke(parentObject,map.get(ref));
}
}

// 把处理之后的parentObject重新放到map中
map.put(parentId,parentObject);

}


} catch (DocumentException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InstantiationException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
}

}


// 任务二:对外提供获取实例对象的接口(根据id获取)
public static Object getBean(String id) {
return map.get(id);
}

}






首先我们读取beans.xml流,然后通过Dom4j进行解析,获取所有的bean标签,然后通过反射(bean标签中的class获取到类的类路径)进行实例化然后将对象存入到map中,key就是我们bean标签中的id,值就是我们实例化的对象。

后面我们可以通过getBean(id)来获取到对象。接下来就要解析property属性,并给属性赋值。




  先获取到所有的property标签,解析property获得name 和ref 的值,然后得到property的父标签,从而获得这个属性所属的类(比如是类A),然后遍历类的所有方法,找出set+name的那个set方法,通过反射将ref指向的类通过set方法放入到类A,然后再把类A放回到map中,这样基本上一个IOC的功能就完成了,通过一个测试来试验一下。

源码学习之路--Spring-ioc_set方法


 结果,我们获取到了我们的类,而整个过程我们也没有进行new的操作。