Spring 是目前 javaEE 开发中的主流框架,可以说 spring 让 java 开发人员迎来了一个“春天”,
Spring框架是Spring家族下一个JAVAEE企业开发的一栈式轻量级框架。
?从 Spring 架构图可以看出,spring 最核心的是 Core Container ,而在核心容器中核心的,是 Core 和 Bean ,也就是 IOC 控制反转和 DI 依赖注入
Spring 的这两个组件是怎么工作的呢,
在我们使用 spring 时,会设置它的配置文件,在里面配置 bean 标签,然后使用 property?标签依赖注入,这里使用到的逻辑也很简单,就是
- 解析这个 xml 文件,获取对应标签的值,
- 使用反射来创建对象,调用 set 方法注入
- 将对象存入 map 中,键名为 beanid值
- 公开一个 getBean 方法,返回Bean
理解它的工作原理之后,我们来自己手写一个类似的容器
项目准备
beans.xml —— spring 配置文件,用于生成 Bean 和依赖注入
<?xml version="1.0" encoding="UTF-8" ?>
<beans>
<bean id="user" class="com.dao.User">
<property name="username" value="TOM"/>
<property name="password" value="123456"/>
</bean>
</beans>
User.java
package com.dao;
public class User {
private String username;
private String password;
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
@Override
public String toString() {
return "User{" +
"username='" + username + '\'' +
", password='" + password + '\'' +
'}';
}
}
现在我们就要编写解析 XML 的 SpringAppLicattionContext 类和启动容器的 app 类
解析 XML 是这个容器最重要也是最核心的功能,与之一样重要的就是使用反射来创建对象
package com;
import org.dom4j.Branch;
import org.dom4j.Document;
import org.dom4j.DocumentException;
import org.dom4j.Element;
import org.dom4j.io.SAXReader;
import java.io.*;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.List;
import java.util.Set;
/**
* 一些声明信息
*
* @author 谢柯
* @date: 2022/9/15 23:42
* @description: 类spring IOC容器
* @since JDK 1.8
*/
public class SpringApplicationContext {
private String path;
private HashMap<String,Object> mapBean=new HashMap<String, Object>();
private HashMap<String,String> mapElement=new HashMap<String, String>();
public SpringApplicationContext(String path) throws DocumentException, FileNotFoundException, ClassNotFoundException, InvocationTargetException, NoSuchMethodException, InstantiationException, IllegalAccessException {
this.path = path;
this.xml();
}
public void xml() throws FileNotFoundException, DocumentException, ClassNotFoundException, NoSuchMethodException, InstantiationException, IllegalAccessException, InvocationTargetException {
// 文件输入流
InputStream stream = ClassLoader.getSystemResourceAsStream(this.path);
// new DOM4j的核心对象,SAXReader
SAXReader saxReader = new SAXReader();
// 通过SAXReader对象的read()方法得到Document对象。该方法需要输入流,该输入流指向要解析的文件
Document read = saxReader.read(stream);
// 用Document对对象的getRootElement()方法,获取根节点
Element rootElement = read.getRootElement();
// 用根节点的elements(节点名)方法,获取子节点对象集
List<Element> beans = rootElement.elements("bean");
// 遍历集合,获得每一个节点对象
for (Element bean : beans) {
// 获取要创建bean的id名,全类名
String id = bean.attributeValue("id");
String _class = bean.attributeValue("class");
// 获取子节点
List<Element> property = bean.elements("property");
// 遍历子节点标签
for (Element element : property) {
// 获取子节点标签的属性值
String name = element.attributeValue("name");
String value = element.attributeValue("value");
// 将遍历出来的值暂存到map中,这是依赖注入的值
mapElement.put(name,value);
}
// 获取字节码文件对象
Class<?> aClass = Class.forName(_class);
// 直接创建对象
Object o = aClass.newInstance();
Set<String> set = mapElement.keySet();
// 执行对象的set方法,注入值
for (String name : set) {
// 将属性名第一个字母大写
String MethodName="set"+name.substring(0,1).toUpperCase()+name.substring(1);
String value = mapElement.get(name);
// 获取set方法
Method method = aClass.getMethod(MethodName, String.class);
method.invoke(o,value);
}
// 加入map
mapBean.put(id,o);
}
}
// 获取Bean,如果传入class的话,可以直接返回指定类型的一个对象
public Object getBean(String BreanName){
Object o = mapBean.get(BreanName);
System.out.println(o.toString());
return o;
}
}
解析 XML 常见的有 JAXP,JDOM,DOM4J,我这里使用DOM4J
DOM4J的解析流程为:
?1 - 创建DOM4J的核心对象,SAXReader ?2 - 通过SAXReader对象的read()方法得到Document对象。该方法需要输入流指向要解析的文件 ?3 - ?用Document对对象的getRootElement()方法,获取根节点 ?4 - 用根节点的elements(子节点名)方法,获取子节点对象集 ?5 - 遍历集合,获得每一个节点对象,并对其处理 ? ? 获取属性信息 —— 节点对象 . attributeValue(属性) ? ? 获取文本内容?—— 节点对象 .?getText() ? ? 获取下级标签 —— 节点对象 . element(标签名)
另外在编写上述代码时遇到了几个问题,
1)定义成员变量
?private HashMap<String,Object> mapBean=new HashMap<String, Object>();
和
private HashMap<String,Object> mapBean
的区别
如果使用 new 来创建,那么是创建了一个实例对象,该对象在堆内存拥有内存空间,变量指向该内存空间
如果只是定义一个变量,那么该变量默认值为 null ,无法进行任何操作
2)在创建文件输入流(FileInputStream)时,不论使用相对还是绝对路径,都读不到 xml,我创建的是一个 maven 项目,实在让我纳闷,最后输入流使用了??
ClassLoader.getSystemResourceAsStream(path);
容器启动类 App.java
package com;
import com.itheima.domain.User;
import com.itheima.service.impl.UserServiceImpl;
import org.dom4j.DocumentException;
import java.io.FileNotFoundException;
import java.lang.reflect.InvocationTargetException;
public class App {
public static void main(String[] args) throws DocumentException, FileNotFoundException, ClassNotFoundException, InvocationTargetException, NoSuchMethodException, InstantiationException, IllegalAccessException {
// 获取解析容器
SpringApplicationContext springApplicationContext = new SpringApplicationContext("beans.xml");
// 取出对应Bean,
User user = (User)springApplicationContext.getBean("user");
// 输出Bean
System.out.println(user);
}
}
运行该启动类后,可以在控制台看到
?说明我们的项目已经成功创建了 User 的实例对象并返回,到此,学习结束
后言
“公道时间唯白发,贵人头上不曾饶”
|