Bootstrap

Spring源码学习:手写模拟Spring底层原理


本文是按照自己的理解进行笔记总结,如有不正确的地方,还望大佬多多指点纠正,勿喷。

目标

  • 了解Spring底层源码启动过程
  • 了解BeanDefinition、BeanPostProcessor的概念
  • 了解Spring解析配置类等底层源码工作流程
  • 了解依赖注入,Aware回调等底层源码工作流程
  • 了解Spring AOP的底层源码工作流程

1. 基础工作

1.1 bean是在什么时候创建的

在手写之前我们先搞清楚bean是在什么时候创建的呢?

bean分为单例bean和原型bean(多例bean),单例bean又分为懒加载和非懒加载。懒加载的意思是你要用的时候才去创建这个bean,非懒加载的单例bean是在Spring启动的过程中间把非懒加载的单例bean给创建出来。

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

在这里插入图片描述

1.2 准备大致框架

首先是先定义一个bean,UserService

package com.ding.service;

public class UserService {
 
}

再模拟一个XXXApplicationContext类,然后写一个测试类,在写测试类之前需要一个AppConfig这个类,用来定义一个配置类的构造方法,还有一个是获取bean的名字一个方法。
XXXApplicationContext

package com.spring;

public class DingJunXiaApplicationContext {
    private Class configClass;

    public DingJunXiaApplicationContext(Class configClass) {
        this.configClass = configClass;
    }
    public Object getBean(String beanName){
        return null;
    }
}

做一个配置类

package com.ding;

public class AppConfig {

}

测试类

package com.ding;

import com.ding.service.UserService;
import com.spring.DingJunXiaApplicationContext;

public class Test {
    public static void main(String[] args) {

        DingJunXiaApplicationContext applicationContext = new DingJunXiaApplicationContext(AppConfig.class);

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

        userService.test();//写这个之前在UserService中添加test方法即可
    }
}

到此一个简单的运行案例就写出来了

2. 实现@ComponentScan注解

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() default "";
}

这样我们就可以使用我们自己的注解了。我们就扫描service这个包

package com.ding;

import com.spring.ComponentScan;

@ComponentScan("com.ding.service")
public class AppConfig {

}

3. 实现@Component注解

其实我们在写了扫描范围之后,我们需要在UserService上加一个@Service或者是@Component,所以我们要实现@Component注解,作用就是用于以后取bean的名字

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 "";
}
package com.ding.service;

import com.spring.Component;

@Component("userService")
public class UserService {
    public void test() {
        System.out.println("测试一下");
    }
}

4. spring底层原理具体简单模拟实现

4.1 @ComponentScan+@Component+@Scope的功能实现

在创建bean对象的时候,我们肯定要先扫描,(因为懒加载单例bean、非懒加载单例bean、多例bean创建bean的时间不同,所以我们是需要知道创建懒加载单例bean,还是非懒加载单例bean,还是多例bean呢?那么我们怎么才能知道呢,我们就需要进行扫描。我们要看service包下面都是有什么类,这个类是属于哪一种加载类还是只是普通类)。
因此扫描这份工作就需要在第9行执行之前进行扫描,也就是new 的这个DingJunXiaApplicationContext这个方法中,这个方法在DingJunXiaApplicationContext这个类中,所以从这个地方入手。
在这里插入图片描述
扫描第一步先拿到路径(在这之前可以先判断是不是有这个ComponentScan注解,如果有我们获取这个注解里面的内容,拿到内容之后我们在那到扫描路径)

package com.spring;

import java.lang.annotation.Annotation;

public class DingJunXiaApplicationContext {
    private Class configClass;

    public DingJunXiaApplicationContext(Class configClass) {
        this.configClass = configClass;
        //扫描
        if (configClass.isAnnotationPresent(ComponentScan.class)) {
            ComponentScan componentScanAnnotation = (ComponentScan) configClass.getAnnotation(ComponentScan.class);
            String path = componentScanAnnotation.value();
            System.out.println(path);
        }
    }
    public Object getBean(String beanName){
        return null;
    }
}

其实这个时候我们可以把test文件运行一下,我们知道肯定会报错呢,因为我们在getBean那个方法里面传的是null,应该会报一个空指针。但是路径会输出
在这里插入图片描述
这个时候我们拿到的扫描路径是com.ding.service这样的,但是我们只是找到了文件的路径,我们还要看这个路径下面有几个文件啊,文件里面有没有Component,如果有就是一个bean,如果没有就不是一个bean。那么我们该怎么做呢?
我们要取编译后的com.ding.service目录下面的文件,因为我们自己写的是源码文件。我们如何去理解这个target下面的classes下面的文件呢?
在这里插入图片描述
可以看一下java文件运行时候idea的启动参数信息(就是下面图片用线画出来的地方):
在这里插入图片描述
为了能够能清晰的看到,我把这部分粘贴出来了,如下:

