1 IOC的概念与刨析
1.1 IOC简介
控制反转(Inversion of Control),是面向对象编程中的一种设计原则,可以用来减低计算机代码之间的耦合度。
其中最常见的方式叫做依赖注入(Dependency Injection,简称DI),还有一种方式叫依赖查找(Dependency Lookup)。
通过控制反转,对象在被创建的时候,由一个调控系统内所有对象的外界实体将其所依赖的对象的引用传递给它。也可以说,依赖被注入到对象中。
通俗一点,解释如下:
- 控制反转,把对象的创建和对象之间调用的过程交给Spring进行管理;
- 使用IOC目的:为了降低耦合度。
1.2 IOC底层原理
所谓使用IOC的目的就是为了降低耦合度,在软件工程概论中我们知道,衡量一个软件设计好坏的标准,是 高耦合、低内聚。
因此,在刨析IOC底层原理的时候,我们不妨一步步来观察如何将代码之间耦合度慢慢降低
传统方式:
对象UserService依赖于对象User,即对象UserService在初始化或者运行到某一点的时候, 自己必须主动去创建对象User或者使用已经创建的对象User,控制权始终在自己手上。耦合度高! 两个对象关系过于紧密,当User的路径或者User中方法变了,UserService也会跟着变,所谓 牵一发而动全身 就是这个道理。
工厂模式:
通过一个UserFactory工厂,完成了对象的创建操作,降低了UserService和User之间的耦合度, 可是工厂和对象之间还是存在耦合度。 其实,完全消除耦合度是不可能的,只能降低耦合度。显然,工厂模式不是最优解。
引入IOC:
IOC底层原理涉及到xml解析、工厂模式与反射机制。IOC过程如下:
- 在xml配置文件中配置创建的对象;
<bean id="user" class="com.hxh.User"></bean>
- 假设还是有UserService与User类,想要在UserService类中调用User的方法,因此创建一个UserFactory工厂类,进行具体的操作,与上面讲解不同的是,这里不再直接返回一个对象,而是通过xml解析和反射的方式 做到进一步降低耦合度:
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文件中的配置,实现了耦合度进一步降低。
1.3 IOC容器
1.3.1 何为容器?
容器的核心是Bean,是豆荚的意思,我们的对象都被装在这个豆荚里统一管理。 可以理解成钞票是你的对象,而你需要把它存储在银行,让银行帮你打理,等你需要钱的时候,银行根据你的需要再把钱打给你… …(我可真是小财迷!)
而在xml里配置的
bean、@repository、 @service、@controller、@component
可以理解成抽象的 map (id-class),在项目启动的时候会读取配置文件里面的bean节点,根据全类名使用反射创建对象放到map里、扫描到打上上述注解的类也是通过反射创建对象放到map里。
此时,map中就放入了我们的对象,接下来我们在代码里需要用到里面的对象时,会通过依赖注入(DI)注入(autowired.resource等注解, xml里bean节点内的ref属性,项目启动的时候会读取xm|节点ref属性根据id注入,也会扫描这些注解,根据类型或id注入)。
1.3.2 IOC容器的理解
IOC思想基于IOC容器完成的,IOC容器的底层是对象工厂
还是再IOC底层原理举的例子,IOC容器会主动创建一个对象User注入到对象UserService需要的地方,从而让User和UserService失去了直接联系。 图示如下:
通过图可以看出来,全部对象的控制权全部上缴给"第三方"IOC容器,所以,IOC容器成了整个系统的关键核心,它起到了一种类似“粘合剂”的作用,把系统中的所有对象粘合在一起发挥作用, 如果没有这个"粘合剂",对象与对象之间会彼此失去联系。
Spring提供了IOC容器实现的两种方式(接口):
- BeanFactory: IOC基本容器的实现,是Spring内部的接口,不提供开发人员进行使用。加载配置文件的时候,不会创建对象,只有在使用的时候才去创建。
- ApplicationContext: BeanFactory接口的子接口,提供了更多更强的功能,一般由开发人员使用。加载配置文件的时候就会把配置文件对象进行创建。
2 IOC操作Bean管理
2.1 Bean管理
Bean管理指的是两个操作:
(1)Spring 创建对象;
(2)Spring 注入属性
Bean管理操作有两种方式
方式一:基于xml配置文件实现
方式二:基于注解的方式实现
2.2 Bean管理xml形式
2.2.1 创建对象与注入过程
一.创建对象
在spring配置文件中,使用bean标签,标签里面添加对应的属性,就可以实现对象的创建。
<bean id="user" class="com.hxh.User"></bean>
在bean标签中,有许多常用的属性:
属性 | 含义 |
---|---|
id | 获取对象的唯一标识 |
class | 类全路径 |
name | 与id基本相同,区别是name可以有特殊字符 |
创建对象,默认执行无参构造方法。
二.注入属性
与JavaSE一样,支持set注入和有参构造注入两种方式。
演示使用set方法进行属性的注入
1.首先创建一个类,类中含有对应的属性和set方法,以Book类为例!
public class Book {
//创建属性
private String bname; //书名
private String bauthor; //作者
//对应的set
public 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配置文件配置对象创建与属性注入。
<?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 http://www.springframework.org/schema/beans/spring-beans.xsd">
<!--配置对象创建-->
<bean id="book" class="com.hxh.Book">
<!--set属性注入-->
<!--使用property完成属性注入-->
<property name="bname" value="Java程序设计"></property>
<property name="bauthor" value="Spring5开发实践"></property>
</bean>
</beans>
3.测试。
@Test
public 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配置文件配置对象创建与属性注入。
<?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 http://www.springframework.org/schema/beans/spring-beans.xsd">
<!--有参构造注入属性-->
<bean id="student" class="com.hxh.Student">
<constructor-arg name="sname" value="黄小黄"></constructor-arg>
</bean>
</beans>
3.测试。(其实和set注入大同小异,往后xml配置文件就不再赘述了)
@Test
public void testStudent(){
//1.加载Spring配置文件
ApplicationContext context = new ClassPathXmlApplicationContext("bean2.xml");
//2.获取配置的对象
Student student = context.getBean("student", Student.class);
//3.输出相关信息
student.testDemo();
}
2.2.2 注入空值和特殊符号
以2.2.1演示的book类为例,演示代码如下:
<!--配置对象创建-->
<bean id="book" class="com.hxh.Book">
<!--注入空值-->
<property name="bname">
<null/>
</property>
<!--注入特殊符号-->
<property name="bauthor">
<value>
<![CDATA[<><><><><>....具体值]]>
</value>
</property>
</bean>
注入特殊符号除了以上的方式,还可以使用转移符号。
2.2.3 注入属性
2.2.3.1 外部bean与内部bean
创建两个类,一个接口UserDao,对应的实现关系及内部方法如下图所示:
此时如果想在UserService里调用UserDaoImpl中的方法该怎么做?
传统方式如下图:
在Spring中可以通过配置实现:
1.在UserServie中创建UserDao类型的属性,并生成对应的set方法,方便注入(2.2.1演示过,这里不再赘述)。
2.在配置文件中进行配置。注意ref属性需要与被注入的bean id保持一致(参照的bean)!
<!--service和dao对象创建-->
<bean id="userService" class="com.ithxh.spring5.service.UserService">
<!--注入userDao对象
name属性值:类里面属性名称
ref属性:创建userDao对象bean标签的id值
-->
<property name="userDao" ref="userDao"></property>
</bean>
<bean id="userDao" class="com.ithxh.spring5.dao.UserDaoImpl"></bean>
3.也可以 使用内部bean的方式实现上述效果,对对象类型的属性进行注入: 在案例代码中,UserDaoImpl添加了name属性,因此,不仅仅展示了内部bean,同时也演示了级联赋值的操作:
<!--service和dao对象创建-->
<bean id="userService" class="com.ithxh.spring5.service.UserService">
<property name="userDao">
<bean id="userDao" class="com.ithxh.spring5.dao.UserDaoImpl">
<!--对象属性赋值-->
<property name="userName" value="黄小黄"></property>
</bean>
</property>
</bean>
2.2.3.2 注入集合
1.注入数组
以Student类的courses数组为例。
代码如下:
<bean id="student" class="com.ithxh.spring5.demo.Student">
<!--数组类型属性注入-->
<property name="courses">
<array>
<value>java</value>
<value>English</value>
<value>python</value>
</array>
</property>
</bean>
2.注入List
与上面类似,相关的Student代码省略,不再赘述。set类型的注入也与下面代码类似,将list标签替换成set标签即可。
<bean id="student" class="com.ithxh.spring5.demo.Student">
<!--List类型属性注入-->
<property name="list">
<list>
<value>java</value>
<value>c</value>
<value>c++</value>
</list>
</property>
</bean>
3.注入Map
<bean id="student" class="com.ithxh.spring5.demo.Student">
<!--Map类型属性注入-->
<property name="map">
<map>
<entry key="Java" value="jdk-1.8"></entry>
</map>
</property>
</bean>
以上,如果在集合中存储的Value为对象,则可以通过<ref>
标签实现,样板代码案例如下:
2.3 工厂Bean
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<Student> {
@Override
public boolean isSingleton() {
//返回是否为单例
return FactoryBean.super.isSingleton();
}
@Override
public Student getObject() throws Exception {
//定义返回bean
Student student = new Student();
//省略...
List<String> strings = new ArrayList<>();
strings.add("黄小黄");
student.setList(strings);
//返回...
return student;
}
@Override
public Class<?> getObjectType() {
return Student.class;
}
}
2.4 Bean的作用域
在Spring中可以设置创建的bean是单实例还是多实例。默认情况下,创建的bean是单实例对象。
在Spring的bean标签中,存在属性scope可以用于设置,其不同值对应表述如下:
1.默认值,singleton,表示单实例对象;
2.prototype,表示多实例对象;
3.request、session,表示每次创建将其放在域对象中(不常用)。
易错点:简述singleton与prototype的区别
1.singleton表示单实例,prototype表示多实例;
2.设置值为singleton时,在加载spring配置文件的时候就会创建单实例对象;而设置值为prototype时,是在调度getBean()方法的时候,去创建多实例对象。
2.5 Bean的生命周期
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("销毁方法");
}
}
文件配置
<bean id="student" class="com.ithxh.spring5.demo.Student" init-method="initMethod" destroy-method="destroyMethod">
<property name="name" value="黄小黄"/>
</bean>
测试
@Test
public 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方法, 配置文件也需要进行相应的配置。
2.6 xml自动装配(偷懒的技巧)
根据指定的装配规则(属性名称或者属性类型),Spring自动将匹配的属性值进行注入。
实现自动装配是通过bean标签的属性autowire
配置自动装配,其有两个常用值:
- byName根据属性名称自动装配,注入值bean的id和类属性名称一样
- byType根据属性类型注入。
演示自动装配
<bean id="student" class="com.ithxh.spring5.demo.Student" autowire="byName"></bean>
<bean id="与待装配的属性名一致" class="xxx"></bean>
写在最后
本文对Spring中的IOC容器进行了刨析,并就xml解析方式实现Bean管理进行了讲解。但是实际开发中,更多使用注解的方式完成开发,下一篇将对注解方式管理Bean进行介绍和讲解。不得不说,Spring真的是互联网的春天!
转载:https://blog.csdn.net/m0_60353039/article/details/127627671