<!-- index-menu -->
IoC是Inverse of Controller的缩写,顾名思义控制反转,就是两个作用:控制和反转。控制:控制对象的创建及销毁;反转:将对象的控制权从需要该对象的类反转到IoC容器。
我们首先来看一个例子:
假设这里有一个叫李明LiMing的这样一个类,他有一台Lenovo电脑,他可以playMusic方法让电脑播放音乐,也可以用office方法让电脑打开办公软件。然后电脑通过一系列操作方法operate1,2,3分别完成了李明的需求。
public class LiMing {
public void playGame(){
Lenovo lenovo = new Lenovo();
lenovo.operate1();
lenovo.operate3();
}
public void office(){
Lenovo lenovo = new Lenovo();
lenovo.operate2();
lenovo.operate3();
}
}
public class Lenovo {
public void operate1(){
System.out.println("Lenovo执行操作1");
}
public void operate2(){
System.out.println("Lenovo执行操作2");
}
public void operate3(){
System.out.println("完成");
}
}
这时候有一个问题来了,如果李明之后换了一台Dell电脑,那其中的代码我们是不是都要挨着全部进行替换。
public class LiMing {
public void playGame(){
Dell dell = new Dell();
dell.operate1();
}
public void office(){
Dell dell = new Dell();
dell.operate2();
}
}
因此我们可以思考两个问题:
上面两个问题很明显,李明很明显他只是需要一个电脑来用于玩游戏和工作这和品牌无关。而李明也不会自己制造电脑,因此电脑这个对象不应该由李明创建。
因此,我们首先应该抽象出一个Computer接口,接口包含电脑中的各种方法
public interface Computer {
void operate1();
void operate2();
void operate3();
}
接下来再看LIMING类中,liming的电脑就不应由liming创建了,因此我们需要通过构造方法给李明传入车辆对象,至此,李明将对车的控制权转让了出去,但车辆的创建应有谁来创建呢,因此这时候就有了IoC容器的概念。
public class LiMing {
private Computer computer;
// 通过构造参数传入
public LiMing(Computer computer){
this.computer = computer;
}
public void playGame(){
computer.operate1();
}
public void office(){
computer.operate2();
}
}
约定:
/**
* 1.实例化bean
* 2.保存bean
* 3.提供bean
* 4.每一个bean要产生一个唯一的id与之相对应
*/
public class IoCContainer {
private Map<String,Object> beans = new ConcurrentHashMap<String, Object>();
/**
* 根据beanId 获取一个Bean
* @param beanId beanId
* @return 返回bean
*/
public Object getBean(String beanId){
return beans.get(beanId);
}
/**
* 委托ioc容器创建一个bean
* @param clazz 要创建的bean的class
* @param beanId beanId
* @param paramBeanIds 要创建的bean的class的构造方法所需要的参数的beanId们
*/
public void setBeans(Class<?> clazz, String beanId,String... paramBeanIds){
// 1.组装构造方法所需要的参数值
Object[] paramValues = new Object[paramBeanIds.length];
for (int i = 0; i < paramValues.length; i++) {
paramValues[i] = beans.get(paramBeanIds[i]);
}
// 2.调用构造方法实例化bean
Object bean = null;
for (Constructor<?> constructor : clazz.getConstructors()) {
try {
bean = constructor.newInstance(paramValues);
} catch (InstantiationException e) {
} catch (IllegalAccessException e) {
} catch (InvocationTargetException e) {
}
if (bean == null){
throw new RuntimeException("找不到合适的构造方法去实例化bean");
}
}
//3.将实例化的bean放入beans
beans.put(beanId,bean);
}
}
至此,我们的IoC容器就写好了。使用的方式如下:
public class TestIoC {
private IoCContainer ioCContainer = new IoCContainer();
// 首先在容器种注册bean(创建bean)
@Before
public void before(){
ioCContainer.setBean(Lenovo.class,"lenovo");
// liming的依赖为联想电脑的bean,因此通过beanid的形式加入依赖
ioCContainer.setBean(LiMing.class,"liming","lenovo");
}
// 通过beanId获取liming的对象
@Test
public void test(){
Human liming = (Human) ioCContainer.getBean("liming");
liming.playGame();
}
}
使用IoC容器的好处可以从上面的例子看出:
ioCContainer.setBean(LiMing.class,"liming","lenovo");
2.每个类只需关注自己的业务逻辑
李明无需关心电脑的创建,甚至不用关心电脑是什么牌子的电脑,它只用于专注自己的办公和游戏:
public class LiMing {
private Computer computer;
// 通过构造参数传入
public LiMing(Computer computer){
this.computer = computer;
}
public void playGame(){
computer.operate1();
}
public void office(){
computer.operate2();
}
}
3.修改依赖关系将变得很容易,只需在注册进容器中时更改依赖关系即可
ioCContainer.setBean(LiMing.class,"liming","lenovo");
//lenovo ----> dell
ioCContainer.setBean(LiMing.class,"liming","dell");