[SSM]手写Spring框架

目录

十一、手写Spring框架

第一步:创建模块myspring

第二步:准备好要管理的Bean

第三步:准备myspring.xml配置文件

第四步:核心接口实现

第五步:实例化Bean

第六步:给Bean属性赋值

第七步:测试

第八步:打包发布

第十一步:使用myspring框架


十一、手写Spring框架

  • Spring IoC容器的实现原理:工厂模式+解析XML+反射机制。

第一步:创建模块myspring

配置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>myspring</artifactId>
    <version>1.0.0</version>
    <packaging>jar</packaging>
​
    <dependencies>
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.13.2</version>
            <scope>test</scope>
        </dependency>
        <!--使用dom4j解析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>
    </dependencies>
​
    <properties>
        <maven.compiler.source>20</maven.compiler.source>
        <maven.compiler.target>20</maven.compiler.target>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    </properties>
​
</project>

第二步:准备好要管理的Bean

  • 这些Bean在将来开发完框架之后是要删除的。

User

复制代码
package com.hhb.myspring.bean;
​
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;
    }
​
    @Override
    public String toString() {
        return "User{" +
                "name='" + name + '\'' +
                ", age=" + age +
                '}';
    }
}

UserDao

复制代码
package com.hhb.myspring.bean;
​
public class UserDao {
    public void insert() {
        System.out.println("保存用户信息");
    }
}

UserService

复制代码
package com.hhb.myspring.bean;
​
public class UserService {
    private UserDao userDao;
​
    public void setUserDao(UserDao userDao) {
        this.userDao = userDao;
    }
​
    public void save() {
        userDao.insert();
    }
}

第三步:准备myspring.xml配置文件

  • 将来在框架开发完毕之后,这个文件也是要删除的。

  • 文件放在类的根路径下。

myspring.xml

复制代码
<?xml version="1.0" encoding="UTF-8"?>
<beans>
​
    <bean id="user" class="com.hhb.myspring.bean.User">
        <property name="name" value="张三"/>
        <property name="age" value="23"/>
    </bean>
​
    <bean id="userDaoBean" class="com.hhb.myspring.bean.UserDao"/>
​
    <bean id="userService" class="com.hhb.myspring.bean.UserService">
        <property name="userDao" ref="userDaoBean"/>
    </bean>
</beans>
  • 使用value给简单属性赋值,使用ref给非简单属性赋值。

第四步:核心接口实现

ApplicationContext

复制代码
package org.myspringframework.core;
​
/**
 * MySpring框架应用上下文接口。
 */
​
public interface ApplicationContext {
    /**
     * 根据bean的名称获取对象的bean对象
     *
     * @param beanName myspring配置文件中bean标签的id
     * @return 对应的单例bean对象
     */
    Object getBean(String beanName);
}

ClassPathXmlApplicationContext

复制代码
package org.myspringframework.core;
​
import java.util.HashMap;
import java.util.Map;
​
public class ClassPathXmlApplicationContext implements ApplicationContext {
​
    private Map<String, Object> singletonObjects = new HashMap<>();
​
    /**
     * 解析myspring的配置文件,然后初始化所有的Bean对象
     *
     * @param configLocation spring配置文件的路径
     */
    public ClassPathXmlApplicationContext(String configLocation) {
        //解析myspring.xml文件,然后实例化Bean,将Bean存放到singletonObjects集合当中
​
    }
​
    @Override
    public Object getBean(String beanName) {
        return singletonObjects.get(beanName);
    }
}

第五步:实例化Bean

ClassPathXmlApplicationContext

