实现SpringIoC容器的原理:工厂模式+XML分析+反射机制。
我们把自己的框架命名为:myspring(春天)
1.第一步:创建模块myspring62Modulen采用Maven新建:myspring
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.第二步:准备我们要管理的Bean62准备好我们要管理的Bean(这些Bean将在未来开发框架后删除)
注意包名,不要使用org.myspringframework包,因为这些bean不是内置框架。它是由未来使用我们框架的程序员提供的。
package com.powernode.myspring.bean;///手写spring框架 62//普通beanpublic class User { private String name; private int age; public void setName(String name) { this.name = name; } public void setAge(int age) { this.age = age; } @Override public String toString() { return "User{" + "name='" + name + '\'' + ", age=" + age + '}'; }}
package com.powernode.myspring.bean;///手写spring框架 62//daopublic class UserDao { public void insert(){ System.out.println(mysql数据库正在保存用户信息); }}
package com.powernode.myspring.bean;///手写spring框架 62//servicepublic class UserService { private UserDao userDao; public void setUserDao(UserDao userDao) { this.userDao = userDao; } public void save(){ userDao.insert(); }}
3.第三步:准备myspring.62xml配置文件未来框架开发完成后,该文件也将被删除。因为该配置文件的提供者应该是使用该框架的程序员。
随意命名文件,我们在这里称之为:myspring.xml
文件可以放在类路径中,我们可以把文件放在类的根路径下。
<?xml version="1.0" encoding="UTF-8"?><!--该配置文件由使用myspring框架的开发人员提供 62--><beans> <bean id="user" class="com.powernode.myspring.bean.User"> <property name="name" vlaue="张三"></property> <property name="age" vlaue="30"></property> </bean> <bean id="userService" class="com.powernode.myspring.bean.UserService"> <property name="userDao" ref="userDaoBean"/> </bean> <bean id="userDaoBean" class="com.powernode.myspring.bean.UserDao"></bean></beans>
使用value赋值简单属性。使用ref赋值非简单属性。
4.第四步:编写Applicationcontext接口633在Applicationcontext接口中提供一种getbean()方法,通过这种方法可以获得bean对象。
注意包名:这个接口是myspring框架的一员。
package org.myspringframework.core;/** * 手写spring框架 63 * 上下文接口应用于MySpring框架。 63 **/public interface ApplicationContext { /** * 根据bean的名字获取相应的bean对象。 * @param beanName bean标签id在myspring配置文件中。 * @return 单例bean对象对应。 */ Object getBean(String beanName);}
5.第五步:编写ClaspathXmlaplicationcontext633classpathXmlaplicationcontext是aplicationconcontext接口的实现类。这种从类路径中加载myspring.xml配置文件。
package org.myspringframework.core;public class ClassPathXmlApplicationContext implements ApplicationContext{ @Override public Object getBean(String beanId) { return null; }}
6.第六步:确定使用Map集存储Bean633确定使用Map集合存储Bean实例。Map集合的key存储beanID,value存储bean实例。Map
Map属性添加到ClasspathXmlaplicationcontext类中。
并在ClaspathXmlaplicationcontext类中添加结构方法,该结构方法的参数接收myspring.xml文件。
同时实现getbean方法。
package org.myspringframework.core;import java.util.HashMap;import java.util.Map;public class ClassPathXmlApplicationContext implements ApplicationContext{ /** * 存储bean的Map集合 */ private Map beanMap = new HashMap<>(); /** * 在这种结构方法中,分析myspring.创建所有Bean实例的xml文件,并将Bean实例存储在Map集合中。 * @param resource 配置文件路径(类别路径中需要) */ public ClassPathXmlApplicationContext(String resource) { } @Override public Object getBean(String beanId) { return beanMap.get(beanId); }}
7.第七步:分析所有Bean644的配置文件实例化在ClaspathXmlaplicationcontext的结构方法中分析配置文件,获取所有bean的类名,并通过反射机制调用无参数结构方法创建bean。并将bean对象存储在Map集合中
/*** 在这种结构方法中,分析myspring.创建所有Bean实例的xml文件,并将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"); // 遍历集合 beanNodes.forEach(beanNode -> { Element beanElt = (Element) beanNode; // 获取id String id = beanElt.attributeValue("id"); // clasname获得clasname String 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(); }}
8.第八步:测试能否获得Bean64编制测试程序。
//测试publicc class MySpringTest { @Test public void testMySpring(){ ApplicationContext applicationContext = new ClassPathXmlApplicationContext("myspring.xml"); }}
9.第九步:给Bean属性赋值65-67通过反射机制调用set法赋值Bean属性。
继续在ClaspathXmlaplicationcontext结构方法中编写代码。
// 所有的bean标签再次遍历,这次主要是赋值对象的属性。 65 nodes.forEach(node -> { try { Element beanElt = (Element) node; // 获取id String id = beanElt.attributeValue("id"); // clasname获得clasname String className = beanElt.attributeValue("class"); // 获取Class Class<?> aClass = Class.forName(className); // 在bean标签下获得所有属性property标签 List<Element> propertys = beanElt.elements("property"); // 遍历所有属性标签 propertys.forEach(property -> { try { // 获取属性名 String propertyName = property.attributeValue("name"); // 获取属性类型 Field field = aClass.getDeclaredField(propertyName); logger.info(属性名:" + propertyName); // 获取set方法名 String setMethodName = "set" + propertyName.toUpperCase().charAt(0) + propertyName.substring(1); // 获取set方法 Method setMethod = aClass.getDeclaredMethod(setMethodName, field.getType()); // 获取具体值 67 String value = property.attributeValue("value"); // "30" Object actualValue = null; // 真值 String ref = property.attributeValue("ref"); if (value != null) { // 说明这个值是简单的类型 // 调用set方法(set方法没有返回值) // 让我们声明一下myspring框架:我们只支持这些类型作为简单类型 // byte short int long float double boolean char // Byte Short Integer Long Float Double Boolean Character // String // 获取属性类型名称 String propertyTypeSimpleName = field.getType().getSimpleName(); switch (propertyTypeSimpleName) { case "byte": actualValue = Byte.parseByte(value); break; case "short": actualValue = Short.parseShort(value); break; case "int": actualValue = Integer.parseInt(value); break; case "long": actualValue = Long.parseLong(value); break; case "float": actualValue = Float.parseFloat(value); break; case "double": actualValue = Double.parseDouble(value); break; case "boolean": actualValue = Boolean.parseBoolean(value); break; case "char": actualValue = value.charAt(0); break; case "Byte": actualValue = Byte.valueOf(value); break; case "Short": actualValue = Short.valueOf(value); break; case "Integer": actualValue = Integer.valueOf(value); break; case "Long": actualValue = Long.valueOf(value); break; case "Float": actualValue = Float.valueOf(value); break; case "Double": actualValue = Double.valueOf(value); break; case "Boolean": actualValue = Boolean.valueOf(value); break; case "Character": actualValue = Character.valueOf(value.charAt(0)); break; case "String": actualValue = value; } setMethod.invoke(singletonObjects.get(id), actualValue); } if (ref != null) { // 说明这个值是非简单的类型 66 // 调用set方法(set方法没有返回值) setMethod.invoke(singletonObjects.get(id), singletonObjects.get(ref)); } } catch (Exception e) { e.printStackTrace(); } }); } catch (Exception e) { e.printStackTrace(); } });
10.第十步:测试68//测试publicc class MySpringTest { @Test public void testMySpring(){ ApplicationContext applicationContext = new ClassPathXmlApplicationContext("myspring.xml"); Object user = applicationContext.getBean("user"); System.out.println(user); UserService userService = (UserService) applicationContext.getBean("userService"); userService.save(); }}
11.第十一步:688包装发布删除多余的类别和配置文件,用maven打包发布。
12.代码汇总org.myspringframework.coreApplicationContextpackage org.myspringframework.core;/** * 手写spring框架 63 * 上下文接口应用于MySpring框架。 63 **/public interface ApplicationContext { /** * 根据bean的名字获取相应的bean对象。 * @param beanName bean标签id在myspring配置文件中。 * @return 单例bean对象对应。 */ Object getBean(String beanName);}
ClassPathXmlApplicationContextpackage org.myspringframework.core;import java.io.InputStream;import java.lang.reflect.Constructor;import java.lang.reflect.Field;import java.lang.reflect.Method;import java.util.HashMap;import java.util.List;import java.util.Map;import org.dom4j.Document;import org.dom4j.Element;import org.dom4j.Node;import org.dom4j.io.SAXReader;import org.slf4j.Logger;import org.slf4j.LoggerFactory;/** * 手写spring框架 63 * ClaspathXmlaplicationcontext是aplicationcontext接口实现的类别 63 **/public class ClassPathXmlApplicationContext implements ApplicationContext{ private static final Logger logger = LoggerFactory.getLogger(ClassPathXmlApplicationContext.class); private Map<String, Object> singletonObjects = new HashMap<>(); /** * 对myspring的配置文件进行分析,然后对所有bean对象进行初始化。 63 * @param configLocation spring配置文件的路径。 63 * @param configLocation spring配置文件的路径。注:使用claspathXmlaplicationcontext,配置文件应放在类别路径下。 */ public ClassPathXmlApplicationContext(String configLocation) { try { // 64 // 分析myspring.xml文件,然后实例化Bean,将Bean存储在singletonobjects集合中。 // 这是dom4jXML文件分析的核心对象。 SAXReader reader = new SAXReader(); // 获取输入流,指向配置文件 ///Classloader获取类加载器,getsystemclasloder获取系统类加载器 ///getresourceasstream以流的形式获取系统资源 InputStream in = ClassLoader.getSystemClassLoader().getResourceAsStream(configLocation); // 读文件 Document document = reader.read(in); // 获取所有bean标签 List<Node> nodes = document.selectNodes("//bean"); // bean标签遍历 nodes.forEach(node -> { try { // 向下转型的目的是在Element界面中使用更丰富的方法。 Element beanElt = (Element) node; // 获取id属性 String id = beanElt.attributeValue("id"); // 获取class属性 String className = beanElt.attributeValue("class"); logger.info("beanName=" + id); logger.info("beanClassName="+className); // 通过反射机制创建对象,将其放入Map集中,提前曝光。 // 获取Class Class<?> aClass = Class.forName(className); // 获取无参数结构的方法 Constructor<?> defaultCon = aClass.getDeclaredConstructor(); // 实例Bean调用无参数结构法 Object bean = defaultCon.newInstance(); // 曝光Bean,加入Map集合 singletonObjects.put(id, bean); // 记录日志 logger.info(singletonObjects.toString()); } catch (Exception e) { e.printStackTrace(); } }); // 再次遍历所有的bean标签,这一次主要是赋值对象的属性。 65 nodes.forEach(node -> { try { Element beanElt = (Element) node; // 获取id String id = beanElt.attributeValue("id"); // clasname获得clasname String className = beanElt.attributeValue("class"); // 获取Class Class<?> aClass = Class.forName(className); // 在bean标签下获得所有属性property标签 List<Element> propertys = beanElt.elements("property"); // 遍历所有属性标签 propertys.forEach(property -> { try { // 获取属性名 String propertyName = property.attributeValue("name"); // 获取属性类型 Field field = aClass.getDeclaredField(propertyName); logger.info(属性名:" + propertyName); // 获取set方法名 String setMethodName = "set" + propertyName.toUpperCase().charAt(0) + propertyName.substring(1); // 获取set方法 Method setMethod = aClass.getDeclaredMethod(setMethodName, field.getType()); // 获取具体值 67 String value = property.attributeValue("value"); // "30" Object actualValue = null; // 真值 String ref = property.attributeValue("ref"); if (value != null) { // 说明这个值是简单的类型 // 调用set方法(set方法没有返回值) // 让我们声明一下myspring框架:我们只支持这些类型作为简单类型 // byte short int long float double boolean char // Byte Short Integer Long Float Double Boolean Character // String // 获取属性类型名称 String propertyTypeSimpleName = field.getType().getSimpleName(); switch (propertyTypeSimpleName) { case "byte": actualValue = Byte.parseByte(value); break; case "short": actualValue = Short.parseShort(value); break; case "int": actualValue = Integer.parseInt(value); break; case "long": actualValue = Long.parseLong(value); break; case "float": actualValue = Float.parseFloat(value); break; case "double": actualValue = Double.parseDouble(value); break; case "boolean": actualValue = Boolean.parseBoolean(value); break; case "char": actualValue = value.charAt(0); break; case "Byte": actualValue = Byte.valueOf(value); break; case "Short": actualValue = Short.valueOf(value); break; case "Integer": actualValue = Integer.valueOf(value); break; case "Long": actualValue = Long.valueOf(value); break; case "Float": actualValue = Float.valueOf(value); break; case "Double": actualValue = Double.valueOf(value); break; case "Boolean": actualValue = Boolean.valueOf(value); break; case "Character": actualValue = Character.valueOf(value.charAt(0)); break; case "String": actualValue = value; } setMethod.invoke(singletonObjects.get(id), actualValue); } if (ref != null) { // 说明这个值是非简单的类型 66 // 调用set方法(set方法没有返回值) setMethod.invoke(singletonObjects.get(id), singletonObjects.get(ref)); } } catch (Exception e) { e.printStackTrace(); } }); } catch (Exception e) { e.printStackTrace(); } }); } catch (Exception e) { e.printStackTrace(); } } @Override public Object getBean(String beanName) { return singletonObjects.get(beanName); }}
pom.xml<?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>course15</artifactId> <version>1.0.0</version><!-- 打包方式 62--> <packaging>jar</packaging><!-- 打包方式 62--> <packaging>jar</packaging><!-- 依赖--> <dependencies><!-- dom4j是一个java组件,可以分析xml文件--> <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> <!--依赖log4j2--> <dependency> <groupId>org.apache.logging.log4j</groupId> <artifactId>log4j-core</artifactId> <version>2.19.0</version> </dependency> <dependency> <groupId>org.apache.logging.log4j</groupId> <artifactId>log4j-slf4j2-impl</artifactId> <version>2.19.0</version> </dependency> </dependencies> <properties> <maven.compiler.source>17</maven.compiler.source> <maven.compiler.target>17</maven.compiler.target> </properties></project>