📔【Spring】Spring IoC 容器浅析及简单实现
Spring IoC 概述
原生的 JavaEE 技术中各个模块之间的联系较强,即耦合度较高
。
比如完成一个用户的创建事务,视图层会创建业务逻辑层的对象,再在内部调用对象的方法,各个模块的独立性很差
,如果某一模块的代码发生改变,其他模块的改动也会很大。
而 Spring 框架的核心——IoC(控制反转)很好的解决了这一问题。控制反转,即某一接口具体实现类的选择控制权从调用类中移除,转交给第三方决定
,即由 Spring 容器借由 Bean 配置来进行控制。
可能 IoC 不够开门见山,理解起来较为困难。因此, Martin Fowler 提出了 DI(Dependency Injection,依赖注入)的概念来替代 IoC,即让调用类对某一接口实现类的依赖关系由第三方(容器或写协作类)注入,以移除调用类对某一接口实现类的依赖
。
比如说, 上述例子中,视图层使用业务逻辑层的接口变量,而不需要真正 new 出接口的实现,这样即使接口产生了新的实现或原有实现修改,视图层都能正常运行。
从注入方法上看,IoC 主要划分为三种类型:构造函数注入、属性注入和接口注入。在开发过程中,一般使用属性注入
的方法。
IoC 不仅可以实现类之间的解耦
,还能帮助完成类的初始化与装配工作
,让开发者从这些底层实现类的实例化、依赖关系装配等工作中解脱出出来,专注于更有意义的业务逻辑开发工作。
Spring IoC 简单实现
下面实现了一个IoC容器的核心部分,简单模拟了IoC容器的基本功能。
下面列举出核心类:
Student.java
/**
* @ClassName Student
* @Description 学生实体类
* @Author Yixiang Zhao
* @Date 2018/9/22 9:19
* @Version 1.0
*/
public class Student {
private String name;
private String gender;
public void intro() {
System.out.println("My name is " + name + " and I'm " + gender + " .");
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getGender() {
return gender;
}
public void setGender(String gender) {
this.gender = gender;
}
}
StuService.java
/**
* @ClassName StuService
* @Description 学生Service
* @Author Yixiang Zhao
* @Date 2018/9/22 9:21
* @Version 1.0
*/
public class StuService {
private Student student;
public Student getStudent() {
return student;
}
public void setStudent(Student student) {
this.student = student;
}
}
beans.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans>
<bean id="Student" class="me.seriouszyx.pojo.Student">
<property name="name" value="ZYX"/>
<property name="gender" value="man"/>
</bean>
<bean id="StuService" class="me.seriouszyx.service.StuService">
<property ref="Student"/>
</bean>
</beans>
下面是核心类 ClassPathXMLApplicationContext.java
/**
* @ClassName ClassPathXMLApplicationContext
* @Description ApplicationContext的实现,核心类
* @Author Yixiang Zhao
* @Date 2018/9/22 9:40
* @Version 1.0
*/
public class ClassPathXMLApplicationContext implements ApplicationContext {
private Map map = new HashMap();
public ClassPathXMLApplicationContext(String location) {
try {
Document document = getDocument(location);
XMLParsing(document);
} catch (Exception e) {
e.printStackTrace();
}
}
// 加载资源文件,转换成Document类型
private Document getDocument(String location) throws JDOMException, IOException {
SAXBuilder saxBuilder = new SAXBuilder();
return saxBuilder.build(this.getClass().getClassLoader().getResource(location));
}
private void XMLParsing(Document document) throws Exception {
// 获取XML文件根元素beans
Element beans = document.getRootElement();
// 获取beans下的bean集合
List beanList = beans.getChildren("bean");
// 遍历beans集合
for (Iterator iter = beanList.iterator(); iter.hasNext(); ) {
Element bean = (Element) iter.next();
// 获取bean的属性id和class,id为类的key值,class为类的路径
String id = bean.getAttributeValue("id");
String className = bean.getAttributeValue("class");
// 动态加载该bean代表的类
Object obj = Class.forName(className).newInstance();
// 获得该类的所有方法
Method[] methods = obj.getClass().getDeclaredMethods();
// 获取该节点的所有子节点,子节点存储类的初始化参数
List<Element> properties = bean.getChildren("property");
// 遍历,将初始化参数和类的方法对应,进行类的初始化
for (Element pro : properties) {
for (int i = 0; i < methods.length; i++) {
String methodName = methods[i].getName();
if (methodName.startsWith("set")) {
String classProperty = methodName.substring(3, methodName.length()).toLowerCase();
if (pro.getAttribute("name") != null) {
if (classProperty.equals(pro.getAttribute("name").getValue())) {
methods[i].invoke(obj, pro.getAttribute("value").getValue());
}
} else {
methods[i].invoke(obj, map.get(pro.getAttribute("ref").getValue()));
}
}
}
}
// 将初始化完成的对象添加到HashMap中
map.put(id, obj);
}
}
public Object getBean(String name) {
return map.get(name);
}
}
最后进行测试
public class MyIoCTest {
public static void main(String[] args) {
ApplicationContext context = new ClassPathXMLApplicationContext("beans.xml");
StuService stuService = (StuService) context.getBean("StuService");
stuService.getStudent().intro();
}
}
测试成功!
My name is ZYX and I'm man .
Process finished with exit code 0
源码
代码在我的 GitHub开源,欢迎一起交流讨论。
总结
熟悉一个框架最好的方式,就是亲手实现它。这样不仅会深刻地认识到框架的工作原理,以后的使用也会更加得心应手。
此外,在实现的过程中,又会收获很多东西,就像实现 IoC 容器一样,不仅了解解析 XML 文件的 JDOM 工具,还加深了对 Java 反射的理解。在实际开发中,几乎没有任何地方需要用到反射这一技术,但在框架实现过程中,不懂反射则寸步难行。
更多的 Spring 学习心得请戳Spring 框架学习
- 版权声明:本文采用知识共享 3.0 许可证 (保持署名-自由转载-非商用-非衍生)
- 发表于 2018-09-22