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