源码学习之路--Spring-ioc
这篇文章我将一步一步实现一个简单的spring框架
首先,我先简单的介绍下Spring的核心思想,IOC和AOP。
本文主要实现的是ioc
什么是IOC?
控制:其实就是对象的创建权利;反转,就是把对象的创建权利交给了外部环境。
IoC解决了什么问题?
当类B依赖于类A时,我们只让A来实现一个接口Interface,而B类中,我们只引用这个接口Interface(我们的容器将这个接口引用指向A的实现),当A类的业务功能有变动时,我们只需要修改A类的实现,
或者重新创建一个C类来实现这个接口Interface(我们的容器将这个接口引用指向C的实现),而我们的B类是不需要进行修改的。
IoC和DI的区别
其实IOC与DI描述的是同一个事情,IOC是站在对象的角度,对象实例化及其管理的权利反转给了容器;DI是站在容器的角度,容器会把对象A依赖的对象B送到对象A中。
什么是AOP
首先我们要知道什么叫横切逻辑代码,在多个纵向流程中出现的相同子流程代码就是横切逻辑代码。简单的说就是在不同的方法中存在重复代码,比如事务控制,权限校验,日志记录等。
而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的功能就完成了,通过一个测试来试验一下。
结果,我们获取到了我们的类,而整个过程我们也没有进行new的操作。