复制代码
package org.myspringframework.core;
​
import org.dom4j.Document;
import org.dom4j.DocumentException;
import org.dom4j.Element;
import org.dom4j.Node;
import org.dom4j.io.SAXReader;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
​
import java.io.InputStream;
import java.lang.reflect.Constructor;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
​
public class ClassPathXmlApplicationContext implements ApplicationContext {
​
    private static final Logger logger = LoggerFactory.getLogger(ClassPathXmlApplicationContext.class);
​
    private Map<String, Object> singletonObjects = new HashMap<>();
​
    /**
     * 解析myspring的配置文件,然后初始化所有的Bean对象
     *
     * @param configLocation spring配置文件的路径
     */
    public ClassPathXmlApplicationContext(String configLocation) {
        try {
            // 解析myspring.xml文件,然后实例化Bean,将Bean存放到singletonObjects集合当中。
            // 这是dom4j解析XML文件的核心对象。
            SAXReader reader = new SAXReader();
            // 获取一个输入流,指向配置文件
            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();
                }
            });
        } catch (DocumentException e) {
            e.printStackTrace();
        }
    }
​
    @Override
    public Object getBean(String beanName) {
        return singletonObjects.get(beanName);
    }
}

第六步:给Bean属性赋值

复制代码
package 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;
​
/**
 * @author 动力节点
 * @version 1.0
 * @className ClassPathXmlApplicationContext
 * @since 1.0
 **/
public class ClassPathXmlApplicationContext implements ApplicationContext{
​
    private static final Logger logger = LoggerFactory.getLogger(ClassPathXmlApplicationContext.class);
​
    private Map<String, Object> singletonObjects = new HashMap<>();
​
    /**
     * 解析myspring的配置文件,然后初始化所有的Bean对象。
     * @param configLocation spring配置文件的路径。注意:使用ClassPathXmlApplicationContext,配置文件应当放到类路径下。
     */
    public ClassPathXmlApplicationContext(String configLocation) {
        try {
            // 解析myspring.xml文件,然后实例化Bean,将Bean存放到singletonObjects集合当中。
            // 这是dom4j解析XML文件的核心对象。
            SAXReader reader = new SAXReader();
            // 获取一个输入流,指向配置文件
            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标签遍历一次,这一次主要是给对象的属性赋值。
            nodes.forEach(node -> {
                try {
                    Element beanElt = (Element) node;
                    // 获取id
                    String id = beanElt.attributeValue("id");
                    // 获取className
                    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());
                            // 获取具体的值
                            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) {
                                // 说明这个值是非简单类型
                                // 调用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);
    }
}

第七步:测试

复制代码
@Test
public void test1(){
    ApplicationContext applicationContext=new ClassPathXmlApplicationContext("myspring.xml");
    Object user = applicationContext.getBean("user");
    System.out.println(user);
​
    UserService userService = (UserService) applicationContext.getBean("userService");
    userService.save();
}

第八步:打包发布

第十一步:使用myspring框架

配置pom.xml

复制代码
<dependencies>
    <!--用myspring框架,需要引入依赖-->
    <dependency>
        <groupId>org.myspringframework</groupId>
        <artifactId>myspring</artifactId>
        <version>1.0.0</version>
    </dependency>
    <dependency>
        <groupId>junit</groupId>
        <artifactId>junit</artifactId>
        <version>4.13.2</version>
        <scope>test</scope>
    </dependency>
</dependencies>
相关推荐
无巧不成书021817 分钟前
30分钟入门Java:从历史到Hello World的小白指南
java·开发语言
苍何1 小时前
30分钟用 Agent 搓出一家跨境网店,疯了
后端
ssshooter1 小时前
Tauri 2 iOS 开发避坑指南:文件保存、Dialog 和 Documents 目录的那些坑
前端·后端·ios
追逐时光者1 小时前
一个基于 .NET Core + Vue3 构建的开源全栈平台 Admin 系统
后端·.net
程序员飞哥1 小时前
90后大龄程序员失业4个月终于上岸了
后端·面试·程序员
zs宝来了2 小时前
Playwright 自动发布 CSDN 的完整实践
java
吴声子夜歌3 小时前
TypeScript——基础类型(三)
java·linux·typescript
GetcharZp3 小时前
Git 命令行太痛苦?这款 75k Star 的神级工具,让你告别“合并冲突”恐惧症!
后端
Victor3564 小时前
MongoDB(69)如何进行增量备份?
后端