2.IOC设计理念(理解控制反转,实现IOC容器实例,Spring中对IOC的支持)
1.什么是IOC(控制反转)?
控制反转的意思是将创建实例的权利交给框架去做,我们不需要关心如何在程序中去创建一个对象,然后去使用它,我们只需要提前配置好对象的一些信息,然后在框架下的程序中就可以直接使用,无需new。因为框架会自动的为我们分配我们需要的对象。
这,便是控制反转。 很神奇对吧,它到底是如何做到的呢? 其实我们仔细想想也不难。这无非就是实现一个容器,在运行我们的程序之前让容器自动的创建对象,然后注入到我们的程序中,这样我们的程序中就拥有了实例,就可以正常运行了。
2.实现一个简单的IOC容器
那么如何去实现这个简单的IOC容器呢?
我们先思考下如下问题:
- 我们如何要保证我们的程序是基于容器运行?
- 如何让容器自动创建对象呢? (扩展)
- 如何如何让容器准确的创建我们需要的对象?
- 有些对象我们只要创建一个,而有些则需要按情况来创建,如何让容器做到这一点?(扩展)
2.1 下面开始设计
我们首先设计容器(一个类) 命名为:ApplicationContext
我们首先要知道这个类的作用,首先这个类必须要的功能是作为一个容器装载生成的对象,其次这个类还需要有根据配置创建我们想要的对象的能力。
所以我们决定给这个类声明一个Map类型的对象用来装载对象,借用它的构造方法来生成对象
/**
* IOC容器,功能是自动创建对象和对象实例存储,
* 可以让我们直接使用实例化的对象
*/
public class ApplicationContext {
/**
* 存储单例对象
*/
private final Map<String,Object> singleObjContext;
public ApplicationContext(List<ObjectConfig> objectConfigs){
}
/**
* 从容器中获取指定名称对象
* @param beanName 对象名称
* @return
*/
public Object getObject(String beanName){
return singleObjContext.get(beanName);
}
}
好啦,下面基本的模子搭建好了,下面就要进行考虑如何让容器正确的创建对象了:
我们知道创建一个对象需要的要素有下面几个:
我们得知道这个对象的类型
我们得知道这对象的属性,并可以为属性赋值
我们得为这个对象声明一个名称
由上可知,我们可以设计一个这样的配置类(省略了get set方法,自行补充):ObjectConfig
public class ObjectConfig {
/**
* 存储对象名称
*/
private String beanName;
/**
* 存储对象类型
*/
private Class<?> cl;
/**
* 存储对象属性名称和赋的值
*/
private Map<String,Object> diMap;
/**
* 标注注入的字段类型是基本类型还是引用类型
*/
private Map<String,String> valueType;
public ObjectConfig(){
diMap = new HashMap<>();
valueType = new HashMap<>();
}
}
好了有了这些,我们就可以根据这个来写最关键方法了:
主要用到反射特性,不了解的同学可以先了解下反射。此方法存在缺陷,想要改进的同学可以进行改进,不过这里只是为了演示学习主题,故不做太多弹性处理。
public ApplicationContext(List<ObjectConfig> objectConfigs){
singleObjContext = new HashMap<>();
for (ObjectConfig objectConfig : objectConfigs) {
try {
String beanName = objectConfig.getBeanName();
Class<?> className = objectConfig.getCl();
Map<String, Object> diMap = objectConfig.getDiMap();
Object obj = className.getConstructor().newInstance();
Field[] fields = className.getDeclaredFields();
AccessibleObject.setAccessible(fields, true);
//遍历注入
for (Field field : fields) {
if (diMap.containsKey(field.getName())) {
Class<?> cl = diMap.get(field.getName()).getClass();
//如果字段类型是传入参数类型的父类型或者相等类型
if(field.getType().isAssignableFrom(cl)||field.getType().equals(cl)){
field.set(obj,diMap.get(field.getName()));
}else{
throw new Exception("类型传输错误!!");
}
}
}
singleObjContext.put(beanName, obj);
} catch (Exception e) {
e.printStackTrace();
}
}
}
写完之后,我们可以进行测试一下:
首先我们写一个Person类,然后进行配置并使用容器进行创建Person对象。
public class Person {
private String name;
private Integer age;
private String sex;
private Integer height;
private Integer weight;
}
public class Test {
public static void main(String[] args) {
ObjectConfig objectConfig = new ObjectConfig();
objectConfig.setBeanName("person");
objectConfig.setCl(Person.class);
Map<String ,Object> diMap = objectConfig.getDiMap();
diMap.put("name","牧之");
diMap.put("age",12);
diMap.put("sex","男");
diMap.put("height",170);
diMap.put("weight",110);
List<ObjectConfig> objectConfigList = new ArrayList<>();
objectConfigList.add(objectConfig);
ApplicationContext applicationContext = new ApplicationContext(objectConfigList);
Person person = (Person) applicationContext.getObject("person");
System.out.println(person.toString());
}
}
从上面的测试代码中我们可以看到,从始至终都没有new Person(),我们是通过容器来的带Person对象的。
下面我们来看看,能否成功得到person对象:

