【Spring】手写 Spring 框架 IoC

简介: 【Spring】手写 Spring 框架 IoC

根据 【动力节点】最新Spring框架教程,全网首套Spring6教程,跟老杜从零学spring入门到高级 以及老杜的原版笔记 https://www.yuque.com/docs/share/866abad4-7106-45e7-afcd-245a733b073f?# 《Spring6》 进行整理, 文档密码:mg9b


Spring 相关文章整理汇总归纳于:https://www.yuque.com/u27599042/zuisie


回顾反射机制

调用方法的四要素

  • 调用方法的四要素
  • 第一要素:使用哪个对象进行方法的调用
  • 第二要素:调用对象的哪个方法
  • 第三要素:调用方法的时候,需要传递什么参数
  • 第四要素:方法执行结束之后的返回结果
  • 总结:调用哪个对象的哪个方法,需要传递什么参数,该方法返回什么值
  • 即使是使用反射机制进行方法的调用,也同样需要具备着四要素

反射机制调用方法

/**
 * ClassName: SomeService
 * Package: PACKAGE_NAME
 * Description:
 *
 * @Author tcw
 * @Create 2023-05-27 10:46
 * @Version 1.0
 */
public class SomeService {
    public void doSome() {
        System.out.println(" public void doSome() 执行");
    }
    public String doSome(String s) {
        System.out.println(" public void doSome(String s) 执行");
        return s;
    }
    public String doSome(String s, Integer i) {
        System.out.println(" public void doSome(String s, Integer i) 执行");
        return s + " " + i;
    }
}
import java.lang.reflect.Method;
/**
 * ClassName: Main
 * Package: PACKAGE_NAME
 * Description:
 *
 * @Author tcw
 * @Create 2023-05-27 10:45
 * @Version 1.0
 */
public class Main {
    public static void main(String[] args) throws Exception {
        // 加载类,获取类对应的Class对象
        Class<?> someServiceClass = Class.forName("SomeService");
        // 创建对象
        // 调用类的无参构造方法创建对象
        SomeService someService = (SomeService) someServiceClass.getDeclaredConstructor().newInstance();
        // 获取方法 public String doSome(String s, Integer i)
        // 参数一:获取方法的方法名
        // 参数二:可变参数,获取方法的形参列表类型
        Method doSomeMethod = someServiceClass.getDeclaredMethod("doSome", String.class, Integer.class);
        // 调用方法(四要素:调用哪个对象的哪个方法,需要传递什么参数,该方法返回什么值)
        // 参数一:调用哪个对象的该方法  someService
        // 参数二:可变参数,调用方法需要传递的形参  "Hello World", 111
        // invoke 的返回值就是调用方法的返回值  Object res
        Object res = doSomeMethod.invoke(someService, "Hello World", 111);
        System.out.println(res);
    }
}

Spring DI 核心实现

  • 需求:假设你现在已知以下信息:
  • 1.有这样一个类,类名叫做:包名.User
  • 2.这个类符合javabean规范。属性私有化,对外提供公开的setter和getter方法。
  • 3.你还知道这个类当中有一个属性,属性的名字叫做 age
  • 4.并且你还知道age属性的类型是int类型。
  • 请使用反射机制调用set方法,给User对象的age属性赋值。
package cw.study.spring.pojo;
/**
 * ClassName: User
 * Package: cw.study.spring.pojo
 * Description:
 *
 * @Author tcw
 * @Create 2023-05-28 9:59
 * @Version 1.0
 */
public class User {
    private String name;
    private int age;
    public User() {
    }
    public User(String name, int age) {
        this.name = name;
        this.age = age;
    }
    @Override
    public String toString() {
        return "User{" + "name='" + name + '\'' + ", age=" + age + '}';
    }
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public int getAge() {
        return age;
    }
    public void setAge(int age) {
        this.age = age;
    }
}
import cw.study.spring.pojo.User;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
/**
 * ClassName: Main
 * Package: PACKAGE_NAME
 * Description:
 *
 * @Author tcw
 * @Create 2023-05-27 10:45
 * @Version 1.0
 */
