手写Spring(一):启动和扫描

1. 内容大纲

  1. 手写Spring启动及扫描流程
  2. 手写getBean()流程
  3. 手写Bean生命周期流程
  4. 手写依赖注入流程
  5. 手写BeanPostProcessor机制
  6. 手写AOP机制
  7. 手写BeanFactory
  8. 手写事务

……

2. Spring用法复习

入门spring时,都是用ClassPathXmlApplicationContext这个容器来学习spring。

但是现在一般都会使用AnnotationConfigApplicationContext。

二者区别:

  1. ClassPathXmlApplicationContext需要传入spring.xml文件,即配置文件。通过ClassPathXmlApplicationContext classPathXmlApplicationContext = new ClassPathXmlApplicationContext("spring.xml"),创建一个spring容器,就可以从中拿到某一个bean对象。
  2. AnnotationConfigApplicationContext传入的配置文件参数为java类。本项目手写的方式为AnnotationConfigApplicationContext

3. 准备工作

3.1 项目结构

  1. 创建一个maven工程,pom.xml里什么都不用写
  2. main.java.com下创建包spring,存储我们手写的spring源码类
  3. main.java.com下创建包psy,可以创建main方法中的test类,测试源码

在这里插入图片描述

3.2 Spring包下源码类

  1. spring下创建PsyApplicationContext,表示spring里面的容器类,添加相关API:
    1. 添加属性configClass及其有参构造器
    2. 添加方法getBean,参数为String类型beanName,返回值为Object类型,该方法先返回null
  2. spring下创建ComponentScan,文件扫描类,添加属性Sting类型value(),定义扫描路径。添加注解@Retention(RetentionPolicy.RUNTIME)@Target(ElementType.TYPE)
  3. spring下创建Component,添加属性Sting类型value(),定义bean的名字,默认值为空,因为这个不一定非要写,如果没写spring会把首字母小写。注解同上。
package com.spring;

public class PsyApplicationContext {

    private Class configClass;

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

        // 解析配置类
        // ComponemtScan注解---->拿到扫描路径---->扫描
    }

    public Object getBean(String beanName){
        return null;
    }
}
package com.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();
}
package com.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.3 psy包下模拟项目类

  1. psy.service包下新建UserService,添加注解@Component("userService")
  2. psy下创建配置类AppConfig,添加注解@ComponentScan("com.psy.service"),定义扫描路径。
  3. psy下创建Test类,实现main方法,使用PsyApplicationContext传入配置类AppConfig
package com.psy.service;

import com.spring.Component;

@Component("userService")
public class UserService {
    
}
package com.psy;

import com.spring.ComponentScan;

@ComponentScan("com.psy.service")
public class AppConfig {
    
}
package com.psy;

import com.spring.PsyApplicationContext;

public class Test {

    public static void main(String[] args){
        PsyApplicationContext applicationContext = new PsyApplicationContext(AppConfig.class);

        Object userService = applicationContext.getBean("userService");
    }
}

4. Spring扫描逻辑

4.1 得到扫描路径下的所有bean

  1. 获取配置文件上的ComponentScan.class注解
  2. 通过注解得到扫描路径(包)
  3. 通过类加载器得到扫描路径(包)下所有的文件类,遍历所有类
    1. 去掉类名com前文本以及.class后文本
    2. 再次通过类加载器得到class对象
    3. 判断当前class是否有注解Component
public PsyApplicationContext(Class configClass){
    this.configClass = configClass;

    // 解析配置类
    // ComponemtScan注解---->拿到扫描路径---->扫描
    // 1. 获取配置文件类上的ComponentScan.class注解
    ComponentScan componentScanAnnotation = (ComponentScan)configClass.getDeclaredAnnotation(ComponentScan.class);
    // 2. 拿到扫描路径 com.psy.service (包名)
    String path = componentScanAnnotation.value();
    path = path.replace(".", "/");
    System.out.println("扫描路径为:" + path);
    // 3. 扫描:通过包名得到包下所有的类———>通过类加载器得到
    // Bootsstrap--->jre/lib
    // Ext---------->jre/ext/lib
    // App---------->classpath
    ClassLoader classLoader = PsyApplicationContext.class.getClassLoader(); //app
    URL resource = classLoader.getResource(path);
    File file = new File(resource.getFile());
    if(file.isDirectory()) {
        File[] files = file.listFiles();
        for (File f : files) {
            //System.out.println(f);
            //System.out.println(f.getAbsolutePath());
            String fileName = f.getAbsolutePath();
            if (fileName.endsWith(".class")) {
                String className = fileName.substring(fileName.indexOf("com"), fileName.indexOf(".class"));
                className = className.replace("\\", ".");
                System.out.println(className);

                try {

                    Class<?> clazz = classLoader.loadClass(className);
                    if (clazz.isAnnotationPresent(Component.class)) {
                        // 表示当前这个类是一個bean
                        // Class ---> bean? 不一定需要在这一步做

                    }
                } catch (ClassNotFoundException e) {
                    e.printStackTrace();
                }

            }
        }
    }
}

