跟我猜spring-boot:bean的注入

211 阅读7分钟
原文链接: mp.weixin.qq.com

引&目标

本篇是《跟我猜Spring-Boot》系列的第2篇(Oh,我竟然已经写了10篇了,真不容易)。

在上一篇中,我们实现了Bean的创建,但是仅仅是创建而已,并没有真正的实现Bean的注入。那么在今天这篇中,我们要去实现bean的自动注入。

我们之前已经在工程中定义了 SimpleService和  SimpleController这两个类,那么这篇文章,我们要把  SimpleService自动注入到  SimpleController中;

SimpleController.java

    @Service

    public class SimpleController {

    @Autowired

    private SimpleService simpleService;

    public SimpleController(){

    System.out.println("the controller is created!");

    }

    }

因为目前只是一个控制台程序,没办法进行真正的调用和展示,所以我给 SimpleService加一个输出,用来表示这个类的的唯一性。这样, SimpleService就变成了这样:

SimpleService.java

    @Service

    public class SimpleService {

    private String serviceId ;

    public SimpleService(){

    serviceId= UUID.randomUUID().toString();

    System.out.println("the service :"+serviceId+"is created!");

    }

    public String getServiceId(){

    return this.serviceId;

    }

    }

现在虽然有了调用,但是还是没有办法去验证我们的想法。

干脆,将计就计,我们再加上一个 PostContruct的注解吧 :)

SimpleController.java

    public class SimpleController{

    // other code

    @PostContruct

    public void init(){

    System.out.println("the service id is :"+this.simpleService.getServiceId());

    }

    }

依赖注入的需求分析

由我们目标程序的更改,可以看出我们这次对 mini-boot的更改主要在如下几点:

  1. 定义 PostConstruct和  Autowired

  2. 以 Autowired为标记,实现依赖注入

  3. 以 PostContruct为标记,实现Bean在创建的自动初始化

以3条是我们这次要实现的直观目标,然而,由于我们之间过于简单的设计,我们有一个问题要解决,即:

我们需要有地方可以存储,查找已经已经生的bean !

那么这个问题显然要比1,2,3条更重要一些,于是

前文挖坑,后文填坑

我们的目标变成了:

  1. 实现Bean的存储和管理

  2. 定义 PostConstruct和  Autowired

  3. 以 Autowired为标记,实现依赖注入

  4. 以 PostContruct为标记,实现Bean在创建的自动初始化

Step 0 Bean的存储和管理

在Spring中,从外部得到一个bean 方式下:

  1. 通过依赖注入或其他方式得到 ApplicationContext

  2. 通过 ApplicationContext .getBean来得到相应的bean.

通过这两条,显而易见:

  1. 我们也照般一个 ApplicationContext

  2. 而通过类名或类拿到bean这种逻辑,显然是一个map.这样,我们的 ApplicationContext变得很好实现:

ApplicationContext.java

    package com.github.yfge.miniboot.context;

    import java.util.Collection;

    import java.util.LinkedHashMap;

    import java.util.Map;

    public class ApplicationContext {

    private Map<String,Object> beanMap ;

    public ApplicationContext(){

    beanMap=new LinkedHashMap<>();

    }

    public void addBean( Object ob){

    this.beanMap.put(ob.getClass().getName(),ob);

    }

    public Object getBean(String beanName){

    return this.beanMap.get(beanName);

    }

    public Object getBean(Class beanClass){

    return this.beanMap.get(beanClass.getName());

    }

    }

嗯,有了 ApplicationContext之后,我们就可以在生成bean的同时用一个applicationContext把所有的bean都保存起来。这时,我们的  Application .loadBeans的函数有了一点点变化:

Application.loadBeans

    public class Application{

    // other code

    private static void LoadBeans(Class source) {

    ClassUtils util = new ClassUtils();

    List<String> classNames = util.loadClass(source);

    /** 实例化一个context **/

    ApplicationContext applicationContext = new ApplicationContext();

    for (String name : classNames) {

    try {

    var classInfo = Class.forName(name);

    /**

    * 检查是否声明了@Service

    **/

    if (classInfo.getDeclaredAnnotation(Service.class) != null) {

    /**

    * 得到默认构造函数

    */

    var constructor = classInfo.getConstructor();

    if (constructor != null) {

    /**

    * 创建实例

    */

    var obj = constructor.newInstance();

    /** 保存bean**/

    applicationContext.addBean(obj);

    }

    }

    } catch (Throwable e) {

    e.printStackTrace();

    }

    }

    }

    //other code

    }

可以看到,这时的变化还很小,我们只是在开始初始化了一个context ,然后在bean生成后,将bean保存在context中

OK,保存bean的功能完成了(只是把前人的坑填了而已),下一步要开始我们正式的工作了。

Step1 定义Annotation

之前已经分析了,我们需要定义 Autowired和  PostContruct两个注解,都简单的很,做一下声明即可:

Autowired.java

    package com.github.yfge.miniboot.autoconfigure;

    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 {

    }

PostConstruct.java

    package com.github.yfge.miniboot.autoconfigure;

    import java.lang.annotation.Retention;

    import java.lang.annotation.RetentionPolicy;

    @Retention(RetentionPolicy.RUNTIME)

    public @interface PostConstruct {

    }

同样注意,我们在这里要加入 @Retention(RetentionPolicy.RUNTIME)这一行,具体的功能和区别,咱会找机会详解(又在挖坑,不挖不舒服基)