"C:\Program Files\Java\jdk1.8.0_221\bin\java.exe" "-javaagent:
D:\soft\idea2020\IntelliJ IDEA 2020.1\lib\idea_rt.jar=8349:
D:\soft\idea2020\IntelliJ IDEA 2020.1\bin"
 -Dfile.encoding=UTF-8 -classpath "C:\Program Files\Java\jdk1.8.0_221\jre\lib\charsets.jar;
 C:\Program Files\Java\jdk1.8.0_221\jre\lib\deploy.jar;
 C:\Program Files\Java\jdk1.8.0_221\jre\lib\ext\access-bridge-64.jar;
 C:\Program Files\Java\jdk1.8.0_221\jre\lib\ext\cldrdata.jar;
 C:\Program Files\Java\jdk1.8.0_221\jre\lib\ext\dnsns.jar;
 C:\Program Files\Java\jdk1.8.0_221\jre\lib\ext\jaccess.jar;
 C:\Program Files\Java\jdk1.8.0_221\jre\lib\ext\jfxrt.jar;
 C:\Program Files\Java\jdk1.8.0_221\jre\lib\ext\localedata.jar;
 C:\Program Files\Java\jdk1.8.0_221\jre\lib\ext\nashorn.jar;
 C:\Program Files\Java\jdk1.8.0_221\jre\lib\ext\sunec.jar;
 C:\Program Files\Java\jdk1.8.0_221\jre\lib\ext\sunjce_provider.jar;
 C:\Program Files\Java\jdk1.8.0_221\jre\lib\ext\sunmscapi.jar;
 C:\Program Files\Java\jdk1.8.0_221\jre\lib\ext\sunpkcs11.jar;
 C:\Program Files\Java\jdk1.8.0_221\jre\lib\ext\zipfs.jar;
 C:\Program Files\Java\jdk1.8.0_221\jre\lib\javaws.jar;
 C:\Program Files\Java\jdk1.8.0_221\jre\lib\jce.jar;
 C:\Program Files\Java\jdk1.8.0_221\jre\lib\jfr.jar;
 C:\Program Files\Java\jdk1.8.0_221\jre\lib\jfxswt.jar;
 C:\Program Files\Java\jdk1.8.0_221\jre\lib\jsse.jar;
 C:\Program Files\Java\jdk1.8.0_221\jre\lib\management-agent.jar;
 C:\Program Files\Java\jdk1.8.0_221\jre\lib\plugin.jar;
 C:\Program Files\Java\jdk1.8.0_221\jre\lib\resources.jar;
 C:\Program Files\Java\jdk1.8.0_221\jre\lib\rt.jar;
D:\postgraduate\Research\employment\code\spring\spring2.2_2\target\classes;
D:\soft\apache-maven-3.6.1\repository\net\bytebuddy\byte-buddy\1.12.22\byte-buddy-1.12.22.jar;
D:\soft\apache-maven-3.6.1\repository\org\springframework\spring-context\6.0.2\spring-context-6.0.2.jar;
D:\soft\apache-maven-3.6.1\repository\org\springframework\spring-aop\6.0.2\spring-aop-6.0.2.jar;
D:\soft\apache-maven-3.6.1\repository\org\springframework\spring-beans\6.0.2\spring-beans-6.0.2.jar;
D:\soft\apache-maven-3.6.1\repository\org\springframework\spring-core\6.0.2\spring-core-6.0.2.jar;
D:\soft\apache-maven-3.6.1\repository\org\springframework\spring-jcl\6.0.2\spring-jcl-6.0.2.jar;
D:\soft\apache-maven-3.6.1\repository\org\springframework\spring-expression\6.0.2\spring-expression-6.0.2.jar" com.ding.Test

其中,在这个里面可以看到划线部分加载的是target/classes(其实这个是与类加载器是由关系的,总共有三个类加载器,
Boot管理的是jre/lib
Ext管理的是jre/ext/lib
App管理的是那个classpath所指定的,从做指定的jar包和target/classes下面的):
在这里插入图片描述
因此下面我们要做的就是把上面的那个路径通过类加载器去target/classes那个目录下去找com.ding.service这个目录。需要注意的是刚才我们打印出来的路径是com.ding.service,要想真正走到这个路径下,需要使用com/ding/service,那么我们就使用替换的方法把原来地址的“.”换成"/"即可。
然后使用ClassLoader先找到路径,在该路径下遍历文件

package com.spring;

import java.io.File;
import java.lang.annotation.Annotation;
import java.net.URL;

public class DingJunXiaApplicationContext {
    private Class configClass;

