依赖注入
引&目标
本篇是《跟我猜 Spring-Boot》系列的第二篇 (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 的更改主要在如下几点:
定义 PostConstruct 和 Autowired
以 Autowired 为标记, 实现依赖注入
以 PostContruct 为标记, 实现 Bean 在创建的自动初始化
以 3 条是我们这次要实现的直观目标, 然而, 由于我们之间过于简单的设计, 我们有一个问题要解决, 即:
我们需要有地方可以存储, 查找已经已经生的 bean !
那么这个问题显然要比 1,2,3 条更重要一些, 于是
前文挖坑, 后文填坑
我们的目标变成了:
实现 Bean 的存储和管理
定义 PostConstruct 和 Autowired
以 Autowired 为标记, 实现依赖注入
以 PostContruct 为标记, 实现 Bean 在创建的自动初始化
Step 0 Bean 的存储和管理
在 Spring 中, 从外部得到一个 bean 方式下:
通过依赖注入或其他方式得到 ApplicationContext
通过
ApplicationContext.getBean
来得到相应的 bean.
通过这两条, 显而易见:
我们也照般一个 ApplicationContext
而通过类名或类拿到 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, 保存 beean 的功能完成了 (只是把前人的坑填了而已), 下一步要开始我们正式的工作了.
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 ....
一切已经非常水到渠成了, 又双叒叕有什么好激动的? 我们要做的无非就是:
把所有的 bean 从 context 里取出来;
用反射得到 bean 的每一个字段;
检查这个字段有没有加 autowird 注解;
如果有, 检查这个字段类型是不是一个 bean;
如果是, 取出来用反射进行赋值;
因为我们用的是 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();
- }
- }
注意这里面有一个点, 即我们循环了两次:
第一次是生成 bean 并存储
第二次是将所有的 bean 取出来, 用依赖注入的形式为每个 bean 进行赋值
这样做的原因也很明显:
为了保证在注入时可以拿到我们想要的 bean, 必须在所有的 bean 都生成后进行处理
Step3 以 @PostConstruct 为标记, 实现初始化方法的自动运行
到上一步, 我们的依赖注入实际上已经完成了, 但是由于目前我们 mini-boot 这个框架太简单了, 根本没办法验证我们成功与否.
所以, 我们在这里顺带将 @PostContruct 的机制也实现一下, 即类加载后的自动初始化.
有了上一步的工作, 你会发现, 我们这次的工作变得非常程式化和无味了, 即:
把所有的 bean 从 context 里取出来;
用反射得到 bean 的每一个方法;
检查这个方法有没有加 PostContruct 注解;
如果有, 使用反射执行这个方法;
你一定会注意到, 这里我有一些字体是标粗的, 标粗的原因是因为: 这里的 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 已经不为空了, 并且就是我们之间自动创建的那个!
也就是说:
我们已经按照预期实现了简单的自动注入
我们同时按照预期实现了简单的自动运行初始化函数
现在, 静静的回想一下我们此文的内容, 是否有某种大门, 已经向您敞开 :)
其他
不给源码的分享都是耍流氓!
所以, 我们的项目地址是:
https://github.com/yfge/mini-boot
由于, 随着文章的发布, 本代码会不停的更新, 所以, 本章的 tag 是: article-02(原谅我起名字的水平)
来源: https://www.cnblogs.com/geyunfei/p/12441636.html