4.2 根据scope创建bean

  1. spring下创建BeanDefinition,其中包含bean的定义,属性目前有bean的类private Class clazz;,以及bean是否为单例或其他private String Scope;

  2. 通过ConcurrentHashMap创建单例池(存储所有单例bean)和bean定义集合(存储所有bean的定义)

  3. PsyApplicationContext下创建方法creatBean,可以使用beanDefinition创建bean。调用无参的构造方法反射得到实例对象Object instance = clazz.getDeclaredConstructor().newInstance();

  4. 提取扫描配置类逻辑为单独方法scan。得到扫描路径下所有类后,选择带有Component注解的类,

    1. 通过注解得到类名beanName
    2. 实例化一个新的BeanDefinition
    3. 将先前得到的clazz通过set方法赋予BeanDefinition
    4. 如果当前类有scope注解,将scope注解的值赋予BeanDefinition
    5. 如果没有scope注解,将scope设为singleton
    6. 将当前bean的BeanDefinition存入beanDefinitionMap
  5. 扫描完配置类后,遍历beanDefinitionMap,如果当前``beanDefinitionMap`的Scope为singleton,即单例,就创建bean并将其放入单例池。

  6. 修改getBean方法:

    1. 如果当前bean是单例的,就从单例池map里拿
    2. 如果不是单例的,调用creatBean方法创建bean
    package com.spring;
    
    import java.io.File;
    import java.lang.annotation.Annotation;
    import java.lang.reflect.InvocationTargetException;
    import java.net.URL;
    import java.util.Map;
    import java.util.concurrent.ConcurrentHashMap;
    
    public class PsyApplicationContext {
    
        private Class configClass;
    
        private ConcurrentHashMap<String, Object> singletonObjects = new ConcurrentHashMap<>(); //单例池
        private ConcurrentHashMap<String, BeanDefinition> beanDefinitionMap = new ConcurrentHashMap<>(); //所有的bean的定义
    
        public PsyApplicationContext(Class configClass){
            this.configClass = configClass;
    
            // 解析配置类
            // ComponemtScan注解--->拿到扫描路径--->扫描--->BeanDefinition--->ConcurrentHashMap
            scan(configClass);
    
            for (Map.Entry<String, BeanDefinition> entry : beanDefinitionMap.entrySet()) {
                String beanName = entry.getKey();
                BeanDefinition beanDefinition = beanDefinitionMap.get(beanName);
                if (beanDefinition.getScope().equals("singleton")){
                    Object bean = creatBean(beanDefinition); // 单例bean对象
                    singletonObjects.put(beanName, bean); // 放入单例池
                }
    
            }
        }
    
        // 根据bean的定义创建bean的对象
        public Object creatBean(BeanDefinition beanDefinition) {
            Class clazz = beanDefinition.getClazz();
            try {
                Object instance = clazz.getDeclaredConstructor().newInstance();// 调用无参的构造方法反射得到实例对象
    
                return instance;
            } catch (InstantiationException e) {
                e.printStackTrace();
            } catch (IllegalAccessException e) {
                e.printStackTrace();
            } catch (InvocationTargetException e) {
                e.printStackTrace();
            } catch (NoSuchMethodException e) {
                e.printStackTrace();
            }
            return null;
        }
    
        private void scan(Class configClass) {
            // 1. 获取配置文件类上的ComponentScan.class注解
            ComponentScan componentScanAnnotation = (ComponentScan)configClass.getDeclaredAnnotation(ComponentScan.class);
            // 2. 拿到扫描路径 com.psy.service (包名)
            String path = componentScanAnnotation.value();
            path = path.replace(".", "/");
            System.out.println("扫描路径为:" + path);
            // 3. 扫描:通过包名得到包下所有的类———>通过类加载器得到
            // Bootsstrap--->jre/lib
            // Ext---------->jre/ext/lib
            // App---------->classpath
            ClassLoader classLoader = PsyApplicationContext.class.getClassLoader(); //app
            URL resource = classLoader.getResource(path);
            File file = new File(resource.getFile());
            if(file.isDirectory()) {
                File[] files = file.listFiles();
                for (File f : files) {
                    //System.out.println(f);
                    //System.out.println(f.getAbsolutePath());
                    String fileName = f.getAbsolutePath();
                    if (fileName.endsWith(".class")) {
                        String className = fileName.substring(fileName.indexOf("com"), fileName.indexOf(".class"));
                        className = className.replace("\\", ".");
                        System.out.println(className);
    
                        try {
    
                            Class<?> clazz = classLoader.loadClass(className);
                            if (clazz.isAnnotationPresent(Component.class)) {
                                // 有Component注解表示当前这个类是一个bean
                                // 解析类----->BeanDefinition
                                // BeanDefinition:bean的定义
    
                                Component componentAnnotation = clazz.getDeclaredAnnotation(Component.class);
                                String beanName = componentAnnotation.value();
    
                                BeanDefinition beanDefinition = new BeanDefinition();
                                beanDefinition.setClazz(clazz);
                                if (clazz.isAnnotationPresent(Scope.class)) {
                                    //有scope注解
                                    Scope scopeAnnotation = clazz.getDeclaredAnnotation(Scope.class);
                                    beanDefinition.setScope(scopeAnnotation.value()); //用户设置的值配入beanDefinition
                                } else {
                                    //没有scope注解
                                    beanDefinition.setScope("singleton");
                                }
    
                                beanDefinitionMap.put(beanName, beanDefinition);
    
                            }
                        } catch (ClassNotFoundException e) {
                            e.printStackTrace();
                        }
    
                    }
                }
            }
        }
    
        public Object getBean(String beanName){
            if (beanDefinitionMap.containsKey(beanName)) {
                BeanDefinition beanDefinition = beanDefinitionMap.get(beanName);
                if (beanDefinition.getScope().equals("singleton")){
                    // 如果是单例的,就从单例池里拿
                    Object o = singletonObjects.get(beanName);
                    return o;
                } else {
                    // 创建Bean对象
                    Object bean = creatBean(beanDefinition);
                    return bean;
                }
            } else {
                // 不存在对应的bean
                throw new NullPointerException();
            }
        }
    }
    
    
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

平什么阿

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值