    public DingJunXiaApplicationContext(Class configClass) {
        this.configClass = configClass;
        //扫描
        if (configClass.isAnnotationPresent(ComponentScan.class)) {
            ComponentScan componentScanAnnotation = (ComponentScan) configClass.getAnnotation(ComponentScan.class);
            String path = componentScanAnnotation.value();
            path = path.replace(".", "/");
            ClassLoader classLoader = DingJunXiaApplicationContext.class.getClassLoader();
            URL url = classLoader.getResource(path);
            //System.out.println(url);
            File file = new File(url.getFile());
            //如果是一个目录,遍历文件
            if (file.isDirectory()){
                for (File f : file.listFiles()){
                    String absolutePath = f.getAbsolutePath();
                    System.out.println(absolutePath);
                }
            }
        }
    }
    public Object getBean(String beanName){
        return null;
    }
}

结果:
在这里插入图片描述
接下来就是要判断我们所找到的这些class文件到底有没有Component,但是现在我们只是找到了文件而已,其实这样也可以看是不是有Component注解,就是利用一项技术(这个我没有去了解)。最简单的方法就是把这个类加载出来,都加载成class对象,然后就可以直接判断这个类上有没有Component注解。
但是由于类加载 Class<?> clazz = classLoader.loadClass(“com.ding.service.UserService”);括号里面加载的是“com.ding.service.UserService”,而我们之前上述结果是一个很长的路径,因此加载之前我们要进去截取。

absolutePath = absolutePath.substring(absolutePath.indexOf("com"),absolutePath.indexOf(".class"));

截取之后,然后因为是windows要把“\”替换成".",最后传进去

absolutePath = absolutePath.substring(absolutePath.indexOf("com"),absolutePath.indexOf(".class"));
absolutePath = absolutePath.replace("\\",".");
Class<?> clazz = classLoader.loadClass(absolutePath);

然后再进行判断是不是有Component注解。
总结一下代码

package com.spring;

import java.io.File;
import java.lang.annotation.Annotation;
import java.net.URL;

public class DingJunXiaApplicationContext {
    private Class configClass;