我们可以看到,通过配置式的方式,我们确实通过容器得到Person对象。
由此我们可以总结出: IOC容器其实就是一个通过某些配置的方式来自动获得我们想要的对象的一个工具。
3.使用IOC容器和不使用IOC容器编程有什么不同的体验?
通过前面的学习,我们实现了一个简单的IOC容器,下面我们来对比一下,使用容器常见对象并使用和不使用容器进行编程的区别:
public class Test {
public static void main(String[] args) {
new Test().IOCCoding();
new Test().NormalCoding();
}
public void IOCCoding(){
ObjectConfig objectConfig = new ObjectConfig();
objectConfig.setBeanName("person");
objectConfig.setCl(Person.class);
Map<String ,Object> diMap = objectConfig.getDiMap();
diMap.put("name","牧之");
diMap.put("age",12);
diMap.put("sex","男");
diMap.put("height",170);
diMap.put("weight",110);
List<ObjectConfig> objectConfigList = new ArrayList<>();
objectConfigList.add(objectConfig);
ApplicationContext applicationContext = new ApplicationContext(objectConfigList);
Person person = (Person) applicationContext.getObject("person");
System.out.println(person.toString());
}
public void NormalCoding(){
Person person = new Person();
person.setName("四郎");
person.setAge(12);
person.setSex("男");
person.setHeight(170);
person.setWeight(110);
System.out.println(person);
}
}

二者结果一样,但是从直观上看,会不会给人一种感觉,使用容器好像代码量更大了,好像更复杂了呀!!
如果你这样认为,那么说明你还没理解到容器的强大之处!
下面我们解析一下上面的使用容器的那部分代码:

在实际的使用过程中,其实使用容器进行编程的代码就两行:
ApplicationContext applicationContext = new ApplicationContext(objectConfigList);
Person person = (Person) applicationContext.getObject("person");
我们只需要在某个地方配置好相应的信息,容器会自动读取并为我们创建好对象,我们只管获取就好了。
甚至在Spring上,我们连获取都不用操心了,所有的都可以通过配置的方式实现,我们只管使用就好啦!!
下面我们来学习Spring对IOC是怎么支持的。。
4.Spring中对IOC的支持
在Spring中,我们可以通过下面注解来了解:
- @Component 注解: 作用在类上,用来声明这个类将在容器初始化的时候进行对象的加载。
- @Service 注解: 和@Component作用一样,但是它有它的特殊含义,一般用它来修饰业务类。
- @Repository注解: 和@Component作用一样,但是它有它的特殊含义,一般用它来修饰数据库操作类。
- @Controller注解:和@Component作用一样,但是它有它的特殊含义,一般用它来修饰控制器类。
- @Autowired注解: 注入注解,用它来实现属性注入,类似的还有 @Inject, @Resource
- @Configuration注解:用来标志某个类为配置类
- @ComponentScan注解:用来配置类上,表示让容器自动扫描配置类平级包或子包进行自动装配。
- @Qualifier: 用来解决歧义注入的。
- @Scope: 用来设置bean的作用域,也就是控制bean是只创建一个还是根据需求创建。
好了,下面我们通过Spring来实现第3小节的例子:
首先,我们要在最外层目录下新建一个配置类:

然后,新建一个Person类并使用注解修饰:

接着建立一个组件类:ChangePerson

最后,在测试方法上初始化容器并获得我们想要的对象,在整个过程中,我们没用用到new

成功获得Person对象:

项目代码地址:
https://gitee.com/yan-jiadou/design-mode/tree/master/%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F/src/main/java/IOC

浙公网安备 33010602011771号