
控制反转(Inversion of Control),是面向对象编程中的一种设计原则,可以用来减低计算机代码之间的耦合度。
其中最常见的方式叫做依赖注入(Dependency Injection,简称DI),还有一种方式叫依赖查找(Dependency Lookup)。
通过控制反转,对象在被创建的时候,由一个调控系统内所有对象的外界实体将其所依赖的对象的引用传递给它。也可以说,依赖被注入到对象中。
通俗一点,解释如下:
所谓使用IOC的目的就是为了降低耦合度,在软件工程概论中我们知道,衡量一个软件设计好坏的标准,是 高耦合、低内聚。
因此,在刨析IOC底层原理的时候,我们不妨一步步来观察如何将代码之间耦合度慢慢降低
传统方式:
对象UserService依赖于对象User,即对象UserService在初始化或者运行到某一点的时候, 自己必须主动去创建对象User或者使用已经创建的对象User,控制权始终在自己手上。耦合度高! 两个对象关系过于紧密,当User的路径或者User中方法变了,UserService也会跟着变,所谓 牵一发而动全身 就是这个道理。

工厂模式:
通过一个UserFactory工厂,完成了对象的创建操作,降低了UserService和User之间的耦合度, 可是工厂和对象之间还是存在耦合度。 其实,完全消除耦合度是不可能的,只能降低耦合度。显然,工厂模式不是最优解。

