Spring 6(二)【IOC(1)https://developer.aliyun.com/article/1532215
1.5、特殊类型属性的注入
我们的对象类型还可能是其它类型,比如数组、集合类型。这些特殊类型属性的注入无非就是配置文件的写法变化罢了。
1.5.1、数组类型
我们给上面的 Student 类添加一个 Int[] 类型的 hobbies 属性,记得添加 setter (没有 setter 方法就无法注入)和 getter 方法。
<bean id="student6" class="com.lyh.study.bean.Student"> <property name="id" value="1006"/> <property name="name" value="狄如燕"/> <property name="age" value="19"/> <property name="sex" value="女"/> <property name="address" ref="address1"/> <property name="hobbies"> <array> <value>唱歌</value> <value>舞剑</value> </array> </property> </bean>
1.5.2、集合类型
(1)List 类型
我们给上面的 Student 类添加一个 List 类型的 students 属性并添加 setter 和 getter 方法。
<bean id="student7" class="com.lyh.study.bean.Student"> <property name="id" value="1006"/> <property name="name" value="狄如燕"/> <property name="age" value="19"/> <property name="sex" value="女"/> <property name="address" ref="address1"/> <property name="students"> <list> <ref bean="student4"/> <ref bean="student5"/> <ref bean="student6"/> </list> </property> </bean>
注意:如果是 Set 集合,只需要将其中的list标签改为set标签即可。
(2)Map 类型
我们给上面的 Student 类添加一个 Map 类型的 studentMap 属性并添加 setter 和 getter 方法。
<bean id="student8" class="com.lyh.study.bean.Student"> <property name="id" value="1006"/> <property name="name" value="狄如燕"/> <property name="age" value="19"/> <property name="sex" value="女"/> <property name="address" ref="address1"/> <property name="studentMap"> <map> <entry> <key> <value>1001</value> </key> <!-- 如果value是基本类型,直接使用<value>标签即可 --> <ref bean="student1"/> </entry> <entry> <key> <value>1002</value> </key> <ref bean="student2"/> </entry> </map> </property> </bean>
1.5.3、通过 标签实现集合类型属性的注入
要使用 util 标签就必须先引入它,在配置文件头部需要加入以下内容:
使用 util 标签实现 Map 集合类型注入:
<bean id="student8" class="com.lyh.study.bean.Student"> <property name="id" value="1008"/> <property name="name" value="狄如燕"/> <property name="age" value="19"/> <property name="sex" value="女"/> <property name="address" ref="address1"/> <property name="studentMap" ref="map"/> </bean> <util:map id="map"> <entry> <key> <value>1001</value> </key> <ref bean="student1"/> </entry> <entry> <key> <value>1002</value> </key> <ref bean="student2"/> </entry> </util:map>
可以看到,效果和上面直接通过 标签注入是一样的,这个 标签就相当于一个 Map 对象。
1.6、P 命名空间注入
命名空间是啥东西,其实就是我们 Spring 配置文件的头部那些带有 xlms: xxx 的部分(比如 xmls:util 就是 util 命名空间),所以 P 命名空间注入其实就是加这么一行:
有啥用呢?其实也就是用来简化配置文件的代码量:
<bean id="student10" class="com.lyh.study.bean.Student" p:id="1010" p:name="武则天" p:age="58" p:address-ref="address1" p:studentMap-ref="map"/>
可以看到,引入 p命名空间后,我们的属性直接变成了 bean 标签的一个属性值 p:属性名,引用类型的属性也可以通过 p:属性名-ref 的方式来引用。
1.7、引入外部属性文件
我们经常需要把一些常用但是又经常需要修改的类写入到 Spring 配置文件(比如 MySQL 工具类),而这些 Bean 的属性值的修改就需要通过外部属性文件来进行注入了,这样更加灵活,方便维护。
(1)需求:
把一些特定的固定值,放到一个特定的外部文件中去(比如 db.properties),在 Spring 配置文件中进行引入,这样我们要进行修改时,只需要修改外部文件,而不需要修改代码。
(2)导入依赖:
<!-- MySQL驱动 --> <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>
(3)创建外部属性文件 db.properties
jdbc.user=root jdbc.password=Yan1029. jdbc.url=jdbc:mysql://localhost:3306/flink?serverTimezone=UTC jdbc.driver=com.mysql.cj.jdbc.Driver
1.7.1、引入属性文件
(1)引入 context 名称空间:
(2)引入外部属性文件(db.properties)
<context:property-placeholder location="db.properties"/>
(3)配置连接池对应的 Bean
这个 Bean druid已经帮我们实现了,相当于一个工具类,我们都不用自己实现,只需要让 Spring 帮我管理即可。
<bean id="druidDataSource" class="com.alibaba.druid.pool.DruidDataSource"> <property name="url" value="${jdbc.url}"/> <property name="driverClassName" value="${jdbc.driver}"/> <property name="username" value="${jdbc.user}"/> <property name="password" value="${jdbc.password}"/> </bean>
(4)测试:
@Test public void testDataSource() throws SQLException { ApplicationContext ac = new ClassPathXmlApplicationContext("beans.xml"); DataSource druidDataSource = ac.getBean("druidDataSource", DataSource.class); Connection connection = druidDataSource.getConnection(); System.out.println(connection); connection.close(); }
(5)运行结果:
com.mysql.cj.jdbc.ConnectionImpl@5b56b654
1.8、Bean 的作用域
在Spring中可以通过配置bean标签的scope属性来指定bean的作用域范围:
取值 | 含义 | 创建对象的时机 |
singleton(默认) | 在IOC容器中,这个bean的对象始终为单实例 | IOC容器初始化时 |
prototype | 这个bean在IOC容器中有多个实例 | 获取bean时 |
也就是我上一节说的,单例对象和多例对象的区别。
如果是在 WebApplicationContext 环境下还会有另外几个作用域(但不常用):
取值 | 含义 |
request | 在一个请求范围内有效 |
session | 在一个会话范围内有效 |
我是应该用不上了。
这里说一下单例模式(singleton)和非单例模式(prototype)的区别:
- 单例模式下,每次 getBean 都会返回同一个对象(在内存中的地址相同,可以用 == 进行测试)
- 非单例模式下,每次 getBena 都会创建一个新的对象,尽管我们在配置文件中设置的它们的属性是一样的,但是它们指向的是不同的内存地址。
1.9、Bean 的生命周期
- bean对象创建(调用无参构造器)
- 给bean对象设置属性(调用我们自己写的 setter 方法)
- bean的后置处理器(初始化之前)
- bean对象初始化(需在配置bean时指定初始化方法)
- bean的后置处理器(初始化之后)
- bean对象就绪可以使用
- bean对象销毁(需在配置bean时指定销毁方法)
- IOC容器关闭
1.9.1、初始化和销毁方法
上面的 Bean 对象的初始化方法和销毁方法是我们自己实现的,然后通过给 标签添加属性来实现:
<!-- 使用init-method属性指定初始化方法 --> <!-- 使用destroy-method属性指定销毁方法 --> <bean class="com.lyh.study.bean.User" scope="prototype" init-method="initMethod" destroy-method="destroyMethod"> <property name="id" value="1001"></property> <property name="name" value="朱重八"></property> <property name="age" value="18"></property> <property name="sex" value="男"></property> </bean>
1.8.2、后置处理器
bean 的后置处理器会在生命周期的初始化前后添加额外的操作,需要实现 BeanPostProcessor 接口,且配置到IOC容器中。
需要注意的是:bean后置处理器不是单独针对某一个bean生效,而是针对IOC容器中所有bean都会执行
package com.lyh.study; import org.springframework.beans.BeansException; import org.springframework.beans.factory.config.BeanPostProcessor; public class MyBeanProcessor implements BeanPostProcessor { @Override public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException { // 初始化之前的处理代码 return bean; } @Override public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException { // 初始化之后的处理代码 return bean; } }
在 IOC 容器内配置后置处理器(放进去就行了):
<!-- bean的后置处理器要放入IOC容器才能生效 --> <bean id="myBeanProcessor" class="com.lyh.study.MyBeanProcessor"/>
1.10、FactoryBean
FactoryBean是Spring提供的一种整合第三方框架的常用机制。和普通的bean不同,配置一个FactoryBean 类型的 bean(FactoryBean 是一个接口,所以这里说 FactoryBean类型的Bean 指的其实是 它的实现类),在获取 bean 的时候得到的并不是 class 属性中配置的这个类的对象,而是getObject()方法的返回值。通过这种机制,Spring可以帮我们把复杂组件创建的详细过程和繁琐细节都屏蔽起来,只把最简洁的使用界面展示给我们。
其实我们整合 Spring + Mybatis 时,Spring就是通过FactoryBean机制来帮我们创建SqlSessionFactory对象的。
public interface FactoryBean<T> { String OBJECT_TYPE_ATTRIBUTE = "factoryBeanObjectType"; @Nullable T getObject() throws Exception; @Nullable Class<?> getObjectType(); default boolean isSingleton() { return true; } }
(1)实现 FactoryBean 接口:
package com.lyh.study; import com.lyh.study.bean.Student; import org.springframework.beans.factory.FactoryBean; public class StudentFactoryBean implements FactoryBean<Student> { @Override public Student getObject() throws Exception { return new Student(); } @Override public Class<?> getObjectType() { return Student.class; } }
(2)添加到 IOC 容器:
<bean id="studentFactoryBean" class="com.lyh.study.StudentFactoryBean"/>
(3)测试:
@Test public void testBeanFactory(){ ApplicationContext ac = new ClassPathXmlApplicationContext("beans.xml"); // 转换出来的不是 StudentBeanFactory 对象,而是 Student 对象 Student studentBeanFactory = (Student) ac.getBean("studentFactoryBean"); System.out.println(studentBeanFactory); }
(4)运行结果:
Student{id=null, name='null', age=null, sex='null'}
当 Spring 容器遇到一个实现了 FactoryBean 接口的 Bean 时,它不会直接实例化这个 Bean,而是会调用该 Bean 的 getObject() 方法来获取对象。这样,我们就可以在 getObject() 方法中编写自定义的对象创建逻辑,从而实现与第三方框架的整合。
Spring 6(二)【IOC(3)https://developer.aliyun.com/article/1532219