一、初识IoC
IoC (Inversion of Control),即控制反转,是一种设计思想,是一个重要的面向对象编程的法则,能够知道开发者设计出松耦合、可扩展的程序。
在Spring中,通过IoC容器来管理所有Java对象的实例化和初始化,控制对象间的依赖关系。
由IoC容器管理的Java对象成为Spring Bean,它与new出来的对象本质上没有区别。
IoC容器是Spring框架的核心组件之一,贯穿了Spring从诞生到成长的整个过程。
控制反转,反转的是什么?(从程序员到第三方容器)
- 将对象的创建权交出去,由第三方容器负责。
- 将对象与对象间关系的维护权交出去,由第三方容器负责。
DI(Dependency Injection),即依赖注入,是控制反转的一种实现。
二、初识DI
依赖注入是控制反转设计思想的一种实现。在Spring创建对象的过程中,将对象的依赖属性通过配置传递给对象(注入依赖)。
依赖注入常见的2中实现方式:
- setter注入
- 构造器注入
IoC容器:用于存放和管理所有的Spring Bean。
DI:用于管理Spring Bean之间的依赖关系。
IoC容器在Spring中实现了两种方式:
- BeanFactory:IoC容器的基本实现,是Spring内部使用的接口,面向Spring本身,不提供给开发者使用。
- ApplicationContext:BeanFactory的子接口,在BeanFactory的基础上添加了更多高级特性,面向开发者,几乎所有的Spring应用都使用ApplicationContext。
ApplicationContext接口的实现类:
- ClassPathXmlApplicationContext:从类路径加载配置文件,创建IoC容器。
- FileSystemXmlApplicationContext:从文件系统加载配置文件,创建IoC容器。
- AnnotationConfigApplicationContext:从注解创建IoC容器。
- WebApplicationContext:在Web应用中创建IoC容器。
- ConfigurableApplicationContext:ApplicationContext的子接口,包含refresh()和close()等方法,让其具有启动、关闭和刷新上下文的能力。
三、基于XML管理Bean
引入依赖
<dependencies> <!--spring基础依赖--> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-context</artifactId> <version>5.3.39</version> </dependency> <!--junit5--> <dependency> <groupId>org.junit.jupiter</groupId> <artifactId>junit-jupiter-api</artifactId> <version>5.8.2</version> <scope>test</scope> </dependency> <!--log4j2--> <dependency> <groupId>org.apache.logging.log4j</groupId> <artifactId>log4j-core</artifactId> <version>2.23.1</version> </dependency> <dependency> <groupId>org.apache.logging.log4j</groupId> <artifactId>log4j-slf4j-impl</artifactId> <version>2.23.1</version> </dependency> </dependencies>
(1)获取Bean
正如在Aspringhelloworld
中,我们使用了三种方式获取了bean,
但是在xml中如果配置了两个class属性一样id不同的bean,通过getBean(Class)
方法获取bean时,会抛出异常,因为此时获取的bean是ambiguous,即不唯一的。
接口也可以作为getBean(Class)
参数获取bean,但是要注意该接口只能有一个实现类。
根据类型获取bean,在满足唯一性的前提下,只要[对象 instanceof 指定类型类或接口] 返回为true,就可以匹配。
(2)依赖注入——setter注入
<bean id="student" class="com.sheeprunner.spring.b.two.Student"> <!-- property标签:通过组件类的setXxx()方法给组件对象设置属性 --> <!-- name属性:指定属性名(这个属性名是getXxx()、setXxx()方法定义的,和成员变量无关) --> <!-- value属性:指定属性值 --> <!--DI--> <property name="id" value="1" /> <property name="name" value="张飞" /> <property name="age" value="31" /> <property name="sex" value="男" /> </bean>
(3)依赖注入——构造器注入
<bean id="student3" class="com.sheeprunner.spring.b.three.Student"> <!--这种写法一定要按照构造器参数的顺序来写--> <!-- <constructor-arg value="2" />--> <!-- <constructor-arg value="李四" />--> <!-- <constructor-arg value="23" />--> <!-- <constructor-arg value="女" />--> <!--constructor-arg标签还有两个属性可以进一步描述构造器参数: - index属性:指定参数所在位置的索引(从0开始) - name属性:指定参数名 index和name属性二选一即可 --> <constructor-arg index="0" name="id" value="2" /> <constructor-arg index="1" name="name" value="关羽" /> <constructor-arg index="2" name="age" value="33" /> <constructor-arg index="3" name="sex" value="男" /> </bean>
(4)特殊值注入
<bean id="student4" class="com.sheeprunner.spring.b.three.Student"> <!--字面量赋值--> <property name="id" value="3" /> <!--null值--> <property name="age"> <null/> </property> <!--xml实体字符--> <property name="sex" value="a<b" /> <!--CDATA节--> <property name="name"> <value><![CDATA[<34]]></value> </property> </bean>
(5)引用类型注入(对象类型赋值)
<!--five-1:外部bean引用--> <bean id="clazz5" class="com.sheeprunner.spring.b.five.Clazz"> <property name="clazzId" value="11" /> <property name="clazzName" value="一年级一班" /> </bean> <bean id="student5" class="com.sheeprunner.spring.b.five.Student"> <constructor-arg value="1"/> <constructor-arg value="孙尚香"/> <constructor-arg value="18"/> <constructor-arg value="女"/> <property name="clazz" ref="clazz5"/> </bean> <!--five-2:内部bean引用--> <bean id="student5_2" class="com.sheeprunner.spring.b.five.Student"> <property name="id" value="2"/> <property name="name" value="刘备"/> <property name="age" value="35"/> <property name="sex" value="男"/> <property name="clazz"> <bean id="clazzInner" class="com.sheeprunner.spring.b.five.Clazz"> <property name="clazzName" value="六年级三班" /> <property name="clazzId" value="63"/> </bean> </property> </bean> <!--five-3:级联属性赋值--> <bean id="student5_3" class="com.sheeprunner.spring.b.five.Student"> <property name="id" value="3"/> <property name="name" value="刘禅"/> <property name="age" value="16"/> <property name="sex" value="男"/> <property name="clazz" ref="clazz5"/> <property name="clazz.clazzId" value="-1"/> <property name="clazz.clazzName" value="小班一班"/> </bean>
(6)数组赋值
<bean id="clazz6" class="com.sheeprunner.spring.b.six.Clazz"> <property name="clazzId" value="-1"/> <property name="clazzName" value="小一班" /> </bean> <bean id="student6" class="com.sheeprunner.spring.b.six.Student"> <property name="id" value="3"/> <property name="name" value="曹植"/> <property name="age" value="16"/> <property name="sex" value="男"/> <property name="clazz" ref="clazz6"/> <property name="hobbies"> <array> <value>打游戏</value> <value>看书</value> <value>看电视电影</value> <value>听音乐</value> </array> </property> </bean>
(7)集合赋值
<!-- 死循环 <bean id="student7_1" class="com.sheeprunner.spring.b.seven.Student"> <property name="id" value="1"/> <property name="name" value="张郃"/> <property name="age" value="18"/> <property name="sex" value="男"/> <property name="clazz" ref="clazz7"/> <property name="hobbies"> <array> <value>读书</value> <value>写字</value> <value>练武</value> </array> </property> </bean> <bean id="student7_2" class="com.sheeprunner.spring.b.seven.Student"> <property name="id" value="2"/> <property name="name" value="关平"/> <property name="age" value="18"/> <property name="sex" value="男"/> <property name="clazz" ref="clazz7"/> <property name="hobbies"> <array> <value>读书</value> <value>写字</value> <value>练武</value> </array> </property> </bean>--> <!--list--> <bean id="clazz7" class="com.sheeprunner.spring.b.seven.Clazz" > <property name="clazzId" value="-1"/> <property name="clazzName" value="小一班"/> <property name="studentList"> <list> <ref bean="student5"/> <ref bean="student5_2"/> <ref bean="student5_3"/> </list> </property> </bean> <bean id="teacher7_1" class="com.sheeprunner.spring.b.seven.Teacher"> <property name="teacherId" value="101"/> <property name="teacherName" value="苍井空"/> </bean> <bean id="teacher7_2" class="com.sheeprunner.spring.b.seven.Teacher"> <property name="teacherId" value="102"/> <property name="teacherName" value="小泽玛利亚"/> </bean> <bean id="student7_1" class="com.sheeprunner.spring.b.seven.Student"> <property name="id" value="1"/> <property name="name" value="张郃"/> <property name="age" value="18"/> <property name="sex" value="男"/> <property name="clazz" ref="clazz7"/> <property name="hobbies"> <array> <value>读书</value> <value>写字</value> <value>练武</value> </array> </property> <property name="teacherMap"> <map> <entry> <key> <value>101</value> </key> <ref bean="teacher7_1"/> </entry> <!-- <entry key="102" value-ref="teacher7_2" />--> <entry> <key> <value>102</value> </key> <ref bean="teacher7_2"/> </entry> </map> </property> </bean> <!--引用集合类型 需要先引入util命名空间--> xmlns:util="http://www.springframework.org/schema/util" http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util.xsd <!--list集合类型的bean--> <util:list id="students_util"> <ref bean="student5"/> <ref bean="student5_2"/> <ref bean="student5_3"/> </util:list> <bean id="clazz_util" class="com.sheeprunner.spring.b.seven.Clazz"> <property name="clazzId" value="13" /> <property name="clazzName" value="一年级三班" /> <property name="studentList" ref="students_util" /> </bean> <!--map集合类型的bean--> <util:map id="teachers_util"> <entry key="101" value-ref="teacher7_1" /> <entry key="102" value-ref="teacher7_2" /> </util:map> <bean id="student_util" class="com.sheeprunner.spring.b.seven.Student"> <property name="id" value="18"/> <property name="name" value="孔明"/> <property name="age" value="56"/> <property name="sex" value="男神" /> <property name="clazz" ref="clazz_util"/> <property name="hobbies"> <array> <value>兵法</value> <value>奇谋</value> <value>百科</value> </array> </property> <property name="teacherMap" ref="teachers_util" /> </bean>
(8)p命名空间
<!--引入p命名空间--> xmlns:p="http://www.springframework.org/schema/p" <!-- util和p仅做一下了解 xml配置注入备案方式需要熟悉其操作及原理 后面上面这些配置应该都很少会用,因为这些配置都是可以通过注解来配置的,而注解更加方便 --> <bean id="student8" class="com.sheeprunner.spring.b.seven.Student" p:id="19" p:name="庞统" p:age="54" p:sex="男" p:hobbies="" p:clazz-ref="clazz_util" p:teacherMap-ref="teachers_util"/>
(9)引入外部配置文件
- 数据源配置
- 引入依赖
<!--mysql连接驱动、druid数据源--> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>8.0.30</version> </dependency> <dependency> <groupId>com.alibaba</groupId> <artifactId>druid</artifactId> <version>1.2.15</version> </dependency>
- spring配置文件
<!--引入context命名空间--> xmlns:context="http://www.springframework.org/schema/context" http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd <!--引入外部资源文件--> <context:property-placeholder location="classpath:jdbc.properties" /> <!--将连接参数注入DruidDataSource的bean--> <bean id="druidDateSource" class="com.alibaba.druid.pool.DruidDataSource"> <!-- 1. 注意引入资源路径和文件名,属性名不要写错 2. 创建druid数据源的时候,driverClassName不是必须的,内部会推断出来 3. ${}中的属性一定要与jdbc.properties文件中一致 --> <!-- <property name="driverClassName" value="${driver}" />--> <property name="url" value="${url}" /> <property name="username" value="${username}" /> <property name="password" value="${password}" /> </bean>
(10)bean的作用域
在Spring的xml配置文件中,bean标签的scope属性用于配置bean的作用域范围。
- singleton:单例模式,默认值,在Spring IoC容器中,只会在初始化时创建一个bean实例,所有对bean的引用都指向同一个对象。
- prototype:多例模式,每次对bean的请求都会创建一个新的bean实例,每次对bean的引用都指向一个新对象。
当在WebApplicationContext环境下还会有另外几个作用域(但不常用)
- request:每次HTTP请求都会创建一个新的bean实例,该实例仅在当前HTTP请求中有效。
- session:每次HTTP会话都会创建一个新的bean实例,该实例仅在当前HTTP会话中有效。
<!--scope属性不配置,默认singleton--> <bean id="user10" class="com.sheeprunner.spring.b.ten.User" scope="singleton"> <property name="id" value="1"/> <property name="username" value="曹操"/> <property name="password" value="54321"/> <property name="age" value="66"/> </bean> <bean id="user10_2" class="com.sheeprunner.spring.b.ten.User" scope="prototype"> <property name="id" value="2"/> <property name="username" value="孙权"/> <property name="password" value="12345"/> <property name="age" value="36"/> </bean>
(11)bean的生命周期
- 具体的生命周期过程
- bean对象创建(调用无参构造器)
- 为bean对象设置属性
- bean的后置处理器(初始化之前)
- bean对象初始化(需在配置bean时指定初始化方法)
- bean的后置处理器(初始化之后)
- bean对象就绪可以使用
- bean对象销毁(需在配置bean时指定销毁方法)
- IOC容器关闭
<bean id="user11" class="com.sheeprunner.spring.b.eleven.User" scope="singleton" init-method="initMethod" destroy-method="destroyMethod" > <property name="id" value="3"/> <property name="username" value="刘备"/> <property name="password" value="11111"/> <property name="age" value="40"/> </bean> <!-- bean的后置处理器要放入IOC容器才能生效 --> <bean id="myBeanProcessor" class="com.sheeprunner.spring.b.eleven.MyBeanProcessor"/>
为什么scope为prototype时不走destroy-method?
在Spring框架中,scope
属性定义了Bean的作用域。当一个Bean被定义为prototype
作用域时,意味着每次请求该Bean时都会创建一个新的实例。因此,对于prototype
作用域的Bean,Spring容器不会管理其生命周期的结束阶段,具体原因如下:
- 生命周期管理:
singleton
作用域的Bean由Spring容器管理其完整的生命周期,包括初始化和销毁。prototype
作用域的Bean由Spring容器创建,但一旦创建完毕并返回给客户端,Spring容器就不再管理该Bean的生命周期。
- 销毁方法调用:
- 对于
singleton
作用域的Bean,Spring容器会在应用关闭时调用其destroy-method
或实现DisposableBean
接口的destroy
方法。 - 对于
prototype
作用域的Bean,Spring容器不会调用其destroy-method
或DisposableBean
接口的destroy
方法,因为这些Bean的生命周期由客户端代码管理。
- 内存管理:
prototype
作用域的Bean通常由客户端代码负责释放资源,例如关闭数据库连接、释放文件句柄等。- 如果Spring容器尝试管理
prototype
作用域Bean的销毁过程,可能会导致资源管理不一致或内存泄漏。
(12)FactoryBean
FactoryBean是Spring提供的一种整合第三方框架的常用机制。
和普通的bean不同,配置一个FactoryBean类型的bean,在获取bean的时候得到的并不是class属性中配置的这个类的对象,而是getObject()方法的返回值。
通过这种机制,Spring可以帮我们把复杂组件创建的详细过程和繁琐细节都屏蔽起来,只把最简洁的使用界面展示给我们。
将来我们整合Mybatis时,Spring就是通过FactoryBean机制来帮我们创建SqlSessionFactory对象的。
FactoryBean
是 Spring 框架中的一个特殊接口,用于自定义 Bean 的创建逻辑。它允许你在 Spring 容器中以更灵活的方式创建复杂的对象。以下是 FactoryBean
的主要作用和特点:
主要作用
- 自定义对象创建:
FactoryBean
可以用来创建复杂的对象,特别是那些不能通过简单的构造函数或工厂方法创建的对象。- 例如,创建一个需要多个步骤初始化的对象,或者需要根据某些条件动态创建不同类型的对象。
- 控制对象的生命周期:
FactoryBean
可以在对象创建前后执行一些额外的操作,如初始化资源、设置属性等。- 这使得你可以更精细地控制对象的生命周期。
- 集成第三方库:
- 当你需要在 Spring 应用中集成第三方库时,
FactoryBean
可以帮助你将这些库的对象无缝集成到 Spring 容器中。 - 例如,创建一个数据库连接池对象,或者初始化一个复杂的第三方服务客户端。
主要方法
FactoryBean
接口定义了以下几个主要方法:
Object getObject()
:
- 返回由
FactoryBean
创建的对象实例。 - 这个方法是
FactoryBean
的核心,用于实际创建对象。
Class<?> getObjectType()
:
- 返回
getObject()
方法返回的对象的类型。 - 如果对象类型在运行时才能确定,可以返回
null
。
boolean isSingleton()
:
- 指示
FactoryBean
创建的对象是否是单例的。 - 返回
true
表示创建的对象是单例的,返回false
表示每次请求都会创建一个新的对象。
(13)自动装配(基于xml)
自动装配:根据指定的策略,在IOC容器中匹配某一个bean,自动为指定的bean中所依赖的类类型或接口类型属性赋值
<!--byType是根据类型去自动匹配,如果只有一个bean配置,不配置id也可以--> <!-- <bean class="com.sheeprunner.spring.b.thirteen.dao.UserDaoImpl" />--> <!-- <bean class="com.sheeprunner.spring.b.thirteen.service.UserServiceImpl" autowire="byType"/>--> <!-- <bean class="com.sheeprunner.spring.b.thirteen.controller.UserController" autowire="byType" />--> <!--byName:将自动装配的属性的属性名,作为bean的id在IOC容器中匹配相对应的bean进行赋值--> <bean id="userDao1" class="com.sheeprunner.spring.b.thirteen.dao.UserDaoImpl" /> <bean id="userService1" class="com.sheeprunner.spring.b.thirteen.service.UserServiceImpl" autowire="byName"/> <bean id="userController1" class="com.sheeprunner.spring.b.thirteen.controller.UserController" autowire="byName" />
public class UserController { private UserService userService1; public void addNewUser() { userService1.addUser(); } public void setUserService1(UserService userService1) { this.userService1 = userService1; } } public class UserServiceImpl implements UserService { private UserDao userDao1; public void addUser() { userDao1.save(); } public void setUserDao1(UserDao userDao1) { this.userDao1 = userDao1; } } public class UserDaoImpl implements UserDao { public void save() { System.out.println("成功添加了一个用户!"); } }