跟我猜Spring-boot:依赖注入

软件发布|下载排行|最新软件

当前位置:首页IT学院IT技术

跟我猜Spring-boot:依赖注入

葛云飞   2020-03-08 我要评论
# 依赖注入 ## 引&目标 本篇是《跟我猜Spring-Boot》系列的第二篇(Oh,我竟然已经写了10篇了,真不容易)。 在上一篇中,我们实现了*Bean*的创建,但是仅仅是创建而已,并没有真正的实现Bean的注入。那么在今天这篇中,我们要去实现bean的自动注入。 我们之前已经在工程中定义了`SimpleService`和`SimpleController`这两个类,那么这篇文章,我们要把`SimpleService`自动注入到`SimpleController`中; **SimpleController.java** ```java @Service public class SimpleController { @Autowired private SimpleService simpleService; public SimpleController(){ System.out.println("the controller is created!"); } } ``` 因为目前只是一个控制台程序,没办法进行真正的调用和展示,所以我给`SimpleService`加一个输出,用来表示这个类的的唯一性。 这样,`SimpleService`就变成了这样: **SimpleService.java** ```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** ```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条更重要一些,于是 > 前文挖坑,后文填坑 我们的目标变成了: 0. **实现Bean的存储和管理** 1. 定义`PostConstruct`和`Autowired` 2. 以`Autowired`为标记,实现依赖注入 3. 以`PostContruct`为标记,实现Bean在创建的自动初始化 ## Step 0 Bean的存储和管理 在Spring中,从外部得到一个bean 方式下: 1. 通过依赖注入或其他方式得到`ApplicationContext` 2. 通过`ApplicationContext.getBean`来得到相应的bean. 通过这两条,显而易见: 1. 我们也照般一个`ApplicationContext` 2. 而通过类名或类拿到bean这种逻辑,显然是一个map.这样,我们的`ApplicationContext`变得很好实现: **ApplicationContext.java** ```java package com.github.yfge.miniboot.context; import java.util.Collection; import java.util.LinkedHashMap; import java.util.Map; public class ApplicationContext { private Map 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** ```java public class Application{ // other code private static void LoadBeans(Class source) { ClassUtils util = new ClassUtils(); List 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,保存beean的功能完成了(只是把前人的坑填了而已),下一步要开始我们正式的工作了。 ## Step1 定义Annotation 之前已经分析了,我们需要定义`Autowired`和`PostContruct`两个注解,都简单的很,做一下声明即可: **Autowired.java** ```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** ```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** ```java 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** ```java public class ApplicationContext { //other code public Collection getAllBeans(){ return this.beanMap.values(); } } ``` 注意这里面有一个点,即我们循环了两次: * 第一次是生成bean并存储 * 第二次是将所有的bean取出来,用依赖注入的形式为每个bean进行赋值 这样做的原因也很明显: **为了保证在注入时可以拿到我们想要的bean,必须在所有的bean都生成后进行处理** ## Step3 以@PostConstruct为标记,实现初始化方法的自动运行 到上一步,我们的依赖注入实际上已经完成了,但是由于目前我们`mini-boot`这个框架太简单了,根本没办法验证我们成功与否。 所以,我们在这里顺带将@PostContruct的机制也实现一下,即类加载后的自动初始化。 有了上一步的工作,你会发现,我们这次的工作变得非常程式化和无味了,即: 1. 把所有的bean从context里取出来; 2. 用反射得到bean的每一个**方法**; 3. 检查这个**方法**有没有加**PostContruct**注解; 4. 如果有,**使用反射执行这个方法**; 你一定会注意到,这里我有一些字体是标粗的,标粗的原因是因为:这里的1-4是**我从上面复制下来的,标粗的只是更改的部分**。 即然实现思路都能复制,那么代码也变得很容易了。 **Application.loadBeans** ```java 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 ,现在编译工程,执行,你会看到如下输出: ```bash 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`**(原谅我起名字的水平)

Copyright 2022 版权所有 软件发布 访问手机版

声明:所有软件和文章来自软件开发商或者作者 如有异议 请与本站联系 联系我们