[Spring框架] 手写Spring

目录

一、背景

二、简易版Spring代码

三、使用自定义Spring代码

四、总结


一、背景

作为一个后端程序员,Spring框架是在开发中必不可少的,相信很多人都学过Spring的底层原理并且看过很多源码。但是对我来说,实操是一个会加深对代码的理解和掌握程度的事情,所以在学习spring原理的时候,跟着视频写了下面的代码。

二、简易版Spring代码

注解里面都会涉及到两个注解,@Rentention 和 @Target,这两个注解的作用分别是:

  • @Retention(RetentionPolicy.RUNTIME): 这个注解指定了注解的保留策略为RUNTIME,意味着这个注解不仅会被编译到class文件中,而且在运行时通过反射还能够被程序读取。这是使得运行时动态处理注解成为可能的关键;

  • @Target(ElementType.TYPE): 这个注解指定了注解的应用目标为TYPE,意味着这个注解只能用于类、接口(包括注解类型)或枚举的声明上。

1、Autowired注解:用于依赖注入

package com.xxx.spring;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface Autowired {

    boolean required() default true;
}

2、Component注解:声明当前类是一个bean对象,由Spring来管理生命周期

这里面value()方法的作用:在使用Component注解的时候可以指定当前bean的name,这里面value的作用是获取beanName。

package com.xxx.spring;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface Component {
    String value() default "";
}

3、ComponentScan注解:用于指定bean的扫描路径

package com.xxx.spring;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface ComponentScan {
    String value() default "";
}

4、Scope注解:指定当前bean是多例还是单例,多例是prototype,默认是单例

package com.xxx.spring;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface Scope {

    String value() default "";
}

5、BeanDefinition类:保存一个bean的定义

package com.xxx.spring;

public class BeanDefinition {

    private Class clazz;
    private String scope;

    public Class getClazz() {
        return clazz;
    }

    public void setClazz(Class clazz) {
        this.clazz = clazz;
    }

    public String getScope() {
        return scope;
    }

    public void setScope(String scope) {
        this.scope = scope;
    }
}

6、BeanPostProcessor接口:接口中包含的是简易版的方法,可用于AOP,在生成bean对象以后,根据业务需求对bean对象进行进一步的处理

package com.xxx.spring;

public interface BeanPostProcessor {

    Object postProcessorBeforeInitialization(Object bean, String beanName);

    Object postProcessorAfterInitialization(Object bean, String beanName);
}

7、XXXApplicationContext类

package com.xxx.spring;

import java.io.File;
import java.lang.reflect.Field;
import java.net.URL;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

public class XXXApplicationContext {

    // 配置类
    private Class configClass;

    // 保存bean定义的map
    private Map<String, BeanDefinition> beanDefinitionMap = new HashMap<>();

    private Map<String, Object> singletonObjects = new HashMap<>(); // 单例池

    // 保存bean后处理器,在初始化前后对指定bean进行特定的处理
    private List<BeanPostProcessor> beanPostProcessorList = new ArrayList<>();

    public XXXApplicationContext(Class configClass) {
        this.configClass = configClass;

        scan(configClass); // beanDefinition

        preInstantiateSingletons(); // 实例化单例--->单例池
    }

    /**
     * 遍历保存bean定义的map:
     * 如果当前bean对象是单例,调用createBean方法,并且放入单例池;
     */
    private void preInstantiateSingletons() {
        for(Map.Entry<String, BeanDefinition> entry : beanDefinitionMap.entrySet()) {
            BeanDefinition beanDefinition = entry.getValue();
            String beanName = entry.getKey();
            if (beanDefinition.getScope().equals("singleton")) {
                Object bean = createBean(beanName, beanDefinition);
                singletonObjects.put(beanName, bean);
            }
        }
    }

