IoC 控制反转

作者: 写代码的海怪 | 来源:发表于2020-07-03 09:48 被阅读0次

    第一次听到这个名词的时候,我就在想到底是哪个“天才”想出来这么丑陋的名字的?当时看了一些博客知道是解决依赖关系的,但是“控制”和“反转”一直没有解释是什么意思。

    初始化对象

    为了解释这个名词,先来看我们初学时候是怎么初始化对象的。

    当我们执行 Java 程序时最关键的步骤是要 new 对象的,比如 String s = new String(),再比如自己写了一个 Stack,测试也好,去做真的 Stack 操作第一步也要 Stack stack = new Stack()。因此,刚开始学 Java 的时候,我们一般都会在 public void main() 这个方法里去初始化我们自己写的类。

    比如这是我练习多态时写的。

    public void main(String[] args) {
      Animal animal = new Animal();
      Animal cat = new Cat();
      Animal dog = new Dog();
      ...
    }
    

    上面的初始化很简单,如果我们要在类里面初始化,可能会在构造器或者字段里 new 对象:

    private List<Cat> children = new ArrayList()<>;
    private Body body;
    
    public Cat(Leg leg, Arm arm) {
      this.body = new Body(leg, arm);
    }
    

    总结一下我们上面初始化对象的步骤:找一个入口,即 main 方法,开始执行,用到哪个类就 new 哪个类,如果里面有成员也要 new,就在所在的类里去先 new 了,再用。

    问题

    上面这种初始化对象的方法很直观,但是不能应对庞大的项目,而且当类之间互相依赖变多的时候,我们就要在他们的 constructor 里不断地手写:XXX x = new XXX();;而且有些类是不能直接 new 的,可能是单例类,要用工厂方法获取对象,这个时候可能会把你烦死。

    解决方案

    这让我们不禁想到:其实这些 new XXX() 或者 new XXX(a, b, c) 又或者 XXX x = XXXFactory.make() 都是一些固定的代码。就跟 setter,getter 一样。当看到 xxx 变量时,脑海自动会有 getXXXsetXXX,那这些 new 对象的代码为什么不可以自动化呢?

    为了让 Java 能识别那些需要被 new 出来的对象,一个简单的想法就是我加个标记呗:

    private DataService Singleton service;
    

    这里的 Singleton 代表 service 要被 new 成一个单例。但是这里语法不行啊,限定符只能是 private, static 啥的。这个时候注解就是我们最佳的选择了。

    @Singleton
    private DataService serivce;
    

    我们还可以定义别的注解如:@Autowiared,@Inject,@Factory,....(当然有些是我编的),然后写个 Java 程序先去检测到带注解的变量,把这些需要自动装配的变量拿出来,然后每个变量都自动 new 自己,这不就是完美的自动化了么?

    除此之外,我们还发现有些类的自动装配不仅仅是 XXX x = new XXX 这么简单,比如新建一个数据库连接池,最好是

    1. 自动读取配置文件(用户名,密码,数据库 url 等)
    2. 新建对象
    3. 初始化池里的一些东西
    4. ...

    因此我们还要将这些要被自动装配的类去细分成 @Service、@Bean、@Application...... 等

    控制反转

    先总结一下上面的解决方案:我们用注解去标识一些类,如 @Service 等。使用当用这些类作为成员变量时,我们再在上面加个注解,如 @Autowired 等,用来表示要自动装配(或者花样自动装配)。然后写个 Java 程序,将需要自动装配的变量都弄成一个 List,遍历 List,动态去 new 对象。至于怎么个 new 法要看注解是啥。

    从上面看到这个 Java 自动装配程序比较关键啊,所以这里提供一个装配单例对象的简化代码:

    1. 先 new 好对象,类的关系都放在 ioc.properties 里
    Properties properties = new Properties();
    properties.load(XXX.class.getResourceAsStream("./ioc.properties");
    
    // 每个 Bean 都 new 一下,这里用 getConstructor().newInstance() 完成的
    properties.forEach((beanName, beanClass) -> {
      Class<?> klass = Class.forName((String) beanClass);
    
      Object beanInstance = klass.getConstructor().newInstance();
    
        beans.put((String) beanName, beanInstance);
    })
    
    1. 把有注解的变量弄出来,自动装配
    beans.forEach((beanName, beanInstance) -> 
        // 将带有 @Autowired 注解的变量成员弄出来
        Listfb<Field> fields = Stream.of(beanInstance.getClass().getDeclaredFields())
                                .filter(field -> field.getAnnotation(Autowired.class) != null)
                                .collect(Collectors.toList());
    
        // 装配之前 new 好的对象
        fields.forEach(field -> {
            String fieldName = field.getName();
            Object obj = beans.get(fieldName);
            
            field.setAccessible(true);
            field.set(beanbeanInstance, obj);
        })
    })
    

    这就是所谓的控制反转,意思就是本来是需要你自己手动去 new Java对象的,现在变成 Java 去帮你 new Java对象了,相当于把控制权交给 Java 。

    所以嘛,反转的意思就是本来你控制 Java 程序,现在 Java 程序控制了你?Emm 听起来怪怪的,但是说 Java(自动装配) 控制了 Java(你的应用)好像也不算反转吧?所以我说这个名字是真的丑陋。

    相关文章

      网友评论

        本文标题:IoC 控制反转

        本文链接:https://www.haomeiwen.com/subject/kaujqktx.html