小狍子也能看懂的手写Spring

项目目录

  • Autowired

  • Component

  • ComponentScan 扫描路径配置在配置类上方

  • Lazy 是否懒加载

  • Scope 原型模式|单例模式

  • InitializingBean

    Spring自带类用于解决Bean在定义时需要通过特殊途径(数据库)得到对象

    实际是在创建完成bean后会执行, 一个接口实现接口后重写方法就可以了

  • BeanPostProcessor

    一个很重要的类, Spring的关键类, 用于被实现AOP操作 这里可以先创建好bean注意注解类都需要加上

java 复制代码
@Retention(RetentionPolicy.RUNTIME)

加上这个注解就代表我们编写的注解是一个运行作用域的注解,这样才可以被反射方法访问到

applicationContext

这个类就是Spring的启动类,我们在实例化的时候会执行这个类的构造方法。所以我们的代码需要写在这个类中。但是,并不是代表着所有方法都编写其中,需要注意方法的抽取!!!

java 复制代码
public applicationContext(Class 配置文件){
    // 类的装载
}

编写装载方法

Spring的bean有一个叫做beanDefinitionMap的集合它是用来存放bean定义的,然后在需要创建对应的情况去找对应的bean定义来实例化对象并返回,在Spring中有两种类的创建模式: 单例模式、原型模式。下面的两种创建模式都是通过获取到这个Map集合中指定bean的值,并将这个定义去实例化

java 复制代码
// bean定义对象beanDefinition
public class BeanDefinition {
    private Class type;
    private String scope;
    private boolean isLazy;
    // 省略get、set
}

单例模式:单例bean是在加载Spring配置的时候就会实例化并且存放于singletonObjects的一个Map集合,这个集合就是存放我们创建单例bean的地方()

原型模式:获取bean定义map对应的value后再实例化这个bean并放回结果。

这里我大概讲解一下他是依靠获取到我们对应配置类中的@ComponentScan注解中的值,将这个值转换为对应的相对路径,再使用我们当前程序的appClassloader获取到再当前操作系统的绝对路径(编译Class文件)。 找到对应的绝对路径就代表着我们找到了我们需要去扫描的包。接下来的操作就很简单了,我们去循环遍历这个目录中的所有class文件,然后使用appClassloader加载到程序中,再去判断是否有@Component注解如果有那么就把他封装为一个BeanDefinition对象

java 复制代码
// 扫描路径(将需要装载的对象作为BeanDefinition存入到集合中等待实例化)