引入IOC:
IOC底层原理涉及到xml解析、工厂模式与反射机制。IOC过程如下:
public class UserFactory {public static User getUser() throws ClassNotFoundException, InstantiationException, IllegalAccessException {//1.xml解析//String classValue = class属性值;//2.通过反射创建对象Class> clazz= Class.forName("com.hxh.User");return (User) clazz.newInstance();}
}
此时,如果User的路径发生了更改,只需要更改xml文件中的配置,实现了耦合度进一步降低。
容器的核心是Bean,是豆荚的意思,我们的对象都被装在这个豆荚里统一管理。 可以理解成钞票是你的对象,而你需要把它存储在银行,让银行帮你打理,等你需要钱的时候,银行根据你的需要再把钱打给你… …(我可真是小财迷!)

而在xml里配置的
bean、@repository、 @service、@controller、@component
可以理解成抽象的 map (id-class),在项目启动的时候会读取配置文件里面的bean节点,根据全类名使用反射创建对象放到map里、扫描到打上上述注解的类也是通过反射创建对象放到map里。
此时,map中就放入了我们的对象,接下来我们在代码里需要用到里面的对象时,会通过依赖注入(DI)注入(autowired.resource等注解, xml里bean节点内的ref属性,项目启动的时候会读取xm|节点ref属性根据id注入,也会扫描这些注解,根据类型或id注入)。
IOC思想基于IOC容器完成的,IOC容器的底层是对象工厂
还是再IOC底层原理举的例子,IOC容器会主动创建一个对象User注入到对象UserService需要的地方,从而让User和UserService失去了直接联系。 图示如下:
通过图可以看出来,全部对象的控制权全部上缴给"第三方"IOC容器,所以,IOC容器成了整个系统的关键核心,它起到了一种类似“粘合剂”的作用,把系统中的所有对象粘合在一起发挥作用, 如果没有这个"粘合剂",对象与对象之间会彼此失去联系。
Spring提供了IOC容器实现的两种方式(接口):
Bean管理指的是两个操作:
(1)Spring 创建对象;
(2)Spring 注入属性
Bean管理操作有两种方式
方式一:基于xml配置文件实现
方式二:基于注解的方式实现
一.创建对象
在spring配置文件中,使用bean标签,标签里面添加对应的属性,就可以实现对象的创建。
在bean标签中,有许多常用的属性:
| 属性 | 含义 |
|---|---|
| id | 获取对象的唯一标识 |
| class | 类全路径 |
| name | 与id基本相同,区别是name可以有特殊字符 |
创建对象,默认执行无参构造方法。
二.注入属性
与JavaSE一样,支持set注入和有参构造注入两种方式。
演示使用set方法进行属性的注入
1.首先创建一个类,类中含有对应的属性和set方法,以Book类为例!
public class Book {//创建属性private String bname; //书名private String bauthor; //作者//对应的setpublic void setBname(String bname) {this.bname = bname;}public void setBauthor(String bauthor) {this.bauthor = bauthor;}//为了测试方便public void testDemo(){System.out.println(bauthor + "--" + bname);}
}
2.在spring配置文件配置对象创建与属性注入。
3.测试。
@Testpublic void testBook(){//1.加载spring配置文件ApplicationContext context = new ClassPathXmlApplicationContext("bean1.xml");//2.获取配置创建的对象Book book = context.getBean("book", Book.class);//3.输出相关信息book.testDemo();}

演示使用有参构造注入属性
1.首先,创建一个类,在类中提供相应的有参构造方法。以Srudent类为例!
public class Student {private String sname;//有参构造public Student(String sname) {this.sname = sname;}//方便测试public void testDemo(){System.out.println("sname = " + sname);}
}
2.在spring配置文件配置对象创建与属性注入。
3.测试。(其实和set注入大同小异,往后xml配置文件就不再赘述了)
@Testpublic void testStudent(){//1.加载Spring配置文件ApplicationContext context = new ClassPathXmlApplicationContext("bean2.xml");//2.获取配置的对象Student student = context.getBean("student", Student.class);//3.输出相关信息student.testDemo();}

以2.2.1演示的book类为例,演示代码如下:
<><><><>....具体值]]>
注入特殊符号除了以上的方式,还可以使用转移符号。
创建两个类,一个接口UserDao,对应的实现关系及内部方法如下图所示:

此时如果想在UserService里调用UserDaoImpl中的方法该怎么做?
传统方式如下图:

在Spring中可以通过配置实现:
1.在UserServie中创建UserDao类型的属性,并生成对应的set方法,方便注入(2.2.1演示过,这里不再赘述)。
2.在配置文件中进行配置。注意ref属性需要与被注入的bean id保持一致(参照的bean)!
3.也可以 使用内部bean的方式实现上述效果,对对象类型的属性进行注入: 在案例代码中,UserDaoImpl添加了name属性,因此,不仅仅展示了内部bean,同时也演示了级联赋值的操作:
1.注入数组
以Student类的courses数组为例。
代码如下:
java English python
2.注入List
与上面类似,相关的Student代码省略,不再赘述。set类型的注入也与下面代码类似,将list标签替换成set标签即可。
java c c++
3.注入Map
以上,如果在集合中存储的Value为对象,则可以通过标签实现,样板代码案例如下:

Spring中有两种bean,一种是普通bean(自己创建的),一种是工厂bean(Spring里内置的)。区别如下:
普通bean:在配置文件中定义的bean类型就是返回类型;
工厂bean:在配置文件中定义的bean类型可以和返回类型不一样。
工厂bean实现步骤:
1.创建类,让该类作为工厂bean,实现FactoryBean接口;
2.实现接口里的方法,在实现方法里面返回bean类型
示例如下:
import org.springframework.beans.factory.FactoryBean;import java.util.ArrayList;
import java.util.List;/*** @author 兴趣使然黄小黄* @version 1.0* 工厂bean示例*/
public class MyBean implements FactoryBean {@Overridepublic boolean isSingleton() {//返回是否为单例return FactoryBean.super.isSingleton();}@Overridepublic Student getObject() throws Exception {//定义返回beanStudent student = new Student();//省略...List strings = new ArrayList<>();strings.add("黄小黄");student.setList(strings);//返回...return student;}@Overridepublic Class> getObjectType() {return Student.class;}
}
在Spring中可以设置创建的bean是单实例还是多实例。默认情况下,创建的bean是单实例对象。
在Spring的bean标签中,存在属性scope可以用于设置,其不同值对应表述如下:
1.默认值,singleton,表示单实例对象;
2.prototype,表示多实例对象;
3.request、session,表示每次创建将其放在域对象中(不常用)。
易错点:简述singleton与prototype的区别
1.singleton表示单实例,prototype表示多实例;
2.设置值为singleton时,在加载spring配置文件的时候就会创建单实例对象;而设置值为prototype时,是在调度getBean()方法的时候,去创建多实例对象。
1.通过构造创建bean实例(无参构造);
2.为bean的属性设置值和其他bean(调用set方法);
3.调用bean中的初始化方法(需要通过bean标签的init-method属性配置);
4.bean可以使用,对象可以获取;
5.当容器在关闭的时候,调用bean的销毁方法(需要通过bean标签的destroy-method属性配置)。
示例代码:
创建类并设置相应的初始化和销毁方法
public class Student {private String name;public Student(){System.out.println("调用了无参构造");}public void setName(String name) {this.name = name;System.out.println("调用了set方法");}public void initMethod(){System.out.println("初始化方法");}public void destroyMethod(){System.out.println("销毁方法");}
}
文件配置
测试
@Testpublic void test1(){ApplicationContext context = new ClassPathXmlApplicationContext("demo3.xml");Student student = context.getBean("student", Student.class);System.out.println(student);//手动销毁bean实例((ClassPathXmlApplicationContext) context).close();}

如果创建的类实现了接口BeanPostProcessor,则 创建了后置处理器。在bean生命周期初始化前和初始化后会分别调用postProcessBeforeInitialization 和 postProcessAfterInitialization方法, 配置文件也需要进行相应的配置。
根据指定的装配规则(属性名称或者属性类型),Spring自动将匹配的属性值进行注入。
实现自动装配是通过bean标签的属性autowire配置自动装配,其有两个常用值:
演示自动装配
本文对Spring中的IOC容器进行了刨析,并就xml解析方式实现Bean管理进行了讲解。但是实际开发中,更多使用注解的方式完成开发,下一篇将对注解方式管理Bean进行介绍和讲解。不得不说,Spring真的是互联网的春天!
