手写Spring框架
1. 打包方式采用jar,并且引入dom4j和jaxen的依赖,因为要使用它解析XML文件,还有junit依赖。
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"><modelVersion>4.0.0</modelVersion><groupId>org.myspringframework</groupId><artifactId>myspring</artifactId><version>1.0.0</version><packaging>jar</packaging><dependencies><dependency><groupId>org.dom4j</groupId><artifactId>dom4j</artifactId><version>2.1.3</version></dependency><dependency><groupId>jaxen</groupId><artifactId>jaxen</artifactId><version>1.2.0</version></dependency><dependency><groupId>junit</groupId><artifactId>junit</artifactId><version>4.13.2</version><scope>test</scope></dependency></dependencies><properties><maven.compiler.source>17</maven.compiler.source><maven.compiler.target>17</maven.compiler.target></properties></project>
2. User相关类
public class User {private String name;private int age;public void setName(String name) {this.name = name;}public void setAge(int age) {this.age = age;}@Overridepublic String toString() {return "User{" +"name='" + name + '\'' +", age=" + age +'}';}
}public class UserDao {public void insert(){System.out.println("mysql数据库正在保存用户信息");}
}public class UserService {private UserDao userDao;public void setUserDao(UserDao userDao) {this.userDao = userDao;}public void save(){userDao.insert();}}
3. 准备myspring.xml配置文件
<?xml version="1.0" encoding="UTF-8" ?><beans><bean id="user" class="com.hnlg.myspring.bean.User"><property name="name" value="张三"/><property name="age" value="30"/></bean><bean id="userService" class="com.hnlg.myspring.bean.UserService"><property name="userDao" ref="userDaoBean"/></bean><bean id="userDaoBean" class="com.hnlg.myspring.bean.UserDao"/>
</beans>
4. 日志文件log4j2.xml
<?xml version="1.0" encoding="UTF-8"?><configuration><appenders><!--输出日志信息到控制台--><console name="spring6log" target="SYSTEM_OUT"><!--控制日志输出的格式--><PatternLayout pattern="%d{yyyy-MM-dd HH:mm:ss SSS} [%t] %-3level %logger{1024} - %msg%n"/></console></appenders><loggers><!--level指定日志级别,从低到高的优先级:(级别越高,输出信息越精简)ALL < TRACE < DEBUG < INFO < WARN < ERROR < FATAL < OFF--><root level="INFO"><appender-ref ref="spring6log"/></root></loggers>
</configuration>
5. 编写ApplicationContext接口
ApplicationContext接口中提供一个getBean()方法,通过该方法可以获取Bean对象。
public interface ApplicationContext {/*** 根据bean的名称获取对应的bean对象。* @param beanName myspring配置文件中bean标签的id。* @return 对应的单例bean对象。*/Object getBean(String beanName);}
6. 编写ClassPathXmlApplicationContext
public class ClassPathXmlApplicationContext implements ApplicationContext{@Overridepublic Object getBean(String beanId) {return null;}
}
7. 采用Map集合存储Bean
采用Map集合存储Bean实例。Map集合的key存储bean Id,value存储Bean实例。
Map<String,Object>在ClassPathXmlApplicationContext类中添加Map<String,Object>属性。
在ClassPathXmlApplicationContext类中添加构造方法,该构造方法的参数接收myspring.xml文件。同时实现getBean()方法。
public class ClassPathXmlApplicationContext implements ApplicationContext{/*** 存储bean的Map集合*/private Map<String,Object> beanMap = new HashMap<>();/*** 在该构造方法中,解析myspring.xml文件,创建所有的Bean实例,并将Bean实例存放到Map集合中。* @param resource 配置文件路径(要求在类路径当中)*/public ClassPathXmlApplicationContext(String resource) {}@Overridepublic Object getBean(String beanId) {return beanMap.get(beanId);}
}
8. 解析配置文件实例化所有Bean
在ClassPathXmlApplicationContext的构造方法中解析配置文件,获取所有bean的类名,通过反射机制调用无参数构造方法创建Bean。并且将Bean对象存放到Map集合中。
public ClassPathXmlApplicationContext(String resource) {try {SAXReader reader = new SAXReader();Document document = reader.read(ClassLoader.getSystemClassLoader().getResourceAsStream(resource));// 获取所有的bean标签List<Node> beanNodes = document.selectNodes("//bean");// 遍历集合beanNodes.forEach(beanNode -> {Element beanElt = (Element) beanNode;// 获取idString id = beanElt.attributeValue("id");// 获取classNameString className = beanElt.attributeValue("class");try {// 通过反射机制创建对象Class<?> clazz = Class.forName(className);Constructor<?> defaultConstructor = clazz.getDeclaredConstructor();Object bean = defaultConstructor.newInstance();// 存储到Map集合beanMap.put(id, bean);} catch (Exception e) {e.printStackTrace();}});} catch (Exception e) {e.printStackTrace();}
}
9. 给Bean的属性赋值
通过反射机制调用set方法,给Bean的属性赋值。
继续在ClassPathXmlApplicationContext构造方法中编写代码。
public class ClassPathXmlApplicationContext implements ApplicationContext{/*** 存储bean的Map集合*/private Map<String,Object> beanMap = new HashMap<>();/*** 在该构造方法中,解析myspring.xml文件,创建所有的Bean实例,并将Bean实例存放到Map集合中。* @param resource 配置文件路径(要求在类路径当中)*/public ClassPathXmlApplicationContext(String resource) {try {SAXReader reader = new SAXReader();Document document = reader.read(ClassLoader.getSystemClassLoader().getResourceAsStream(resource));// 获取所有的bean标签List<Node> beanNodes = document.selectNodes("//bean");// 遍历集合(这里的遍历只实例化Bean,不给属性赋值。为什么要这样做?)beanNodes.forEach(beanNode -> {Element beanElt = (Element) beanNode;// 获取idString id = beanElt.attributeValue("id");// 获取classNameString className = beanElt.attributeValue("class");try {// 通过反射机制创建对象Class<?> clazz = Class.forName(className);Constructor<?> defaultConstructor = clazz.getDeclaredConstructor();Object bean = defaultConstructor.newInstance();// 存储到Map集合beanMap.put(id, bean);} catch (Exception e) {e.printStackTrace();}});// 再重新遍历集合,这次遍历是为了给Bean的所有属性赋值。// 思考:为什么不在上面的循环中给Bean的属性赋值,而在这里再重新遍历一次呢?// 通过这里你是否能够想到Spring是如何解决循环依赖的:实例化和属性赋值分开。beanNodes.forEach(beanNode -> {Element beanElt = (Element) beanNode;// 获取bean的idString beanId = beanElt.attributeValue("id");// 获取所有property标签List<Element> propertyElts = beanElt.elements("property");// 遍历所有属性propertyElts.forEach(propertyElt -> {try {// 获取属性名String propertyName = propertyElt.attributeValue("name");// 获取属性类型Class<?> propertyType = beanMap.get(beanId).getClass().getDeclaredField(propertyName).getType();// 获取set方法名String setMethodName = "set" + propertyName.toUpperCase().charAt(0) + propertyName.substring(1);// 获取set方法Method setMethod = beanMap.get(beanId).getClass().getDeclaredMethod(setMethodName, propertyType);// 获取属性的值,值可能是value,也可能是ref。// 获取valueString propertyValue = propertyElt.attributeValue("value");// 获取refString propertyRef = propertyElt.attributeValue("ref");Object propertyVal = null;if (propertyValue != null) {// 该属性是简单属性String propertyTypeSimpleName = propertyType.getSimpleName();switch (propertyTypeSimpleName) {case "byte": case "Byte":propertyVal = Byte.valueOf(propertyValue);break;case "short": case "Short":propertyVal = Short.valueOf(propertyValue);break;case "int": case "Integer":propertyVal = Integer.valueOf(propertyValue);break;case "long": case "Long":propertyVal = Long.valueOf(propertyValue);break;case "float": case "Float":propertyVal = Float.valueOf(propertyValue);break;case "double": case "Double":propertyVal = Double.valueOf(propertyValue);break;case "boolean": case "Boolean":propertyVal = Boolean.valueOf(propertyValue);break;case "char": case "Character":propertyVal = propertyValue.charAt(0);break;case "String":propertyVal = propertyValue;break;}setMethod.invoke(beanMap.get(beanId), propertyVal);}if (propertyRef != null) {// 该属性不是简单属性setMethod.invoke(beanMap.get(beanId), beanMap.get(propertyRef));}} catch (Exception e) {e.printStackTrace();}});});} catch (Exception e) {e.printStackTrace();}}@Overridepublic Object getBean(String beanId) {return beanMap.get(beanId);}
}