依赖注入 - 如何使用反射和注解实现简单的依赖注入

本文介绍了依赖注入的概念及其好处,并通过Cute Koala框架展示了如何使用注解和反射实现依赖注入。文章详细阐述了框架的配置文件、模块创建、Bean注册和依赖解析的过程,以及框架的其他功能,如模板生成和Http RPC代理。
摘要由CSDN通过智能技术生成

Github源码 - > Anddd7/cute-koala

Spring Bean 应该是Spring被使用最多的功能 ,通过注解/xml配置 ,可以简化大量的依赖代码 ,同时也提高了效率 .在试用了Guice和阅读了部分Spring BeanFactory的源码后 ,也了解了一部分依赖注入的原理 ,自己写了一个小框架来印证一下.

依赖注入

依赖注入 ,也就是所依赖的对象不由自己处理 ,而是由外部系统去分配 .就像排队取餐时 ,不是每个人自己去盛菜 ,而是告诉服务员自己需要什么 ,由服务员准备好给你 .

这样的好处在于 ,我们不再需要 ‘盛菜’(new) 这个动作 ,只需要告诉 ‘服务员’(DI 容器) 我需要 ‘菜’(依赖的对象) 就可以了 .
对于很多情况下 ,需要的还可能是 ‘扳手’ 这样可复用的工具 ,那么我们就可以避免大量的new动作 ,节省很多对象资源 .

如何注入

当我们需要使用某个对象时 ,我们会把它引入到类中 :

class A{
    B object;
    void method(){
        object.do();
    }
}

直接运行的话 ,object是null ,程序异常 .我们需要在执行method方法前把B的实例(instance)放到object这个位置 .

  • constructor - 通过A的构造器传入 - [必须在A初始化前初始化B]
  • set - 设置一个set方法 - [需要显式的调用set/使用反射调用set…]
  • setField - 反射直接设置属性值

constructor方式需要分析对象依赖的顺序 ,如果有环 ,那么就会死锁 ; set方法和对象的实例化是分离的 ,如果所依赖的对象还没有实例 ,就等待它实例化后再set到需要的类中 ;后两种都是Spring使用过的方法 ,都是使用反射 ;setField不需要生成set方法 ,代码看起来会清洁一点 .

这里写图片描述

如上图 ,按照配置文件顺序加载 ,如果依赖对象未存在则等待 ,依赖对象实例化后立即回填这个依赖 .

Cute Koala

首先 ,为这个框架取了一个可爱的名字 Github - Cute Cute Koala ,然后按照上图的流程进行设计.


配置文件 - 使用注解来标识Bean

使用@Module标识的UserModule.class其实就相当于一个配置文件 ,本身没有实际的作用 ,注解看起来更加整洁一些.

@Module //标识这是一个模块 ,不同模块的bean互相独立 
public class UserModule {
   

  @Koala //标识Bean的注解
  HttpService httpService;

  @Koala(UserServiceImpl.class) //如果Bean是一个接口 ,可以指定其实现 ;同一module内不允许设置不同的实现
  UserService userService;

  @Koala(scope = ScopeEnum.NOSCOPE) //Bean是非单例的
  RandomTool randomTool;
}

框架代码:@Koala注解用来标识Bean的实现(如果声明是Interface) 和 是否单例

public @interface Koala {
  Class<?> value() default Koala.class;
  ScopeEnum scope() default ScopeEnum.SINGLETON;
  enum ScopeEnum {
    SINGLETON, NOSCOPE
  }
}

通过读取@Module注解的class文件 ,就可以分析出里面所有的Bean

衍生问题

  • 嵌套依赖
    依赖关系一般是多层相互交错的 ,Bean可能依赖另一个Bean ,分析Module Class只是将最表一层的Bean加载到了容器中 ,但他们里面还会有依赖关系的存在 . 如下 ,UserService (实现为UserServiceImpl ) 里面还依赖了其他Bean ,因此我们在扫描Bean的时候需要递归扫描 .
public class UserServiceImpl implements UserService {
   
  @Koala(UserDaoImpl.class)
  UserDao userDao;

  @Koala(scope = ScopeEnum.NOSCOPE)
  RandomTool randomTool;

  public void welcome() {
    System.out.println("Welcome," + userDao.getName());
  }
}

框架代码: 扫描@Module中的@Koala标记 ,注册到当前@Module的Bean容器中 ;扫描Bean中还有没有@Koala标记 .

class ModuleScanner {
  /**
   * 扫描
   */
  private void scanModule(KoalaModule beanModule) {
    log.info("开始扫描模块:{}", beanModule.getModuleName());
    scanClass(beanModule, beanModule.moduleClass);
  }

  /**
   * 扫描类 ,方便树型操作
   */
  private void scanClass(KoalaModule beanModule, Class moduleClass) {
    log.info("开始扫描目标类[{}],路径[{}]", moduleClass.getSimpleName(), moduleClass.getName());
    Arrays.asList(moduleClass.getDeclaredFields())
        .forEach(field -> scanComponent(beanModule, field));
    log.info("扫描类[{}]完毕", moduleClass.getSimpleName());
  }

  /**
   * 扫描需要注入依赖的字段
   */
  private void scanComponent(KoalaModule beanModule, Field field) {
    if (Objects.isNull(field.getAnnotation(Koala.class))) {
      return;
    }

    Class defineType = field.getType();
    Class implementType = field.getAnnotation(Koala.class).value();
    if (implementType.equals(Koala.class)) {
      implementType = defineType;
    }
    Boolean isSingleton = field.getAnnotation(Koala.class).scope().equals(ScopeEnum.SINGLETON);

    log.info("扫描Bean,声明类型[{}] ,实现类型[{}],是否单例[{}]", defineType.getName(), implementType.getName(),
        isSingleton);

    beanModule.createBean(defineType, implementType, isSingleton);

    //递归扫描子模块
    log.info("开始扫描[{}]字段的依赖类[{}]", field.getName(), implementType.getSimpleName());
    scanClass(beanModule, implementType);
  }
}

创建Module

框架代码: 通过@Module进行配置 ,然后交给框架的中心控制器KoalaFactory进行扫描 ,就会按照配置生成相应KoalaModule类 ,里面包含Bean容器 ,扫描到的Bean就会进行实例化放入Bean容器 .

public class KoalaFactory {
   

  @Getter
  private List<Class> moduleList;

  private Map<Class, KoalaModule> moduleMap = new HashMap<>();
  private ModuleScanner scanner = new ModuleScanner();

  //可以切换当前module
  private int index;
  private KoalaModule currentModule;

  public static KoalaFactory of(Class... modules) {
    return 
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值