【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);
}

相关文章
|
10天前
|
安全 Java 开发者
如何在Spring框架中实现横切关注点的集中管理和重用?
【4月更文挑战第30天】如何在Spring框架中实现横切关注点的集中管理和重用?
17 0
|
10天前
|
安全 Java 程序员
Spring框架的核心特性是什么?
【4月更文挑战第30天】Spring 的特性
16 0
|
10天前
|
Java 测试技术 开发者
Spring IoC容器通过依赖注入机制实现控制反转
【4月更文挑战第30天】Spring IoC容器通过依赖注入机制实现控制反转
21 0
|
1天前
|
前端开发 安全 Java
使用Spring框架加速Java开发
使用Spring框架加速Java开发
5 0
|
1天前
|
设计模式 数据采集 监控
Spring日志框架
Spring日志框架
6 0
|
1天前
|
前端开发 Java 应用服务中间件
Spring MVC框架概述
Spring MVC 是一个基于Java的轻量级Web框架,采用MVC设计模型实现请求驱动的松耦合应用开发。框架包括DispatcherServlet、HandlerMapping、Handler、HandlerAdapter、ViewResolver核心组件。DispatcherServlet协调这些组件处理HTTP请求和响应,Controller处理业务逻辑,Model封装数据,View负责渲染。通过注解@Controller、@RequestMapping等简化开发,支持RESTful请求。Spring MVC具有清晰的角色分配、Spring框架集成、多种视图技术支持以及异常处理等优点。
8 1
|
9天前
|
SQL Java 数据库连接
Springboot框架整合Spring JDBC操作数据
JDBC是Java数据库连接API,用于执行SQL并访问多种关系数据库。它包括一系列Java类和接口,用于建立数据库连接、创建数据库操作对象、定义SQL语句、执行操作并处理结果集。直接使用JDBC涉及七个步骤,包括加载驱动、建立连接、创建对象、定义SQL、执行操作、处理结果和关闭资源。Spring Boot的`spring-boot-starter-jdbc`简化了这些步骤,提供了一个在Spring生态中更便捷使用JDBC的封装。集成Spring JDBC需要添加相关依赖,配置数据库连接信息,并通过JdbcTemplate进行数据库操作,如插入、更新、删除和查询。
|
9天前
|
SQL Java 数据库连接
Springboot框架整合Spring Data JPA操作数据
Spring Data JPA是Spring基于ORM和JPA规范封装的框架,简化了数据库操作,提供增删改查等接口,并可通过方法名自动生成查询。集成到Spring Boot需添加相关依赖并配置数据库连接和JPA设置。基础用法包括定义实体类和Repository接口,通过Repository接口可直接进行数据操作。此外,JPA支持关键字查询,如通过`findByAuthor`自动转换为SQL的`WHERE author=?`查询。
|
10天前
|
安全 Java 开发者
在Spring框架中,IoC和AOP是如何实现的?
【4月更文挑战第30天】在Spring框架中,IoC和AOP是如何实现的?
21 0
|
10天前
|
XML Java 程序员
什么是Spring的IoC容器?
【4月更文挑战第30天】什么是Spring的IoC容器?
19 0