第一次听到这个名词的时候,我就在想到底是哪个“天才”想出来这么丑陋的名字的?当时看了一些博客知道是解决依赖关系的,但是“控制”和“反转”一直没有解释是什么意思。
初始化对象
为了解释这个名词,先来看我们初学时候是怎么初始化对象的。
当我们执行 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 变量时,脑海自动会有 getXXX
和 setXXX
,那这些 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
这么简单,比如新建一个数据库连接池,最好是
- 自动读取配置文件(用户名,密码,数据库 url 等)
- 新建对象
- 初始化池里的一些东西
- ...
因此我们还要将这些要被自动装配的类去细分成 @Service、@Bean、@Application...... 等
控制反转
先总结一下上面的解决方案:我们用注解去标识一些类,如 @Service 等。使用当用这些类作为成员变量时,我们再在上面加个注解,如 @Autowired 等,用来表示要自动装配(或者花样自动装配)。然后写个 Java 程序,将需要自动装配的变量都弄成一个 List,遍历 List,动态去 new 对象。至于怎么个 new 法要看注解是啥。
从上面看到这个 Java 自动装配程序比较关键啊,所以这里提供一个装配单例对象的简化代码:
- 先 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);
})
- 把有注解的变量弄出来,自动装配
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(你的应用)好像也不算反转吧?所以我说这个名字是真的丑陋。
网友评论