    /**
     * 创建bean对象
     * 1、根据bean定义创建一个bean的对象;
     * 2、遍历当前对象里面的所有属性,如果当前属性使用了@Autowired注解,调用getBean()方法获取对应的bean对象,把bean对象
     *    赋值给当前属性,其中一定要有filed.setAccessible(true);这行代码,作用是:设置当前字段可访问,这样就可以访问和修改
     *    这个字段的值;
     * 3、初始化前的代码,spring框架里面当前方法返回的是null。如果返回的是对象,就不会继续执行后面的代码,所以要返回null;
     * 4、初始化代码;
     * 5、初始化后的代码:其中代理可以在这里实现;
     * 6、返回创建好的bean对象。
     * @param beanName
     * @param beanDefinition
     * @return
     */
    private Object createBean(String beanName, BeanDefinition beanDefinition) {
        try {

            Class clazz = beanDefinition.getClazz();
            Object instance = clazz.newInstance();

            // 依赖注入,这里Autowired注解如果required = true,如果getBean为null的话,就会报错,需要其他处理逻辑
            for (Field filed : clazz.getDeclaredFields()) {
                if (filed.isAnnotationPresent(Autowired.class)) {
                    String name = filed.getName();
                    Object bean = getBean(name);
                    filed.setAccessible(true);
                    filed.set(instance, bean);
                }
            }

            // 初始化前
            for (BeanPostProcessor beanPostProcessor : beanPostProcessorList) {
                beanPostProcessor.postProcessorBeforeInitialization(instance, beanName);
            }

            // 初始化
            if (instance instanceof InitializingBean) {
                // 这里的后处理方法可以给defaultUser赋值
                ((InitializingBean)instance).afterPropertiesSet();
            }

            // 初始化后
            for (BeanPostProcessor beanPostProcessor : beanPostProcessorList) {
                beanPostProcessor.postProcessorAfterInitialization(instance, beanName);
            }

            return instance;
        } catch (InstantiationException e) {
            throw new RuntimeException(e);
        } catch (IllegalAccessException e) {
            throw new RuntimeException(e);
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    /**
     * 扫描指定路径下面的bean对象
     * 1、从指定的配置类里面获取ComponentScan注解,得到要扫描的路径;
     * 2、使用类加载器获取对应路径下面的所有文件;
     * 3、遍历所有文件:
     *              如果当前文件以.class结尾,那么获取以com开头,以类名结尾的字符串,比如:com/xxx/test/userService
     *              用'.'替换字符串中的'/'
     *              如果当前类里面包含Component注解,
     *                  如果是BeanPostProcessor的实现类,就放到对应的list里面;
     *                  获取Component里面的value,也就是beanName;
     *                  把当前clazz和beanName放到bean定义的map中,同时根据scope注解设置是singleton还是prototype
     * @param configClass
     */
    private void scan(Class configClass) {
        // 解析配置类,获取扫描路径
        ComponentScan annotation = (ComponentScan) configClass.getAnnotation(ComponentScan.class);
        String path = annotation.value();
        path = path.replace(".", "/");

        // 扫描加了Component注解的类--->生成Bean对象(多例Bean还是单例Bean)
        ClassLoader classLoader = BhlApplicationContext.class.getClassLoader();
        // 这里是相对路径,对的是classPath
        URL resource = classLoader.getResource(path);
        File file = new File(resource.getFile());

        File[] files = file.listFiles();
        for(File f : files) {
            String fileName = f.getAbsolutePath();

            if (fileName.endsWith(".class")) {
                String className = fileName.substring(fileName.indexOf("com"), fileName.indexOf(".class"));
                className = className.replace("/", ".");

                try {
                    Class clazz = classLoader.loadClass(className);

                    if (clazz.isAnnotationPresent(Component.class)) {

                        if (BeanPostProcessor.class.isAssignableFrom(clazz)) {
                            BeanPostProcessor beanPostProcessor = (BeanPostProcessor) clazz.newInstance();
                            beanPostProcessorList.add(beanPostProcessor);
                        }

                        Component component = (Component) clazz.getAnnotation(Component.class);
                        String beanName = component.value();

                        BeanDefinition beanDefinition = new BeanDefinition();
                        beanDefinition.setClazz(clazz);

                        if (clazz.isAnnotationPresent(Scope.class)) {
                            Scope scope = (Scope) clazz.getAnnotation(Scope.class);
                            beanDefinition.setScope(scope.value());
                        } else {
                            beanDefinition.setScope("singleton");
                        }

                        beanDefinitionMap.put(beanName, beanDefinition);
                    }
                } catch (ClassNotFoundException e) {
                    throw new RuntimeException(e);
                } catch (InstantiationException e) {
                    throw new RuntimeException(e);
                } catch (IllegalAccessException e) {
                    throw new RuntimeException(e);
                }

            }
        }
    }

    /**
     * 根据name获取bean对象
     * 判断bean定义map中是否包含当前beanName;
     * 如果当前scope是singleton,从单例池里面获取直接返回;
     * 如果scope是prototype,调用createBean创建对象并返回。
     * @param beanName
     * @return
     */
    public Object getBean(String beanName) { // xxx--->Map--->BeanDefinition--->scope

        if (beanDefinitionMap.containsKey(beanName)) {
            BeanDefinition beanDefinition = beanDefinitionMap.get(beanName);

            if (beanDefinition.getScope().equals("singleton")) {
                Object singleObject = singletonObjects.get(beanName);
                return singleObject;
            } else {
                Object prototypeObject = createBean(beanName, beanDefinition);
                return prototypeObject;
            }
        } else {
            throw new NullPointerException();
        }
    }
}

8、InitializingBean接口

package com.xxx.spring;

public interface InitializingBean {

    void afterPropertiesSet() throws Exception;
}

三、使用自定义Spring代码

1、AppConfig类

package com.xxx.test;

import com.xxx.spring.ComponentScan;

/**
 * ComponentScan里面的路径和JVM类加载器有关
 * JVM类加载器:BootstrapCLassLoader:最顶层的加载类,由 C++实现,通常表示为 null,并且没有父级,主要用来加载 JDK 内部的核心类库
 *                                ( %JAVA_HOME%/lib目录下的 rt.jar、resources.jar、charsets.jar等 jar 包和类)
 *                                 以及被 -Xbootclasspath参数指定的路径下的所有类
 *            ExtensionCLassLoader:主要负责加载 %JRE_HOME%/lib/ext 目录下的 jar 包和类以及被 java.ext.dirs
 *                                 系统变量所指定的路径下的所有类
 *            AppClassLoader:面向我们用户的加载器,负责加载当前应用 classpath 下的所有 jar 包和类。
 */
@ComponentScan("com.xxx.test")
public class AppConfig {
}

2、XXXBeanPostProcessor类

package com.xxx.test;

import com.xxx.spring.BeanPostProcessor;
import com.xxx.spring.Component;

/**
 * 根据业务中的需要对bean对象进行处理,
 * 如果只针对某一个特定的bean进行处理,使用 if 进行条件判断。
 *
 * 这里面方法的返回值是Object,如果需要的话,可以替换之前生成的bean对象。
 */
@Component
public class BhlBeanPostProcessor implements BeanPostProcessor {

    @Override
    public Object postProcessorBeforeInitialization(Object bean, String beanName) {
        System.out.println("before -->" + bean);
        return bean;
    }

    // 代理是在 after 方法里面实现的。
    @Override
    public Object postProcessorAfterInitialization(Object bean, String beanName) {
        System.out.println("after -->" + bean);
        return bean;
    }
}

3、Main类

package com.xxx.test;

import com.xxx.spring.BhlApplicationContext;

public class Main {
    public static void main(String[] args) {
        // 使用自定义Spring
        XXXApplicationContext applicationContext = new XXXApplicationContext(AppConfig.class);

        UserService userService = (UserService) applicationContext.getBean("userService");

        userService.test();
    }
}

4、OrderService类

package com.xxx.test;

import com.xxx.spring.Component;
import com.xxx.spring.Scope;

@Component("orderService")
@Scope("prototype") // prototype指定这是一个多例bean
public class OrderService {
}

5、UserService类

package com.xxx.test;

import com.xxx.spring.Autowired;
import com.xxx.spring.Component;
import com.xxx.spring.InitializingBean;

@Component("userService")
public class UserService implements InitializingBean { // BeanDefinition--->Map<beanName, BeanDefinition对象>--->scope属性

    @Autowired
    private OrderService orderService;

    private User defaultUser; // MySQL ---> User ----> defaultUser

    public void test() {
        System.out.println(orderService);
        System.out.println(defaultUser);
    }

    @Override
    public void afterPropertiesSet() throws Exception {
        // MySQL ---> User ----> defaultUser
        defaultUser = new User();
    }
}

6、User类

  UserService中实现了InitializingBean接口,实现了afterPropertiesSet方法,在该方法里面对bean对象中的其他字段进行了赋值。

package com.xxx.test;

public class User {
}

四、总结

这里只是简单写了一下spring框架里面的几个方法,其中很多逻辑都有欠缺的地方,没有涉及到的地方有很多,比如循环依赖的问题,在springboot中循环依赖涉及到了三个map对象,这里只有一个单例池,但是代码里面把大概的逻辑都讲了一下。

比如在启动Spring的时候,spring如何扫描的bean对象,如何把bean对象放到了容器里面,也稍微涉及了AOP的事情,可以在后处理器中进行实现。

AppConfig是启动类,通常在开发的时候main方法是写在AppConfig里面的,这里类里面指明了bean对象的扫描路径。

  • 13
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
### 回答1: Spring Framework是一个开源的Java平台,它提供了一组全面的工具来支持Java应用程序开发。它主要包括IoC容器、AOP框架、事务管理、MVC框架、DAO框架、连接池等。它可以帮助程序员更好地组织代码,减少重复性工作,提高代码质量。手写Spring框架需要对Java编程和设计模式有较深的了解,并对Spring框架的源码有着深入的研究。 ### 回答2: Spring框架是一个开源的Java平台,用于构建企业级应用程序。它提供了一种全面的编程和配置模型,用于开发基于Java的应用程序和服务。手写Spring框架意味着从头开始实现Spring的核心功能。 手写Spring框架的基本步骤包括: 1. 创建一个核心容器类,用于管理应用程序中的Bean对象。这个容器类需要提供注册、获取和销毁Bean的功能。 2. 定义一个Bean注解,用于标识哪些类应该被容器所管理。 3. 创建一个Bean定义类,用于存储每个Bean的元数据信息,包括类名、作用域和依赖关系等。 4. 实现依赖注入功能,通过读取Bean定义中的依赖关系,将相关的Bean对象注入到目标Bean中。 5. 提供AOP(面向切面编程)功能,允许开发者在应用程序的特定点进行横切关注点的处理。 6. 实现声明式事务管理功能,使用注解或XML配置方式来处理数据库事务。 7. 提供对不同数据访问技术(如JDBC、ORM框架、NoSQL等)的支持,通过集成相应的库来简化数据访问代码。 8. 增加对Web开发的支持,如处理请求、渲染视图等。 手写Spring框架需要具备对Java语言的深入了解,熟悉反射、代理、设计模式等概念和技术。还需要对依赖注入、AOP、事务管理、Web开发等方面有一定的理解。实现一个完整的Spring框架是一个庞大而复杂的任务,需要经过反复的设计、开发和测试。通过手写Spring框架,可以更深入地理解Spring的原理和内部机制,提高对框架的使用和调试能力。 ### 回答3: 手写Spring框架是一个相当复杂的任务,但我可以简要介绍手写Spring框架的一些关键步骤和组成部分。 1. 依赖注入:Spring框架的核心概念之一是依赖注入。我们需要编写一个容器,负责管理和维护各个对象之间的依赖关系。可以使用反射机制来实现依赖注入。 2. 控制反转:Spring框架通过控制反转(IoC)来管理对象的创建和生命周期。我们需要编写一个BeanFactory,负责加载和实例化对象,并将依赖注入到相应的对象中。 3. 配置文件:手写Spring框架也需要一个配置文件,用于定义对象的依赖关系和属性。我们可以使用XML、注解或者JavaConfig等方式来定义配置文件。 4. AOP支持:Spring框架提供了面向切面编程(AOP)的支持,可以通过编写切面和通知来实现横切关注点的统一处理。我们需要实现一个AOP框架,用于拦截和处理切面逻辑。 5. MVC模式:Spring框架也提供了一个MVC框架,用于处理Web应用程序的请求和响应。我们需要编写一个DispatcherServlet,负责将请求分发给相应的控制器,并处理模型和视图的交互。 6. 整合其他技术:Spring框架还可以与其他技术进行整合,例如数据库访问、事务管理、安全性控制等。我们需要编写相应的模块,将这些技术与Spring框架集成起来。 虽然这只是一个简要的介绍,手写Spring框架是一个非常庞大的工程,需要深入理解Spring的原理和设计思想,并具备扎实的Java编程能力。但通过手写Spring框架,我们可以更好地掌握Spring的核心概念和原理,并加深对框架的理解。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值