一、IoC思想
IoC(Inversion of Control,控制倒转)。Spring最核心的部分。所谓IoC,对于Spring框架来说,就是由Spring来负责控制对象的生命周期和对象间的关系。
该思想提出的背景:在Spring没出现之前,编写的功能代码之间存在着大量关联,导致代码耦合度很高,往往修改一处代码,就需要修改很多处互相关联的代码。
用业务的角度来看,如果用户需求产生变化,会直接影响原本的代码程序,这时候只能不断的去修改原本的代码,如果代码量十分庞大,那么改动成本也是十分昂贵的。
二、IoC 理论推导
以往的业务实现我们通常都是使用的三层架构,“用户层、业务层、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层(业务层)代码
- 修改 UserServiceImpl 实现类
public class UserServiceImpl implements UserService{ // private UserDao userDao = new UserDaoImpl(); private UserDao userDao = new MySqlUserDaoImpl(); @Override public void getUser() { userDao.getUser(); } }
- 修改 UserServiceImpl 实现类
-
用户层代码不变
用户层成功获取到了新的数据来源。
但是每次新增加数据来源都需要去将 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 的本质
控制反转IoC(Inversion of Control),是一种设计思想,DI(依赖注入)是实现IoC的一种方法。也有人认为DI只是IoC的另一种说法,但是其实DI是IoC的一种实现方式。
没有IoC的程序中,我们使用面向对象编程,对象的创建与对象间的依赖关系完全硬编码在程序中,对象的创建由程序自己控制。使用IoC控制反转之后,程序会将对象的创建转移给第三方,程序本身不创建对象,而变成了被动的接收对象。个人认为所谓控制反转就是:获得依赖对象的方式反转了。
控制反转是一种通过描述(XML或注解)并通过第三方去生产或获取特定对象的方式。在Spring中实现控制反转的是IoC容器,其的实现方法是依赖注入(Dependency Injection,DI),而依赖注入就是就是通过 set方法来进行注入的。
一句话总结IOC:Bean(对象)的创建、管理以及装配全部交给Spring 来负责!
- 由上述图片可以看出,早些时期,对象的创建都是通过程序来控制,程序互相之间依赖性过大,导致程序耦合。为了解决这类问题,IoC容器诞生了,通过IoC容器当中间件的角色,来调用其他程序,以此来解放程序,大大降低了程序耦合度。
四、IoC 实现的方式以及IoC在Spring框架中的作用
-
实现IoC的方式
IoC的实现方式又很多,可以通过XML配置,可以使用注解实现,新版的Spring框架也可以无需配置实现IoC。
采用XML方式配置Bean的时候,Bean的定义信息是和实现分离的,而采用注解的方式可以把两者合为一体,Bean的定义信息直接以注解的形式定义在实现类中,从而达到了零配置的目的。
-
IoC在Spring框架中的作用
Spring容器在初始化时会先读取配置文件,根据配置文件或者元数据创建与组织对象存入IoC容器中,程序使用时在从IoC容器中取出所需要的对象。
4.1 Spring 通过 XML 配置 Bean 的方式
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不用定义该属性。
-
- property 标签: bean 标签的子标签,用于调用 Bean 实例中的 set()方法完成属性赋值,从而完成依赖注入。
- property 标签的属性:
- name: 用于指定属性的名称,与 Bean 实现类中的set方法所对应的属性名称需要保持一致。(即:指定创建实现类中的某一个属性的 Bean)
- value:用于给指定属性赋予一个具体的值,类型为基本类型、字符串类型,值为标签内的文本内容,可以使用null值将属性的值设置为null。
- ref: 用于给指定属性赋值,类型为引用对象类型,值为其属性的值。
- property 标签的属性:
-
4.2 编写第一个 Spring 程序,给 Bean 注入基本类型或者字符串数据 (通过XML方式配置Bean)
-
Hello Spring
- 创建 maven 项目,在项目中创建实体类
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 配置文件用来管理 Bean,可以抽象的理解为,该文件就是 Spring 的容器(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"> <!--使用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 中的值进行返回打印到控制台上。
4.3 给 Bean 注入 Spring 容器中已经创建好的对象(引用类型数据)
将 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(); } }
-
运行结果:
4.4 IoC 创建对象的方式(依赖注入,通过构造函数注入)
-
创建一个子模块
-
创建 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); } }
-
修改后运行会发生报错,并且 Bean.xml中也会标红
-
通过运行结果可知, IoC 容器在创建对象时默认使用了无参构造方法。如果在默认情况下使用有参构造会报错。
但是 IoC 容器不可能只要默认创建对象的方式,在 < bean > 标签中,有个 < constructor-arg > 标签就行专门用来解决这个问题的。
4.4.1 < 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.xml
<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>
- 运行结果
- 实体构造函数参数增加
五、 Spring 容器配置
5.1 给 Bean 起别名
<bean id="user" class="com.sys.dto.User">
<property name="name" value="姚青"/>
</bean>
<!--别名,如果添加了别名,我们也可以使用别名获取到这个对象-->
<alias name="user" alias="userNew"/>
5.2 配置总的容器文件(import)
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>