public class Main {
    public static void main(String[] args) throws Exception {
        // 1.有这样一个类,类名叫做:包名.User
        String className = "cw.study.spring.pojo.User";
        // 2.这个类符合javabean规范。属性私有化,对外提供公开的setter和getter方法。
        // 3.你还知道这个类当中有一个属性,属性的名字叫做 age
        String fieldName = "age";
        // 4.并且你还知道age属性的类型是int类型。
        // 请使用反射机制调用set方法,给User对象的age属性赋值。
        // 加载类
        Class<?> userClass = Class.forName(className);
        // 创建对象
        User user = (User) userClass.getDeclaredConstructor().newInstance();
        // 获取属性
        Field field = userClass.getDeclaredField(fieldName);
        // 获取属性的类型
        Class<?> fieldType = field.getType();
        // 获取方法名
        String methodName = "set" + fieldName.toUpperCase().charAt(0) + fieldName.substring(1);
        // 获取方法
        Method method = userClass.getDeclaredMethod(methodName, fieldType);
        // 调用方法
        method.invoke(user, 22);
        System.out.println(user);
    }
}

手写 Spring 框架 IoC

  • Spring IoC 的实现原理:工厂模式 + 解析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>cw.study.spring</groupId>
  <artifactId>spring-study-011-myspring</artifactId>
  <version>1.0-SNAPSHOT</version>
  <packaging>jar</packaging>
  <dependencies>
    <!-- dom4j 解析XML文件 -->
    <dependency>
      <groupId>org.dom4j</groupId>
      <artifactId>dom4j</artifactId>
      <version>2.1.3</version>
    </dependency>
    <!-- jaxen 需要使用xpath -->
    <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>

JavaBean

  • 这些JavaBean是使用框架的用户写的
package cw.study.spring.bean;
/**
 * ClassName: User
 * Package: cw.study.spring.bean
 * Description:
 *
 * @Author tcw
 * @Create 2023-05-31 12:06
 * @Version 1.0
 */
public class User {
    private String name;
    private Integer age;
    public User() {
    }
    public void setName(String name) {
        this.name = name;
    }
    public void setAge(Integer age) {
        this.age = age;
    }
    @Override
    public String toString() {
        return "User{" + "name='" + name + '\'' + ", age=" + age + '}';
    }
}
package cw.study.spring.bean;
/**
 * ClassName: UserDao
 * Package: cw.study.spring.bean
 * Description:
 *
 * @Author tcw
 * @Create 2023-05-31 12:07
 * @Version 1.0
 */
public class UserDao {
    public void insert() {
        System.out.println("数据库正在保存用户信息...");
    }
}
package cw.study.spring.bean;
/**
 * ClassName: UserService
 * Package: cw.study.spring.bean
 * Description:
 *
 * @Author tcw
 * @Create 2023-05-31 14:08
 * @Version 1.0
 */
public class UserService {
    private UserDao userDao;
    public void save() {
        userDao.insert();
    }
    public void setUserDao(UserDao userDao) {
        this.userDao = userDao;
    }
}

配置文件

  • 这个配置文件是使用框架的用户写的
<?xml version="1.0" encoding="UTF-8" ?>
<beans>
  <bean id="user" class="cw.study.spring.bean.User">
    <property name="name" value="张三"/>
    <property name="age" value="21"/>
  </bean>
  <bean id="userDao" class="cw.study.spring.bean.UserDao"/>
  <bean id="userService" class="cw.study.spring.bean.UserService">
    <property name="userDao" ref="userDao"/>
  </bean>
</beans>

Spring 框架核心接口实现 ApplicationContext

package org.myspringframework.core;
/**
 * ClassName: ApplicationContext
 * Package: org.myspringframework.core
 * Description:
 * MySpring框架应用上下文接口
 *
 * @Author tcw
 * @Create 2023-05-31 14:22
 * @Version 1.0
 */
public interface ApplicationContext {
    /**
     * 通过Bean的id从容器中获取相应的Bean对象
     *      Bean的名称就是配置文件中Bean的id
     *
     * @param beanName Bean的id
     * @return Bean对象
     */
    Object getBean(String beanName);
}

Spring 框架核心接口实现类 ClassPathXmlApplicationContext

package org.myspringframework.core;
import org.dom4j.*;
import org.dom4j.io.SAXReader;
import java.io.InputStream;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
 * ClassName: ClassPathXmlApplicationContext
 * Package: org.myspringframework.core
 * Description:
 *
 * @Author tcw
 * @Create 2023-05-31 14:25
 * @Version 1.0
 */