Step2 以@Autowired为标记,实现依赖注入

嗯,我们要进行最激动人心的部分了,实现依赖注入,即我们要将 SimpleService自动 注入到 SimpleController中。

But ....

一切已经非常水到渠成了,又双叒叕有什么好激动的?我们要做的无非就是:

  1. 把所有的bean从context里取出来;

  2. 用反射得到bean的每一个字段;

  3. 检查这个字段有没有加autowird注解;

  4. 如果有,检查这个字段类型是不是一个bean;

  5. 如果是,取出来用反射进行赋值;

因为我们用的是Map进行的存储,所以4,5两步可以合并为:

4-5. 按这个字段类型取到bean,如果不为空,就赋值。

因为想的清楚,代码自然一气呵成,即在 Application.loadBeans后面增加这个注入的逻辑:

Application.loadBeans

    public class Application {

    /**

    * 加载相应的bean(Service)

    *

    * @param source

    */

    private static void LoadBeans(Class source) {

    //other code

    //上面是之前的逻辑

    /** 注入bean **/

    for (Object ob : applicationContext.getAllBeans()) {

    var classInfo = ob.getClass();

    for (var field : classInfo.getDeclaredFields()) {

    if (field.getDeclaredAnnotation(Autowired.class) != null) {

    field.setAccessible(true);

    var filedBean = applicationContext.getBean(field.getType());

    if (filedBean != null) {

    try {

    field.set(ob, filedBean);

    } catch (IllegalAccessException e) {

    e.printStackTrace();

    }

    }

    }

    }

    }

    }

    //other code

    }

同时,我们为ApplicationContext加了一个新的方法,用来得到所有的bean

ApplicationContext.java

    public class ApplicationContext {

    //other code

    public Collection<Object> getAllBeans(){

    return this.beanMap.values();

    }

    }

注意这里面有一个点,即Applicaton.loadBeans我们循环了两次:

  • 第一次是生成bean并存储

  • 第二次是将所有的bean取出来,用依赖注入的形式为每个bean进行赋值

这样做的原因也很明显:

为了保证在注入时可以拿到我们想要的bean,必须在所有的bean都生成后进行处理

Step3 以@PostConstruct为标记,实现初始化方法的自动运行

到上一步,我们的依赖注入实际上已经完成了,但是由于目前我们 mini-boot这个框架太简单了,根本没办法验证我们成功与否。

所以,我们在这里顺带将@PostContruct的机制也实现一下,即类加载后的自动初始化。

有了上一步的工作,你会发现,我们这次的工作变得非常程式化和无味了,即:

  1. 把所有的bean从context里取出来;

  2. 用反射得到bean的每一个方法;

  3. 检查这个方法有没有加PostContruct注解;

  4. 如果有,使用反射执行这个方法;

你一定会注意到,这里我有一些字体是标粗的,标粗的原因是因为:这里的1-4是我从上面复制下来的,标粗的只是更改的部分

即然实现思路都能复制,那么代码也变得很容易了。

Application.loadBeans

    public class Application {

    /**

    * 加载相应的bean(Service)

    *

    * @param source

    */

    private static void LoadBeans(Class source) {

    //other code

    //上面是之前的逻辑

    //包括

    //1. 生成所有的bean并存储

    //2. 遍历已经生成的bean进行依赖注入

    /** 执行初始化方法 **/

    for (Object ob : applicationContext.getAllBeans()) {

    var classInfo = ob.getClass();

    if (classInfo.getDeclaredAnnotation(Service.class) != null) {

    for (var method : classInfo.getDeclaredMethods()) {

    if (method.getDeclaredAnnotation(PostConstruct.class) != null) {

    try {

    method.invoke(ob);

    } catch (IllegalAccessException e) {

    e.printStackTrace();

    } catch (InvocationTargetException e) {

    e.printStackTrace();

    }

    }

    }

    }

    }

    }

    // other code

    }

OK ,现在编译工程,执行,你会看到如下输出:

    the controller is created!

    the service :580fbd69-d82b-44b6-847a-b4c5cbc4d97b is created!

    the service Id is :580fbd69-d82b-44b6-847a-b4c5cbc4d97b

    The Mini-Boot Application Is Run! The Name is Hello

这个输出中:

  • 第一行是controller自动创建

  • 第二行表时service自动创建,并且唯一id 是 xxxx

  • 第三行是我们的controller 中init函数的输出,可以看到 这里service 已经不为空了,并且就是我们之间自动创建的那个!

也就是说:

  1. 我们已经按照预期实现了简单的自动注入

  2. 我们同时按照预期实现了简单的自动运行初始化函数

现在,静静的回想一下我们此文的内容,是否有某扇大门,已经向您敞开 

其他

不给源码的分享都是耍流氓!

所以,我们的项目地址是:https://github.com/yfge/mini-boot 由于,随着文章的发布,本代码会不停的更新,所以,本章的tag是article-02(原谅我起名字的水平)

关于老拐瘦

  • 散养程序猿,野生架构狮

  • 二流搬砖工,三流摄影师

  • 假正经真逗比,装文艺实二逼

所以,这么一个公众号里,会有代码,有段子,有美图,有鸡汤,反正,乱七八遭的,没准碰上哪个刚好就烦到您了呢

啥也不说,扫码关注吧