[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对象的扫描路径。

相关推荐

  1. [Spring框架] Spring

    2024-05-10 09:48:06       13 阅读
  2. Spring框架之: HelloSpring

    2024-05-10 09:48:06       34 阅读
  3. spring框架(4)

    2024-05-10 09:48:06       10 阅读

最近更新

  1. TCP协议是安全的吗?

    2024-05-10 09:48:06       16 阅读
  2. 阿里云服务器执行yum,一直下载docker-ce-stable失败

    2024-05-10 09:48:06       16 阅读
  3. 【Python教程】压缩PDF文件大小

    2024-05-10 09:48:06       15 阅读
  4. 通过文章id递归查询所有评论(xml)

    2024-05-10 09:48:06       18 阅读

热门阅读

  1. 面试题:进程与线程的区别

    2024-05-10 09:48:06       14 阅读
  2. go匿名字段

    2024-05-10 09:48:06       9 阅读
  3. k8s集群的常用命令

    2024-05-10 09:48:06       11 阅读
  4. Django框架之Ajax基础

    2024-05-10 09:48:06       7 阅读
  5. 什么是图片的像素与分辨率?

    2024-05-10 09:48:06       8 阅读
  6. 机器学习过拟合解决方案 - 正则化

    2024-05-10 09:48:06       13 阅读