public void scan(Class confClass){
    // 如果没有配置扫描包注解便不会执行
    if(!confClass.isAnnotationPresent(ComponentScan.class)) return;

    // 获取需要扫描的路径
    ComponentScan componentScan = (ComponentScan) confClass.getAnnotation(ComponentScan.class);
    String path= componentScan.value();
    path = path.replace(".","/"); // 将.换位对应操作系统的分隔符

    // 将设置的扫描包路径变为对应的绝对路径,便于后续操作
    ClassLoader appClassLoader = confClass.getClassLoader();
    URL resource = appClassLoader.getResource(path);
    String filePath = resource.getFile();

    // 找到需要扫描的路径文件夹,注意这里我们拿到的是target项目编译运行的路径
    // (拿到一个.java文件是没有的这个文件是给我们编写人员看的,JVM所获取的是)

    // (在我们@ComponentScan注解传递的是一个包,是没有办法直接拿到其中的所有类文件的所以这个我们通过io拿到所有文件
    // ,再对文件数据进行转化为我们的类路径)
    File file = new File(filePath);
    // 将文件夹中的所有问题给拿出了,这里就不考虑相互嵌套的问题了
    ArrayList<File> loadFile = new ArrayList<>();

    // 如果是一个非文件夹对象接下来的代码就不必执行了,也可以判断为参数错误并非一个包路径
    if(!file.isDirectory()) return;
    for (File item : file.listFiles()) {
        if (item.isDirectory()){
            for (File item2 : item.listFiles()) {
                loadFile.add(item2);
            }
        }else{
            loadFile.add(item);
        }
    }

    // 拿到文件的file对象后, 遍历使用类加载器加载得到class对象
    for (File loadFileItem : loadFile) {
        // 通过先获取对应的class文件绝对路径,再将其操作为对应的路径
        String absolutePath = loadFileItem.getAbsolutePath();
        // 将绝对路径转化为对应的类路径
        String className = absolutePath.substring(absolutePath.indexOf("com"),absolutePath.indexOf(".class")).replace("/",".").replace("\",".");
        try {
            // 加载这个类
            Class clazz = appClassLoader.loadClass(className);
            if (clazz.getAnnotation(Component.class)==null) continue;

            BeanDefinition beanDefinition = new BeanDefinition();
            beanDefinition.setType(clazz);
            if (clazz.getAnnotation(Scope.class)!=null){
                beanDefinition.setScope(((Scope)clazz.getAnnotation(Scope.class)).value());
            }else { // 默认情况
                beanDefinition.setScope("singleton");
            }

            // 添加BeanDefinition中
            String beanName = ((Component)clazz.getAnnotation(Component.class)).value();
            beanDefinitionMap.put(beanName,beanDefinition);

            // 判断是否是BeanPostProcessor, class之间的判断类型
            if(BeanPostProcessor.class.isAssignableFrom(clazz)){
                BeanPostProcessor beanPostProcessor = (BeanPostProcessor) createBean(beanName,beanDefinition);
                beanPostProcessorList.add(beanPostProcessor);
            }
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }
}

创建bean对象

这里面的概念均会在下文讲解 这里其实就是创建对象以及注入bean。

java 复制代码
// 创建bean
public Object createBean(String beanName, BeanDefinition beanDefinition){
    Class clazz = beanDefinition.getType();
    try {
        Object bean = null;

        // 初始化并注入对象
        bean = clazz.newInstance();
        bean = assemble(bean);

        // 执行BeanPostProcessor方法(前)
        for (BeanPostProcessor beanPostProcessor : beanPostProcessorList) {
            bean = beanPostProcessor.postProcessBeforeInitialization(bean,beanName);
        }

        // 判断是否执行initialzingBean中的方法
        if(bean instanceof InitializingBean){
            try {
                ((InitializingBean)bean).afterPropertiesSet(); // 将当前Object对象转换后调用对应方法
            } catch (Exception e) {
                throw new RuntimeException(e);
            }
        }

        // 执行BeanPostProcessor方法(后)
        for (BeanPostProcessor beanPostProcessor : beanPostProcessorList) {
            bean = beanPostProcessor.postProcessAfterInitialization(bean,beanName);
        }

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

// 装配bean,如Autowired注解等
public Object assemble(Object bean){
    Field[] fields = bean.getClass().getDeclaredFields();
    for (Field field : fields) {
        if(field.isAnnotationPresent(Autowired.class)){
            Object aurowiredBean = getBean(field.getName());
            try {
                field.setAccessible(true); // 关闭访问检查
                field.set(bean,aurowiredBean); // 赋值属性
            } catch (IllegalAccessException e) {
                throw new RuntimeException(e);
            }
        }
    }

    return bean;
}

获取bean

判断是在单例对象池中获取(单例模式)还是创建bean返回(原型模式)

java 复制代码
// 获取bean1(泛型方法)
public<T> T getBean(String beanName,Class<T> t){
    BeanDefinition beanDefinition = beanDefinitionMap.get(beanName);

    // 如果在bean定义中没有找到就代表这个对象并没有注册到Spring中
    if(beanDefinition==null){
        throw new RuntimeException(new ClassNotFoundException());
    }

    if("singleton".equals(beanDefinition.getScope())){ //单例的情况直接从单例bean中读取
        Object singletonBean =  singletonObjects.get(beanName);
        if(singletonBean == null){
            singletonBean = createBean(beanName,beanDefinition);
        }
        return (T)singletonBean;
    }else{ // 原型的情况直接使用createBean创建
        Object protoType = createBean(beanName,beanDefinition);
        return (T)protoType;
    }
}

现在方法都有了那么完善构造方法

java 复制代码
// 构造器初始化
public ApplicationContext(Class confClass){
    // 扫描包设置的包路径
    scan(confClass);

    //(单例对象(singleton)在创建ApplicationContext后就会创建,而prototype是获取的时候创建的)
    // 创建设置为singleton的对象,单例对象
    // 获取bean定义中的数据,查看来创建
    for (String beanName : beanDefinitionMap.keySet()) {
        BeanDefinition beanDefinition = beanDefinitionMap.get(beanName);
        if("singleton".equals(beanDefinition.getScope())){
            // 通过crateBean方法创建同时实现依赖注入
            Object o = createBean(beanName,beanDefinition);
            singletonObjects.put(beanName,o);
        }
    }

}

InitializingBean

在我们工作中会不会出现Spring注入bean的时候属性需要通过数据库获取的情况

这种情况就可以使用我们的InitializingBean接口了,重写方法。它会在我们创建完毕bean的时候调用,使用Bean对象来调用,所以这个方法是可以改变我们bean对象的内部值的

java 复制代码
void afterPropertiesSet() throws Exception;

为我们需要注入的bean实现接口以及接口的方法,这个方法其实就是穿插在Spring创建过程中的,在上面的创建Bean对象标题的模块就可以看到了

BeanPostProcessor

这个对象是Spring的一个关键对象,它的主要作用是可以在创建Spring的bean的前方或者后方来改变bean对象的值

执行方法完毕后返回的就是处理后的bean对象,返回回去就可以直接对SpringBean进行重新的赋值了

java 复制代码
/**
 * postProcessBeforeInitialization 在spring创建bean之前执行
 *
 * postProcessAfterInitialization
 * 在spring创建bean之后执行,可以对bean进行一些修改,就比如把bean变为一个代理对象返回会去
 *
 * 在扫描包时就直接创建,并在类的类的内部存储起来在创建bean的时间就直接调用
 *
 * 执行方法完毕后返回的就是处理后的bean对象需要重新赋值
 */
public interface BeanPostProcessor {
    //创建bean之前执行的对象
    @Nullable
    default Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
        return bean;
    }

    //创建bean之后执行的对象
    @Nullable
    default Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
        return bean;
    }
}

这个对象主要是继承使用,我们在继承这个类后并装配到Spring中Spring就会识别我们的BeanPostProcessor对象并将其存储在一个集合中BeanPostProcessList在创建bean的时候Spring就会循环调用这里面的方法,而AOP就是在方法体里面做了判断在我们创建bean的时候就会对其实现动态代理

贴一个代码

java 复制代码
@Override
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
    if("userService".equals(beanName)){
        Object proxy = Proxy.newProxyInstance(MyBeanPostProcessor.class.getClassLoader(), bean.getClass().getInterfaces(), new InvocationHandler() {
            @Override
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                System.out.println("切面逻辑");

                return method.invoke(bean,args);
            }

        });
        return proxy;
    }
    return bean;
}

gitee地址:mySpring: 手写简单Spring (gitee.com)

相关推荐
喵叔哟1 分钟前
重构代码之移动字段
java·数据库·重构
喵叔哟1 分钟前
重构代码之取消临时字段
java·前端·重构
fa_lsyk4 分钟前
maven环境搭建
java·maven
Daniel 大东23 分钟前
idea 解决缓存损坏问题
java·缓存·intellij-idea
wind瑞29 分钟前
IntelliJ IDEA插件开发-代码补全插件入门开发
java·ide·intellij-idea
HappyAcmen29 分钟前
IDEA部署AI代写插件
java·人工智能·intellij-idea
马剑威(威哥爱编程)35 分钟前
读写锁分离设计模式详解
java·设计模式·java-ee
鸽鸽程序猿35 分钟前
【算法】【优选算法】前缀和(上)
java·算法·前缀和
修道-032336 分钟前
【JAVA】二、设计模式之策略模式
java·设计模式·策略模式
九圣残炎41 分钟前
【从零开始的LeetCode-算法】2559. 统计范围内的元音字符串数
java·算法·leetcode