    public DingJunXiaApplicationContext(Class configClass) {
        this.configClass = configClass;
        //扫描
        if (configClass.isAnnotationPresent(ComponentScan.class)) {
            ComponentScan componentScanAnnotation = (ComponentScan) configClass.getAnnotation(ComponentScan.class);
            String path = componentScanAnnotation.value();
            path = path.replace(".", "/");
            ClassLoader classLoader = DingJunXiaApplicationContext.class.getClassLoader();
            URL url = classLoader.getResource(path);
            //System.out.println(url);
            File file = new File(url.getFile());
            //如果是一个目录,遍历文件
            if (file.isDirectory()){
                for (File f : file.listFiles()){
                    String absolutePath = f.getAbsolutePath();
                    System.out.println(absolutePath);
                    absolutePath = absolutePath.substring(absolutePath.indexOf("com"),absolutePath.indexOf(".class"));
                    absolutePath = absolutePath.replace("\\",".");
                    try {
                        //把这个类加载出来,都加载成class对象
                        Class<?> clazz = classLoader.loadClass(absolutePath);
                        //但是由于类加载加载的是“com.ding.service.UserService”,因此加载之前我们要进去截取
                        // Class<?> clazz = classLoader.loadClass("com.ding.service.UserService");
                        //判断是否存在Component注解
                        if (clazz.isAnnotationPresent(Component.class)){

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

                }
            }
        }
    }
    public Object getBean(String beanName){
        return null;
    }
}

如果有Component注解,那么就代表这是一个bean,那么知道了之后我们就还要知道这是一个单例bean还是一个原型bean,如果是一个原型的我们不需要创建,所以我们现在判断是不是单例bean。
因此我们需要先看上面有没有Scope注解,要是没有,那就是单例的,如果有,我们要看里面的内容,根据里面的内容判断是单例还是原型,singleton就是单例,prototype就是多例。

package com.spring;

import java.io.File;
import java.lang.annotation.Annotation;
import java.net.URL;

public class DingJunXiaApplicationContext {
    private Class configClass;

    public DingJunXiaApplicationContext(Class configClass) {
        this.configClass = configClass;
        //扫描
        if (configClass.isAnnotationPresent(ComponentScan.class)) {
            ComponentScan componentScanAnnotation = (ComponentScan) configClass.getAnnotation(ComponentScan.class);
            String path = componentScanAnnotation.value();
            path = path.replace(".", "/");
            ClassLoader classLoader = DingJunXiaApplicationContext.class.getClassLoader();
            URL url = classLoader.getResource(path);
            //System.out.println(url);
            File file = new File(url.getFile());
            //如果是一个目录,遍历文件
            if (file.isDirectory()){
                for (File f : file.listFiles()){
                    String absolutePath = f.getAbsolutePath();
                    System.out.println(absolutePath);
                    absolutePath = absolutePath.substring(absolutePath.indexOf("com"),absolutePath.indexOf(".class"));
                    absolutePath = absolutePath.replace("\\",".");
                    System.out.println(absolutePath);
                    try {
                        //把这个类加载出来,都加载成class对象
                        Class<?> clazz = classLoader.loadClass(absolutePath);
                        //但是由于类加载加载的是“com.ding.service.UserService”,因此加载之前我们要进去截取
                        // Class<?> clazz = classLoader.loadClass("com.ding.service.UserService");
                        //判断是否存在Component注解
                        if (clazz.isAnnotationPresent(Component.class)){
                            if (clazz.isAnnotationPresent(Scope.class)){
                                Scope scopeAnnotation = clazz.getAnnotation(Scope.class);
                                String value = scopeAnnotation.value();
                                //这个value也可能是单例也可能是原型
                            }else {
                                //单例
                            }
                        }
                    } catch (ClassNotFoundException e) {
                        e.printStackTrace();
                    }

                }
            }
        }
    }
    public Object getBean(String beanName){
        return null;
    }
}

因为上面判断了,我们后期会用到去测试哈,我们先去写一个Scope注解

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 Scope {
    String value() default "";
}

然后我们在UserService里面也添加这个注解

package com.ding.service;

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

@Component("userService")
@Scope("prototype")
public class UserService {
    public void test() {
        System.out.println("测试一下");
    }
}

假设我们现在已经判断出来了是单例了,那么接下来我们就去创建bean吗(我们先不关心懒加载哈),其实思路是没有问题的,但是有一个非常非常关键的一个点,回到getbean
在这里插入图片描述
getBean的用法就是你传进去一个bean的名字,最后我们要去返回一个对象,----->就是DingJunXiaApplicationContext这个类里面的这块。

public Object getBean(String beanName){
        return null;
    }

传一个字符串,我怎么返回一个对象呢?我们的思路是这样的。
我们根据传的字符串,去找到这个类,不然就找不到这个类怎么返回一个对象呢?
//beanName—>UserService.class---->又得解析这个类,看这个类有没有scope,是不是单例,其实我们在扫描的时候这些我们已经做了啊,结果getBean的时候又做了一遍,这个我们就引入一个非常重要的概念,bean的定义---->BeanDefinition,在这里面有bean的类型是什么(UserService吗),bean的作用域是什么(单例还是原型),bean是不是懒加载的,这些都是bean的定义,在这里我们可以定义bean的所有的属性。

package com.spring;

public class BeanDefinition {
    private Class type;
    private String scope;
    private boolean isLazy;

    public Class getType() {
        return type;
    }

    public void setType(Class type) {
        this.type = type;
    }

    public String getScope() {
        return scope;
    }

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

    public boolean isLazy() {
        return isLazy;
    }

    public void setLazy(boolean lazy) {
        isLazy = lazy;
    }
}

所以我们把刚才判断的那个代码改一下去。如果扫描过程中发现有Component,说明这是一个bean,如果要是一个bean,我们就要去创建bean的定义,设置bean的scope以及bean的Type。
在这里插入图片描述

除此之外还有就是在内部还有一个map,在spring源码中也很重要,存的是BeanDefinition,我们每扫描完一个bean就有一个BeanDefinition,然后存在map里面,map中的key是bean的名字,value是BeanDefinition。为什么要有这个Map后面又讲。
在这里插入图片描述
在这里插入图片描述
为了方便,我们单独把这个方法抽象出来,作为一个扫描方法,全部代码如下:

package com.spring;

import java.io.File;
import java.lang.annotation.Annotation;
import java.net.URL;
import java.util.HashMap;
import java.util.Map;

public class DingJunXiaApplicationContext {
    private Class configClass;
    private Map<String,BeanDefinition> beanDefinitionMap = new HashMap<>();

    public DingJunXiaApplicationContext(Class configClass) {
        this.configClass = configClass;
        //扫描
        scan(configClass);
    }
    
    public Object getBean(String beanName){
        return null;
    }


    private void scan(Class configClass) {
        if (configClass.isAnnotationPresent(ComponentScan.class)) {
            ComponentScan componentScanAnnotation = (ComponentScan) configClass.getAnnotation(ComponentScan.class);
            String path = componentScanAnnotation.value();
            path = path.replace(".", "/");
            ClassLoader classLoader = DingJunXiaApplicationContext.class.getClassLoader();
            URL url = classLoader.getResource(path);
            //System.out.println(url);
            File file = new File(url.getFile());
            //如果是一个目录,遍历文件
            if (file.isDirectory()){
                for (File f : file.listFiles()){
                    String absolutePath = f.getAbsolutePath();
                    System.out.println(absolutePath);
                    absolutePath = absolutePath.substring(absolutePath.indexOf("com"),absolutePath.indexOf(".class"));
                    absolutePath = absolutePath.replace("\\",".");
                    System.out.println(absolutePath);
                    try {
                        //把这个类加载出来,都加载成class对象
                        Class<?> clazz = classLoader.loadClass(absolutePath);
                        //但是由于类加载加载的是“com.ding.service.UserService”,因此加载之前我们要进去截取
                        // Class<?> clazz = classLoader.loadClass("com.ding.service.UserService");
                        //判断是否存在Component注解
                        if (clazz.isAnnotationPresent(Component.class)){
                            Component componentAnnotation = clazz.getAnnotation(Component.class);
                            String beanName = componentAnnotation.value();
                            BeanDefinition beanDefinition = new BeanDefinition();
                            beanDefinition.setType(clazz);
                            if (clazz.isAnnotationPresent(Scope.class)){
                                Scope scopeAnnotation = clazz.getAnnotation(Scope.class);
                                String value = scopeAnnotation.value();
                                //这个value也可能是单例也可能是原型,我们拿到什么我们就设置什么
                                beanDefinition.setScope(value);
                            }else {
                                //单例
                                beanDefinition.setScope("singleton");
                            }
                            beanDefinitionMap.put(beanName,beanDefinition);
                        }
                    } catch (ClassNotFoundException e) {
                        e.printStackTrace();
                    }

                }
            }
        }
    }
}

写到现在了,那我们总结一下这个扫描做了什么事情。解析得到扫描路径,遍历路径下的每一class文件,加载每一个class文件得到一个class对象,然后判断有没有Component注解,然后去解析bean的名字
,scope注解,得到BeanDefinition,然后存在Map中。
那么这个beanDefinitionMap到底有什么用呢?如果Map里面没有这个名字,代表我们压根就没有定义过这个bean。这个Map代表当前整个项目里面到底定义了哪些bean。

public Object getBean(String beanName){
        if (!beanDefinitionMap.containsKey(beanName)){
            throw new NullPointerException();
        }
        BeanDefinition beanDefinition = beanDefinitionMap.get(beanName);
        if (beanDefinition.getScope().equals("singleton")){

        }else {
            //原型
        }
        return null;
    }

做到这该做的都做完了,也扫描完了,那就该创建bean了,那我们开始
就是扫描----->遍历寻找单例bean----->创建bean

public DingJunXiaApplicationContext(Class configClass) {
        this.configClass = configClass;
        //扫描
        scan(configClass);
        for (Map.Entry<String, BeanDefinition> entry : beanDefinitionMap.entrySet()) {
            String beanName = entry.getKey();
            BeanDefinition beanDefinition = entry.getValue();
            if (beanDefinition.getScope().equals("singleton")){
                Object bean = createBean(beanName, beanDefinition);
            }
        }

    }
    //创建bean
    private Object createBean(String beanName,BeanDefinition beanDefinition){

    }

在这里插入图片描述
在我们把单例bean创建出来,还要进行保存,引入单例池。把创建好的单例bean存进去,存进去有什么好处?回头去getBean的时候不用创建了,直接去单例池去拿就行了。
在这里插入图片描述
在这里插入图片描述
现在来完成创建bean,要想创建bean,还要根据类去创建,那么就要先找到他的类在哪里?找到类后,这里为了简单,我们直接使用空参的构造方法

//创建bean
    private Object createBean(String beanName,BeanDefinition beanDefinition){
        Class clazz = beanDefinition.getType();
        Object instance = null;
        try {
            instance = clazz.getConstructor().newInstance();
        } catch (InstantiationException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (InvocationTargetException e) {
            e.printStackTrace();
        } catch (NoSuchMethodException e) {
            e.printStackTrace();
        }
        return instance;
    }

写到这,我们就可以测试一下啦。
单例:
在这里插入图片描述
多例:
在这里插入图片描述
其实我们在写的过程中一直遗漏了一个问题,就是在我写@Component注解的时候,我们在service那块没写名字,会如何呢?肯定会报错啊,报错图:
在这里插入图片描述

因为之前我们在使用别人写好的spring的时候,如果@Component后面没有传值,他会有一个默认值,就是类的名字首字母小写,因此我们也可以加进去并进行判断。
在这里插入图片描述
所以我们也去设置默认值:
在这里插入图片描述
然后我们再来测试一下:
在这里插入图片描述

4.2 依赖注入

在UserService里面加入OrderService,然后再这个属性上加上注解,加注解前先自己写一个@Autowired注解:

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.FIELD)
public @interface Autowired {

}
package com.ding.service;

import com.spring.Autowired;
import com.spring.Component;
import com.spring.Scope;

@Component("userService")
@Scope("singleton")
public class UserService {
    @Autowired
    private OrderService orderService;
    public void test() {
        System.out.println(orderService);
    }
}

这个时候有值吗?属性赋值成功了吗?测试一下:
在这里插入图片描述
显然是空值,没有值
依赖注入是在创建bean的过程会去依赖注入。因此我们在createBean这个方法中进行依赖注入。
因此我们需要遍历属性,看哪个属性上面有@Autowired,如果有就去给这个字段去赋值。赋值之前记得反射。

    //创建bean
    private Object createBean(String beanName,BeanDefinition beanDefinition){
        Class clazz = beanDefinition.getType();
        Object instance = null;
        try {
            instance = clazz.getConstructor().newInstance();
            for (Field field : clazz.getDeclaredFields()){
                if (field.isAnnotationPresent(Autowired.class)){
                    field.setAccessible(true);
                    field.set(instance,getBean(field.getName()));
                }
            }
        } catch (InstantiationException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (InvocationTargetException e) {
            e.printStackTrace();
        } catch (NoSuchMethodException e) {
            e.printStackTrace();
        }
        return instance;
    }

那赋是赋什么值呢,需要根据当前字段的名字找。循环依赖,这个不在这次的分享范围内。
流程上是先扫描了,假设创建userService这个bean,进行到了createBean,而内部注入的orderService还没有生成,我们会拿到一个空;所以我们可以加一个判断,如果为空,就创建bean。

    public Object getBean(String beanName){
        if (!beanDefinitionMap.containsKey(beanName)){
            throw new NullPointerException();
        }
        BeanDefinition beanDefinition = beanDefinitionMap.get(beanName);
        if (beanDefinition.getScope().equals("singleton")){
            Object singletonBean = singletonObjects.get(beanName);
            if (singletonBean == null){
                singletonBean = createBean(beanName,beanDefinition);
                singletonObjects.put(beanName,singletonBean);
            }
            return singletonBean;
        }else {
            //原型
            Object prototypeBean = createBean(beanName, beanDefinition);
            return prototypeBean;
        }
    }

结果:
在这里插入图片描述
DingJunXiaApplicationContext.java总代码

package com.spring;

import java.beans.Introspector;
import java.io.File;
import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.net.URL;
import java.util.HashMap;
import java.util.Map;

public class DingJunXiaApplicationContext {
    private Class configClass;
    private Map<String,BeanDefinition> beanDefinitionMap = new HashMap<>();
    private Map<String,Object> singletonObjects = new HashMap<>();
    public DingJunXiaApplicationContext(Class configClass) {
        this.configClass = configClass;
        //扫描
        scan(configClass);
        for (Map.Entry<String, BeanDefinition> entry : beanDefinitionMap.entrySet()) {
            String beanName = entry.getKey();
            BeanDefinition beanDefinition = entry.getValue();
            if (beanDefinition.getScope().equals("singleton")){
                Object bean = createBean(beanName, beanDefinition);
                singletonObjects.put(beanName,bean);
            }
        }
    }
    //创建bean
    private Object createBean(String beanName,BeanDefinition beanDefinition){
        Class clazz = beanDefinition.getType();
        Object instance = null;
        try {
            instance = clazz.getConstructor().newInstance();
            for (Field field : clazz.getDeclaredFields()){
                if (field.isAnnotationPresent(Autowired.class)){
                    field.setAccessible(true);
                    field.set(instance,getBean(field.getName()));
                }
            }
        } catch (InstantiationException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (InvocationTargetException e) {
            e.printStackTrace();
        } catch (NoSuchMethodException e) {
            e.printStackTrace();
        }
        return instance;
    }

    public Object getBean(String beanName){
        if (!beanDefinitionMap.containsKey(beanName)){
            throw new NullPointerException();
        }
        BeanDefinition beanDefinition = beanDefinitionMap.get(beanName);
        if (beanDefinition.getScope().equals("singleton")){
            Object singletonBean = singletonObjects.get(beanName);
            if (singletonBean == null){
                singletonBean = createBean(beanName,beanDefinition);
                singletonObjects.put(beanName,singletonBean);
            }
            return singletonBean;
        }else {
            //原型
            Object prototypeBean = createBean(beanName, beanDefinition);
            return prototypeBean;
        }
    }


    private void scan(Class configClass) {
        if (configClass.isAnnotationPresent(ComponentScan.class)) {
            ComponentScan componentScanAnnotation = (ComponentScan) configClass.getAnnotation(ComponentScan.class);
            String path = componentScanAnnotation.value();
            path = path.replace(".", "/");
            ClassLoader classLoader = DingJunXiaApplicationContext.class.getClassLoader();
            URL url = classLoader.getResource(path);
            //System.out.println(url);
            File file = new File(url.getFile());
            //如果是一个目录,遍历文件
            if (file.isDirectory()){
                for (File f : file.listFiles()){
                    String absolutePath = f.getAbsolutePath();
                    //System.out.println(absolutePath);
                    absolutePath = absolutePath.substring(absolutePath.indexOf("com"),absolutePath.indexOf(".class"));
                    absolutePath = absolutePath.replace("\\",".");
                    //System.out.println(absolutePath);
                    try {
                        //把这个类加载出来,都加载成class对象
                        Class<?> clazz = classLoader.loadClass(absolutePath);
                        //但是由于类加载加载的是“com.ding.service.UserService”,因此加载之前我们要进去截取
                        // Class<?> clazz = classLoader.loadClass("com.ding.service.UserService");
                        //判断是否存在Component注解
                        if (clazz.isAnnotationPresent(Component.class)){
                            Component componentAnnotation = clazz.getAnnotation(Component.class);
                            String beanName = componentAnnotation.value();
                            if ("".equals(beanName)){
                                beanName = Introspector.decapitalize(clazz.getSimpleName());
                            }
                            BeanDefinition beanDefinition = new BeanDefinition();
                            beanDefinition.setType(clazz);
                            if (clazz.isAnnotationPresent(Scope.class)){
                                Scope scopeAnnotation = clazz.getAnnotation(Scope.class);
                                String value = scopeAnnotation.value();
                                //这个value也可能是单例也可能是原型,我们拿到什么我们就设置什么
                                beanDefinition.setScope(value);
                            }else {
                                //单例
                                beanDefinition.setScope("singleton");
                            }
                            beanDefinitionMap.put(beanName,beanDefinition);
                        }
                    } catch (ClassNotFoundException e) {
                        e.printStackTrace();
                    }

                }
            }
        }
    }
}

4.3 模拟初始化InitializingBean +BeanPostProcessor

spring bean的创建流程,依赖注入完,还涉及到了初始化的东西,也来实现一下。

4.3.1 接口实现InitializingBean

首先创建一个接口

package com.spring;

public interface InitializingBean {
    void afterPropertiesSet();
}

再去实例化一个接口

package com.ding.service;

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

@Component("userService")
@Scope("prototype")
public class UserService implements InitializingBean {
    @Autowired
    private OrderService orderService;
    public void test() {
        System.out.println(orderService);
    }

    @Override
    public void afterPropertiesSet() {
        System.out.println("初始化");
    }
}

但是目前这个方法能自动被调到妈?我们还调用不到,因为底层还没有实现,实现还是在创建bean的过程中进行实现,依赖注入完之后spring就支持这个功能,就可以进行实现。
判断当前bean的实例instanceof有没有实现InitializingBean 这个接口,如果实现了把当前这个实例对象强制转化成为这个接口的类型
在这里插入图片描述
运行:
在这里插入图片描述

4.3.2 BeanPostProcessor

下面就是spring中特别重要的一个东西BeanPostProcessor ,这个接口有两个方法 postProcessBeforeInitialization 和 postProcessAfterInitialization,也就是初始化前与初始化后,这个接口的方法,是针对于所有的bean的,也就是说,如果实现了这个接口,所有的bean都会调用重写的这两个方法
定义接口

package com.spring;

public interface BeanPostProcessor {
    default Object postProcessBeforeInitialization(Object bean,String beanName){
        return bean;
    }
    default Object postProcessAfterInitialization(Object bean,String beanName){
        return bean;
    }
}

实现接口

package com.ding.service;

import com.spring.BeanPostProcessor;

public class DingJunXiaBeanPostProcessor implements BeanPostProcessor {
    @Override
    public Object postProcessAfterInitialization(Object bean, String beanName) {
        
        return BeanPostProcessor.super.postProcessAfterInitialization(bean,beanName);
    }
}

这个方法里面我们就可以写一些我们想实现的功能,按照逻辑我们这个方法应该在初始化后调用该方法,那怎么调呢?按照我们现在这个代码
在这里插入图片描述
这样逻辑是没有错的,但是这样写合适吗?Spring会这样直接new出来吗?Spring会怎么做呢?是的,用接口。Spring要想调用这个方法,首先就是要拿到这个类的一个实例,Spring得知道你写了这个类,那就是需要加一个Component,扫描发现有一个Component。

package com.ding.service;

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

@Component
public class DingJunXiaBeanPostProcessor implements BeanPostProcessor {
    @Override
    public Object postProcessAfterInitialization(Object bean, String beanName) {
        System.out.println(beanName);
        //return BeanPostProcessor.super.postProcessAfterInitialization(bean,beanName);
        return bean;
    }
}

那现在就要去看扫描代码,扫描的时候发现有一个@Component注解,这个时候就会去判断这个类是不是实现的BeanPostprocessor这个接口,如果是就构造实例化出来,因为是在创建得时候用所以要进行缓存,
在这里插入图片描述
在这里插入图片描述
在创建bean里面遍历去调函数
在这里插入图片描述
结果展示:
在这里插入图片描述
如果我们想针对某一个方法,可以直接使用if语句
在这里插入图片描述
因为这个方法是有返回值的,我们还需要改一下,到时候直接返回instance
在这里插入图片描述
Aop就是基于BeanPostProcessor
那么我们现在就针对userService去实现一下,
使用jdk的代理对象,我们还要先去写一个接口

package com.ding.service;

public interface UserInterface {
    public void test();
}

接口实现

package com.ding.service;

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

@Component("userService")
@Scope("prototype")
public class UserService implements UserInterface {
    @Autowired
    private OrderService orderService;
    public void test() {
        System.out.println(orderService);
    }
}

代理

package com.ding.service;

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

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

@Component
public class DingJunXiaBeanPostProcessor implements BeanPostProcessor {
    @Override
    public Object postProcessAfterInitialization(Object bean, String beanName) {
        if (beanName.equals("userService")){
            Object proxyInstance = Proxy.newProxyInstance(DingJunXiaBeanPostProcessor.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 proxyInstance;
            //System.out.println(beanName);
        }
        //return BeanPostProcessor.super.postProcessAfterInitialization(bean,beanName);
        return bean;
    }
}

测试

package com.ding;

import com.ding.service.UserInterface;
import com.ding.service.UserService;
import com.spring.DingJunXiaApplicationContext;

public class Test {
    public static void main(String[] args) {

        DingJunXiaApplicationContext applicationContext = new DingJunXiaApplicationContext(AppConfig.class);

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

        userService.test();
    }
}

结果
在这里插入图片描述

5. 拓展:

我希望在我自己的项目中定义一个注解,叫DingJunXiaValue

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.FIELD)
public @interface DingJunXiaValue {
    String value() default "";
}

在UserService上进行使用,实现的功能是:@DIngJunXiaValue里面传的值是多少就赋值给这个属性

package com.ding.service;

import com.spring.*;

@Component("userService")
@Scope("prototype")
public class UserService implements UserInterface {
    @Autowired
    private OrderService orderService;
    
    @DingJunXiaValue("XXX")
    private String test;
    public void test() {
        System.out.println(test);
    }

    /*@Override
    public void afterPropertiesSet() {
        System.out.println("初始化");
    }*/
}

添加在DingJunXiaApplicationContext
在这里插入图片描述
在DingJunXiaValueBeanPostProcessor.java实现before

package com.ding.service;

import com.spring.BeanPostProcessor;
import com.spring.Component;
import com.spring.DingJunXiaValue;

import java.lang.reflect.Field;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

@Component
public class DingJunXiaBeanPostProcessor implements BeanPostProcessor {

    @Override
    public Object postProcessAfterInitialization(Object bean, String beanName) {

       if (beanName.equals("userService")){
            Object proxyInstance = Proxy.newProxyInstance(DingJunXiaBeanPostProcessor.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 proxyInstance;
            //System.out.println(beanName);
        }
        //return BeanPostProcessor.super.postProcessAfterInitialization(bean,beanName);
        return bean;
    }

    @Override
    public Object postProcessBeforeInitialization(Object bean, String beanName) throws IllegalAccessException {
        for (Field field : bean.getClass().getDeclaredFields()) {
            if (field.isAnnotationPresent(DingJunXiaValue.class)){
                field.setAccessible(true);
                try {
                    field.set(bean,field.getAnnotation(DingJunXiaValue.class).value());
                } catch (IllegalAccessException e) {
                    e.printStackTrace();
                }
            }
        }
        return bean;
    }
}

结果
在这里插入图片描述
再来模拟一下BeanNameAware接口(如果一个bean想知道自己的名字的话就用这个)

package com.ding.service;

import com.spring.*;

@Component
public class UserService implements UserInterface,BeanNameAware {
    @Autowired
    private OrderService orderService;

    @DingJunXiaValue("test01010101")
    private String test;

    private String beanName;

    @Override
    public void setBeanName(String name) {
        this.beanName = name;
    }
    public void test() {
        System.out.println(beanName);
        System.out.println(test);
        //System.out.println("pppp");
    }
}

在这里插入图片描述
结果
在这里插入图片描述

;