public class ClassPathXmlApplicationContext implements ApplicationContext{
    // 用于存放Bean对象的map集合,使用Bean的名称可以获取相应的Bean对象
    private Map<String, Object> singletonObjects = new HashMap<>();
    /**
     * 根据Spring配置文件创建Spring容器的构造方法
     * 该方法一执行就会对配置文件进行解析创建Bean对象
     * 使用ClassPathXmlApplicationContext,配置文件应该放在类路径下
     *
     * @param configLocation Spring配置文件的路径
     */
    public ClassPathXmlApplicationContext(String configLocation) {
        try {
            // 解析XML核心配置文件,实例化Bean,将Bean存放到map集合中
            // 获取dom4j解析xml文件的核心对象,读取xml文件中的内容
            SAXReader reader = new SAXReader();
            // 获取xml的输入流,通过类加载器以流的方式获取资源
            InputStream inputStream = ClassLoader.getSystemClassLoader().getResourceAsStream(configLocation);
            // 读取xml文件中的内容,获取xml对应的文档对象
            Document document = reader.read(inputStream);
            // 获取xml文件中所有的bean标签
            List<Node> beansNode = document.selectNodes("//bean");
            // 遍历所有的bean标签,创建相应的对象放到容器中
            beansNode.forEach(bean->{
                try {
                    // System.out.println(bean);
                    // org.dom4j.tree.DefaultElement@74ad1f1f
                    // bean实际上是Element类型,向下转型,因为Element中方法更丰富
                    Element beanElement = (Element) bean;
                    // 获取Bean id
                    String id = beanElement.attributeValue("id");
                    // 获取bean的类型
                    String className = beanElement.attributeValue("class");
                    // System.out.println(id + ": " + className);
                    // 利用反射机制进行对象的创建,并放到map集合中,进行提前曝光
                    Object beanInstance = createBeanObjectByReflect(className);
                    // 将创建的对象放入map集合中进行曝光
                    singletonObjects.put(id, beanInstance);
                    // System.out.println(singletonObjects);
                } catch (Exception e) {
                    e.printStackTrace();
                }
            });
            // 再次遍历所有的bean标签,为创建的所有bean对象进行属性的赋值
            beansNode.forEach(bean->{
                try {
                    // 进行强转
                    Element beanElement= (Element) bean;
                    // 获取bean的id
                    String id = beanElement.attributeValue("id");
                    // // 判断bean是否已经被创建了
                    // if (!singletonObjects.containsKey(id)) {
                    //     // 如果没有创建bean对象,则进行bean对象的创建
                    //     Object beanInstance = createBeanObjectByReflect(className);
                    // }
                    // 获取bean的类名
                    String className = beanElement.attributeValue("class");
                    // 加载类
                    Class<?> clazz = Class.forName(className);
                    // 获取bean标签中的所有子标签属性标签
                    List<Element> properties = beanElement.elements("property");
                    // 遍历bean标签中的所有子标签属性标签,为bean的属性进行赋值
                    properties.forEach(property->{
                        try {
                            // 获取property标签的name属性值,获取要为哪个属性赋值
                            String fieldName = property.attributeValue("name");
                            // 获取property标签的value属性值,获取要为属性赋的值(标签中如果无value,返回null)
                            String fieldValue = property.attributeValue("value");
                            // 获取property标签的ref属性值,获取要为属性赋的值(标签中如果无ref,返回null)
                            String ref = property.attributeValue("ref");
                            // System.out.println(fieldName + " = " + fieldValue);
                            // 获取set方法的方法名
                            String setMethodName = "set" + fieldName.toUpperCase().charAt(0) + fieldName.substring(1);
                            // 获取set方法对应的属性的类型
                            Class<?> fieldType = clazz.getDeclaredField(fieldName).getType();
                            // 获取set方法
                            Method setMethod = clazz.getDeclaredMethod(setMethodName, fieldType);
                            // 如果属性的值是通过value进行赋值
                            if (fieldValue != null) {
                                // 值为简单类型
                                // 调用id对应的bean对象的set方法(set方法无返回值)
                                // 从标签的属性中获取的值都为字符串类型,但是set方法需要的参数有具体的数据类型,需要进行处理
                                // 获取set方法参数的简单类型名
                                String fieldTypeSimpleName = fieldType.getSimpleName();
                                String propertyValue = fieldValue; // 简单类型的属性值
                                // 进行数据类型匹配,对参数进行类型转换
                                Object propertyVal = null;
                                switch (fieldTypeSimpleName) {
                                    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;
                                }
                                // 调用id对应的bean对象的set方法(set方法无返回值)
                                setMethod.invoke(singletonObjects.get(id), propertyVal);
                            }
                            // 如果属性的值是通过ref进行赋值
                            if (ref != null) {
                                // 值为非简单类型
                                // 调用id对应的bean对象的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();
        }
    }
    /**
     * 通过反射机制进行相应类的对象的创建
     *
     * @param className 类的全限定类名
     * @return 类的对象
     * @throws Exception 异常
     */
    private Object createBeanObjectByReflect(String className) throws Exception {
        // 加载类
        Class<?> clazz = Class.forName(className);
        // 获取类的无参构造器创建对象
        return clazz.getDeclaredConstructor().newInstance();
    }
    /**
     * 根据bean的id获取bean对象
     *
     * @param beanName Bean的id
     * @return bean对象
     */
    @Override
    public Object getBean(String beanName) {
        return singletonObjects.get(beanName);
    }
}

MySpring 框架测试

@org.junit.Test
public void test01() {
    ApplicationContext applicationContext = new ClassPathXmlApplicationContext("myspring.xml");
    UserService userService = (UserService) applicationContext.getBean("userService");
    userService.save();
    User user = (User) applicationContext.getBean("user");
    System.out.println(user);
}

相关文章
|
13天前
|
安全 Java Ruby
我尝试了所有后端框架 — — 这就是为什么只有 Spring Boot 幸存下来
作者回顾后端开发历程,指出多数框架在生产环境中难堪重负。相比之下,Spring Boot凭借内置安全、稳定扩展、完善生态和企业级支持,成为构建高可用系统的首选,真正经受住了时间与规模的考验。
103 2
|
3月前
|
Java Spring
聊聊你对SpringBoot框架的理解 ?
SpringBoot是Spring家族中流行的子项目,旨在简化Spring框架开发的繁琐配置。它主要提供三大功能:starter起步依赖简化依赖管理,自动配置根据条件创建Bean,以及内嵌Web服务器支持Jar包运行,极大提升了开发效率。
141 0
|
3月前
|
安全 Java 微服务
Java 最新技术和框架实操:涵盖 JDK 21 新特性与 Spring Security 6.x 安全框架搭建
本文系统整理了Java最新技术与主流框架实操内容,涵盖Java 17+新特性(如模式匹配、文本块、记录类)、Spring Boot 3微服务开发、响应式编程(WebFlux)、容器化部署(Docker+K8s)、测试与CI/CD实践,附完整代码示例和学习资源推荐,助你构建现代Java全栈开发能力。
402 0
|
3月前
|
NoSQL Java 数据库连接
SpringBoot框架
Spring Boot 是 Spring 家族中最流行的框架,旨在简化 Spring 应用的初始搭建与开发。它通过自动配置、起步依赖和内嵌服务器三大核心功能,大幅减少配置复杂度,提升开发效率。开发者可快速构建独立运行的 Web 应用,并支持多种数据访问技术和第三方集成。
|
3月前
|
缓存 安全 Java
Spring 框架核心原理与实践解析
本文详解 Spring 框架核心知识,包括 IOC(容器管理对象)与 DI(容器注入依赖),以及通过注解(如 @Service、@Autowired)声明 Bean 和注入依赖的方式。阐述了 Bean 的线程安全(默认单例可能有安全问题,需业务避免共享状态或设为 prototype)、作用域(@Scope 注解,常用 singleton、prototype 等)及完整生命周期(实例化、依赖注入、初始化、销毁等步骤)。 解析了循环依赖的解决机制(三级缓存)、AOP 的概念(公共逻辑抽为切面)、底层动态代理(JDK 与 Cglib 的区别)及项目应用(如日志记录)。介绍了事务的实现(基于 AOP
124 0
|
3月前
|
存储 缓存 NoSQL
Spring Cache缓存框架
Spring Cache是Spring体系下的标准化缓存框架,支持多种缓存(如Redis、EhCache、Caffeine),可独立或组合使用。其优势包括平滑迁移、注解与编程两种使用方式,以及高度解耦和灵活管理。通过动态代理实现缓存操作,适用于不同业务场景。
323 0
|
3月前
|
消息中间件 NoSQL Java
SpringBoot框架常见的starter你都用过哪些 ?
本节介绍常见的Spring Boot Starter,分为官方(如Web、AOP、Redis等)与第三方(如MyBatis、MyBatis Plus)两类,用于快速集成Web开发、数据库、消息队列等功能。
248 0
|
3月前
|
缓存 安全 Java
第五章 Spring框架
第五章 Spring框架
|
3月前
|
缓存 Java 数据库
第五章 Spring框架
第五章 Spring框架
|
3月前
|
SQL XML Java
配置Spring框架以连接SQL Server数据库
最后,需要集成Spring配置到应用中,这通常在 `main`方法或者Spring Boot的应用配置类中通过加载XML配置或使用注解来实现。
286 0