IoC(Inversion of Control,控制倒转)。Spring最核心的部分。所谓IoC,对于Spring框架来说,就是由Spring来负责控制对象的生命周期和对象间的关系。
该思想提出的背景:在Spring没出现之前,编写的功能代码之间存在着大量关联,导致代码耦合度很高,往往修改一处代码,就需要修改很多处互相关联的代码。
用业务的角度来看,如果用户需求产生变化,会直接影响原本的代码程序,这时候只能不断的去修改原本的代码,如果代码量十分庞大,那么改动成本也是十分昂贵的。
以往的业务实现我们通常都是使用的三层架构,“用户层、业务层、dao层”。逻辑通常为用户层调用业务层,业务层调用Dao层获取数据返还给用户层,用户层与Dao层不产生直接关联,都是通过业务层去获取Dao层的数据。
Dao层代码
UserDao 接口
public interface UserDao {
void getUser();
}
UserDaoImpl 实现类
public class UserDaoImpl implements UserDao{
@Override
public void getUser() {
System.out.println("获取默认用户信息");
}
}
service层(业务层)代码
UserService 接口
public interface UserService {
void getUser();
}
UserServiceImpl 实现类
public class UserServiceImpl implements UserService{
private UserDao userDao = new UserDaoImpl();
@Override
public void getUser() {
userDao.getUser();
}
}
用户层代码
public class MyTest {
public static void main(String[] args) {
UserService userService = new UserServiceImpl();
userService.getUser();
}
}
从结果可以看出,用户层获取到了Dao层的数据,没有问题。
现在的代码业务中,只要一个需求,就是从Dao层中获取默认用户数据。如果用户的需求增加了,需要获取其他用户数据,就需要在Dao层中重新创建新的实现类,并且在业务层的实现类中重新 new 一个Dao层中新创建的实现类的对象,用户层业务不变。
Dao层
新增 MySqlUserDaoImpl(新的数据来源)
public class MySqlUserDaoImpl implements UserDao{
@Override
public void getUser() {
System.out.println("通过MySQL获取用户数据");
}
}
service层(业务层)代码
public class UserServiceImpl implements UserService{
// private UserDao userDao = new UserDaoImpl();
private UserDao userDao = new MySqlUserDaoImpl();
@Override
public void getUser() {
userDao.getUser();
}
}
用户层成功获取到了新的数据来源。
但是每次新增加数据来源都需要去将 service 层的实现类中 new 新的对象,不但程序耦合性极高,如果程序代码量十分庞大的话,还需要耗费大量的时间去修改。
为了解决这个问题,Java革命性的一步出现了!!Dao层写法不变,在业务层的实现类中新增 set() ,专门用来接收对象。在从此我们便不在需要一个一个的去 new 新的对象了,而是将创建对象的主动权交给用户层手中,让用户自己去选择。
service层(业务层)代码
UserService 接口中新增 set() 方法用来接收用户创建的对象
public interface UserService {
void getUser();
void setUser(UserDao userDao);
}
修改 UserServiceImpl 实现类
public class UserServiceImpl implements UserService{
// private UserDao userDao = new UserDaoImpl();
// 将UserDao 设置成变量,而不是创建指定的对象
private UserDao userDao;
@Override
public void getUser() {
userDao.getUser();
}
@Override
public void setUser(UserDao userDao) {
this.userDao = userDao;
}
}
用户层代码
public class MyTest {
public static void main(String[] args) {
UserService userService = new UserServiceImpl();
//用户需要创建那个对象就创建那个对象,将创建权交给用户
userService.setUser(new UserDaoImpl());
userService.getUser();
}
}
以往的业务逻辑是程序是主动创建对象!控制权在程序猿手上!
使用了set注入后,程序不再具有主动性,而是变成了被动的接收对象!用户掌握主动权,由用户选择所要调用的业务!
这种思想,从本质上解决了问题,我们不在需要在程序中管理此类对象的创建了,将对象的创建权交给用户。这样做不但可以大大降低程序代码的耦合,还可以使我们更加专注将经理放在业务实现上。这就是 Spring 框架 IoC思想的原型。
控制反转IoC(Inversion of Control),是一种设计思想,DI(依赖注入)是实现IoC的一种方法。也有人认为DI只是IoC的另一种说法,但是其实DI是IoC的一种实现方式。
没有IoC的程序中,我们使用面向对象编程,对象的创建与对象间的依赖关系完全硬编码在程序中,对象的创建由程序自己控制。使用IoC控制反转之后,程序会将对象的创建转移给第三方,程序本身不创建对象,而变成了被动的接收对象。个人认为所谓控制反转就是:获得依赖对象的方式反转了。
控制反转是一种通过描述(XML或注解)并通过第三方去生产或获取特定对象的方式。在Spring中实现控制反转的是IoC容器,其的实现方法是依赖注入(Dependency Injection,DI),而依赖注入就是就是通过 set方法来进行注入的。
一句话总结IOC:Bean(对象)的创建、管理以及装配全部交给Spring 来负责!
实现IoC的方式
IoC的实现方式又很多,可以通过XML配置,可以使用注解实现,新版的Spring框架也可以无需配置实现IoC。
采用XML方式配置Bean的时候,Bean的定义信息是和实现分离的,而采用注解的方式可以把两者合为一体,Bean的定义信息直接以注解的形式定义在实现类中,从而达到了零配置的目的。
IoC在Spring框架中的作用
Spring容器在初始化时会先读取配置文件,根据配置文件或者元数据创建与组织对象存入IoC容器中,程序使用时在从IoC容器中取出所需要的对象。
Spring 可以通过配置 xml 文件来 进行 Bean 的管理 和装配,在 xml 文件中使用标签来管理 Bean 之间的依赖关系
在Spring中,XML配置文件的根元素是< beans >,< beans >中可以包含多个< bean >子元素,每一个< bean >子元素定义了一个Bean,并描述了该 Bean 是如何被装配到 Spring 容器中的。< bean >子元素中包含多个属性和子元素,常用的属性和子元素如下所示:
beans 标签:在Spring中,XML配置文件的根元素是< beans >,< beans >中可以包含多个< bean >子元素。
bean 标签:每一个 bean 标签都定义一个 Bean, 负责管理 Bean 之间的依赖关系,以及将 Bean 装配到 Spring 容器中。
bean 标签的属性:
id: 是 Bean 的唯一标识,IoC 容器中 bean 的 id 标签不能重复,否则报错。相当于 class 属性所指定的类的对象别名。spring 对 Bean 的配置和管理都是通过 id 来进行的。
class: 定义 Bean 实现类的全路径(包名+类名)。只有子类Bean不用定义该属性。
Hello Spring
public class Hello {
private String str;
public String getStr() {
return str;
}
public void setStr(String str) {
this.str = str;
}
@Override
public String toString() {
return "Hello{" +
"str='" + str + '\'' +
'}';
}
}
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd">
<!--使用Spring来创建对象,在Spring中这些对象被称为Bean-->
<bean id="hello" class="com.sys.pojo.Hello">
<!--value:给指定属性赋值,只能为基本类型和字符串类型-->
<property name="str" value="SpringIoC"/>
</bean>
</beans>
public class MyTest {
public static void main(String[] args) {
//获取 Spring 的上下文对象,ClassPathXmlApplicationContext 通过配置文件来获取 Bean
ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");
//将对象交给Spring来处理,我们要使用对象直接去Spring容器中取出对象即可
Hello hello = (Hello) context.getBean("hello");//获取指定的对象
System.out.println(hello);
}
}
个人学习总结理解:程序运行时,会将 beans.xml 中所管理的 Bean存储到 Spring 容器当中。当执行 main 方法时,所创建的 ClassPathXmlApplicationContext 对象的构造方法会通过传入的参数匹配 Spring 容器中 beans.xml 所管理的全部的 Bean。 然后通过调用该对象父类的 getBean(),来获取容器中指定的 Bean。在通过 bean 标签的 property 子标签对该 Bean进行赋值,然后通过 Bean 实现类中的 set() 进行赋值,在通过实现类中的toString() 将 Bean 中的值进行返回打印到控制台上。
将 IoC 理论推导的代码进行改造
Dao层代码
UserDao 接口:新增 OracleUserDaoImpl 用户实现类
public interface UserDao {
void getUser();
}
UserDaoImpl 实现类
public class UserDaoImpl implements UserDao{
@Override
public void getUser() {
System.out.println("获取默认用户信息");
}
}
MysqlUserDaoImpl 实现类
public class MysqlUserDaoImpl implements UserDao{
@Override
public void getUser() {
System.out.println("获取MySQL用户数据");
}
}
OracleUserDaoImpl 实现类
public class OracleUserDaoImpl implements UserDao{
@Override
public void getUser() {
System.out.println("获取Oracle用户数据");
}
}
service层(业务层)代码
UserService 接口
public interface UserService {
void getUser();
void setUser(UserDao userDao);
}
UserServiceImpl 实现类
public class UserServiceImpl implements UserService {
private UserDao user;
public void getUser() {
user.getUser();
}
@Override
public void setUser(UserDao user) {
this.user = user;
}
}
在 resources 资源文件夹下新增 Beans.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="userDao" class="com.sys.dao.UserDaoImpl"/>
<bean id="mysqlUserDao" class="com.sys.dao.MysqlUserDaoImpl"/>
<bean id="oracleUserDao" class="com.sys.dao.OracleUserDaoImpl"/>
<bean id="userService" class="com.sys.service.UserServiceImpl">
<!--ref:将 Spring 容器中已经创建好的对象注入到该 Bean 中 -->
<property name="user" ref="userDao"/>
</bean>
<bean id="userService2" class="com.spring.service.UserServiceImpl">
<property name="user" ref="mysqlUserDao"/>
</bean>
<bean id="userService3" class="com.spring.service.UserServiceImpl">
<property name="user" ref="oracleUserDao"/>
</bean>
</beans>
用户层代码: 不在使用 new UserServiceImpl 来实现对象的传入,而是通过调用容器中不同的 Bean 来实现不同的依赖关系
public class MyTest {
public static void main(String[] args) {
ApplicationContext context = new ClassPathXmlApplicationContext("Beans.xml");
UserServiceImpl service = (UserServiceImpl) context.getBean("userService");
service.getUser();
UserServiceImpl service2 = (UserServiceImpl) context.getBean("userService2");
service2.getUser();
UserServiceImpl service3 = (UserServiceImpl) context.getBean("userService3");
service3.getUser();
}
}
运行结果:
创建一个子模块
创建 User 实体类,并给无参构造方法一个输出
public class User {
private String name;
public User(){
System.out.println("学Java喽");
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public void show(){
System.out.println("name:" + name);
}
}
创建 Beans.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="user" class="com.sys.dto.User">
<property name="name" value="姚青"/>
</bean>
</beans>
创建 MyTest
public class MyTest {
public static void main(String[] args) {
ApplicationContext context = new ClassPathXmlApplicationContext("Beans.xml");
User user = (User) context.getBean("user");
user.show();
}
}
将 User 实体中的无参构造修改成有参构造
public class User {
private String name;
public User(String name){
this.name = name;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public void show(){
System.out.println("name:" + name);
}
}
通过运行结果可知, IoC 容器在创建对象时默认使用了无参构造方法。如果在默认情况下使用有参构造会报错。
但是 IoC 容器不可能只要默认创建对象的方式,在 < bean > 标签中,有个 < constructor-arg > 标签就行专门用来解决这个问题的。
指定创建类对象时使用哪个构造函数,每一对或者每一个constructor-arg子标签配置一个参数列表中的参数值;如果不配置子标签,则默认使用无参构造方法实例化对象(即该标签通过指定参数列表,对应地来指定多个构造方法中一个具体的构造方法)。
constructor-arg 标签属性:
name属性:通过Bean中的参数名找到参数列表中对应参数
index属性:通过参数在参数列表中的索引找到参数列表中对应参数,index从0开始:
type属性:通过参数的数据类型找到参数列表中对应参数
value属性:对参数列表参数进行赋值,只能是基本数据类型和String类型的数据
ref属性:如果参数值为非基本数据类型,则可通过ref为参数注入值,其值为另一个bean标签id或name属性的属性值
通过 constructor-arg 标签创建对象的三种方式
通过下标创建
<bean id="user" class="com.sys.dto.User">
<!--<property name="name" value="姚青"/>-->
<constructor-arg index="0" value="在慵懒的生活中学习Java"/>
</bean>
通过类型创建,不建议使用(通常实体中不会只存在一种类型的数据,局限性太高)
<bean id="user" class="com.sys.dto.User">
<!--<property name="name" value="姚青"/>-->
<!--<constructor-arg index="0" value="在慵懒的生活中学习Java"/>-->
<constructor-arg type="java.lang.String" value="qwert"/>
</bean>
通过参数名创建,不建议使用
<bean id="user" class="com.sys.dto.User">
<!--<property name="name" value="姚青"/>-->
<!--<constructor-arg index="0" value="在慵懒的生活中学习Java"/>-->
<!--<constructor-arg type="java.lang.String" value="qwert"/>-->
<constructor-arg name="name" value="姚青"/>
</bean>
注:constructor-arg 配置参数的数量是随着构造函数而变化的
public class User {
private String str;
public String getAge() {
return age;
}
public void setAge(String age) {
this.age = age;
}
private String age;
private String name;
public String getStr() {
return str;
}
public void setStr(String str) {
this.str = str;
}
public User(String name,String str,String age){
this.name = name;
this.str = str;
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public void show(){
System.out.println("name:" + name);
System.out.println("age:" + age);
System.out.println("str:" + str);
}
}
<bean id="user" class="com.sys.dto.User">
<constructor-arg index="1" value="在慵懒的生活中学习Java"/>
<constructor-arg name="name" value="姚青"/>
<constructor-arg type="java.lang.String" value="24"/>
</bean>
<bean id="user" class="com.sys.dto.User">
<property name="name" value="姚青"/>
</bean>
<!--别名,如果添加了别名,我们也可以使用别名获取到这个对象-->
<alias name="user" alias="userNew"/>
import 标签的作用是将其他的配置文件引入到当前文件中。在团队开发时,通常每个人都需要创建一个自己开发使用的 Bean 容器,不同的类注册到不同的容器中。这样做可能会存在一些弊端,所有在团队开发时,一般都是创建一个总的 Bean 容器(applicationContext.xml),通过 import 标签将全部的 Bean 容器引入到这个总的容器当中,之后在使用对象时,直接调用这个总的容器即可
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd">
<import resource="Beans.xml"/>
<import resource="Beans2.xml"/>
<import resource="Beans3.xml"/>
</beans>