第四部分 Spring IoC的应用
第1节 Spring Ioc基础知识说明
Spring框架的IOC实现如下图所示:
1、先在beans配置文件中添加实例化对象 及全限定类名 及其依赖关系
2、通过配置文件的信息进行读取并调用
学习注解的时候找对应的xml。
第2节BeanFactory和ApplicationContext的区别
BeanFactory在上一节手写的时候是自定义的一个工厂类,那么在Spring框架的IOC里面 有没有 BeanFactory的类型
无论是javaweb还是javase都用到一个ApplicationContext,那么如果有BeanFactory这个类的话,它和BeanFactory有什么关系呢?
如下图所示:
BeanFactory是Spring框架中IoC容器的顶层接口,它只是用来定义一些基础功能,定义一些基础规范,而ApplicationContext是它的一个子接口,所以ApplicationContext是具备BeanFactory提供的全部功能的。
通常,我们称BeanFactory为SpringIOC的基础容器,ApplicationContext是容器的高级接口(它也是一个容器),比BeanFactory要拥有更多的功能,比如说国际化支持和资源访问(xml,java配置类)等等。
第3节Spring IoC的纯xml模式回顾
纯xml模式下
1、在pom中先引入Spring IoC容器功能
<!--引入Spring IoC容器功能-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.1.12.RELEASE</version>
</dependency>
可以看到了已经引入相应的插件
2、applicationContext.xml中的约束头
<?xml version="1.0" encoding="UTF-8"?>
<!--根标签beans,里面配置一个又一个的bean子标签,每一个bean子标签都代表一个类的配置
xmlns是命名空间。下面之所以能用bean,是因为引入了beans的xml的相关约束。
schemaLocation都是成对出现的-->
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:tx="http://www.springframework.org/schema/tx"
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
http://www.springframework.org/schema/tx
https://www.springframework.org/schema/tx/spring-tx.xsd
">
3、java模式下的启动
@Test
public void testIoC() throws Exception {
// 通过读取classpath下的xml文件来启动容器(xml模式SE应用下推荐)
ClassPathXmlApplicationContext applicationContext =
new ClassPathXmlApplicationContext("classpath:applicationContext.xml");
// 不推荐使用
//ApplicationContext applicationContext1 = new FileSystemXmlApplicationContext("文件系统的绝对路径");
// 在容器中通过id来获取对应的实例化对象
AccountDao accountDao = (AccountDao) applicationContext.getBean("accountDao");
System.out.println(accountDao);
applicationContext.close();
}
4、web应用模式下的启动
上面是java模式中的启动,那么web模式的启动是在web.xml中需要添加监听器,通过监听器来启动。
如图所示:因为web所有的东西都封装在Web容器中,如果使用web功能,就需要引入Spring的web功能
1、pom.xml中引入Spring的Web功能依赖
<!--引入spring web功能-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-web</artifactId>
<version>5.1.12.RELEASE</version>
</dependency>
2、Web下面的配置文件
<!DOCTYPE web-app PUBLIC
"-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
"http://java.sun.com/dtd/web-app_2_3.dtd" >
<web-app>
<display-name>Archetype Created Web Application</display-name>
<!--要使用监听器来做容器的初始化工作,就需要将applicationContext配置文件加载并读取进来
所以下面需要指定一个全局参数-->
<!--配置Spring ioc容器的配置文件 (指定加载配置文件) -->
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:applicationContext.xml</param-value>
</context-param>
<!--使用监听器启动Spring的IOC容器-->
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
</web-app>
注意:首先web应用先去读取web.xml,发现有listener这个组件,就会去执行初始化ContextLoaderListener
监听器,监听器一旦执行,就会执行Spring IOC容器的启动工作,我们的对象呢就能够进行管理了。
同时要注意的是,BeanFactory目前是手动写的,但是Spring容器里面有,所以要删除手写的,改用Spring框架的。那么如何切换呢?
其实Web容器一旦启动,Spring容器一初始化,BeanFactory容器已经被放到了ServletContext的上下文当中,其实Spring提供了一个工具类,方便我们去获取这个容器,容器拿到后,自然也就拿到容器里面的对象了。
// 声明出去
private TransferService transferService = null ;
// 初始化方法
@Override
public void init() throws ServletException {
//通过WebApplicationContextUtils工具类,拿到WebApplicationContext上下文
WebApplicationContext webApplicationContext = WebApplicationContextUtils.getWebApplicationContext(this.getServletContext());
//通过上下文拿到容器,并获取对应的方法拿代理对象工厂
ProxyFactory proxyFactory = (ProxyFactory)webApplicationContext.getBean("proxyFactory");
// 通过代理工厂去获取JDK的动态代理
transferService = (TransferService) proxyFactory.getJdkProxy(webApplicationContext.getBean("transferService")) ;
}
第4节 Bean创建的方式以及Bean标签属性回顾
Bean的三种创建方式
下面的方法二、方法三 一般很少使用,除非自己new的对象,在xml中匹配得特别的麻烦,才使用自己new出来的,然后添加到容器当中进行管理。(也就是将自己创建的添加到xml进行配置)
<!--Spring ioc 实例化Bean的三种方式-->
<!--方式一:使用无参构造器(推荐)-->
<bean id="connectionUtils" class="com.lagou.edu.utils.ConnectionUtils"></bean>
<!--另外两种方式是为了我们自己new的对象加入到SpringIOC容器管理-->
<!--方式二:静态方法 锁定到具体的类, 且锁定到具体的静态方法
理解: 调用CreateBeanFactory类 的静态方法 getInstanceStatic 创建 connectionUtils 对象 -->
<!--<bean id="connectionUtils" class="com.lagou.edu.factory.CreateBeanFactory"
factory-method="getInstanceStatic"/>-->
<!--方式三:实例化方法 需要配置两个bean,因为无法通过类名调用非静态方法-->
<!--<bean id="createBeanFactory" class="com.lagou.edu.factory.CreateBeanFactory"></bean>
<bean id="connectionUtils" factory-bean="createBeanFactory"
factory-method="getInstance"/>-->
测试是否获得bean的实例化对象
@Test
public void testIoC() throws Exception {
// 通过读取classpath下的xml文件来启动容器(xml模式SE应用下推荐)
ClassPathXmlApplicationContext applicationContext =
new ClassPathXmlApplicationContext("classpath:applicationContext.xml");
AccountDao accountDao = (AccountDao) applicationContext.getBean("accountDao");
System.out.println(accountDao);
Object connectionUtils = applicationContext.getBean("connectionUtils");
System.out.println(connectionUtils);
applicationContext.close();
}
不同的作用范围的生命周期
单例模式:singleton
对象出生:当创建容器时,对象就被创建了。
对象活着:只要容器在,对象一直活着。
对象死亡:当销毁容器时,对象就被销毁了。
一句话总结:单例模式的bean对象生命周期与容器相同。
多例模式:prototype
对象出生:当使用对象时,创建新的对象实例。
对象活着:只要对象在使用中,就一直活着。
对象死亡:当对象长时间不用时,被java的垃圾回收器回收了。
一句话总结:多例模式的bean对象,spring框架只负责创建,不负责销毁。
Bean标签属性
在基于xml的loC配置中, bean标签是最基础的标签。它表示了loC容器中的一个对象。换句话说, 如果一个对象想让spring管理, 在XML的配置中都需要使用此标签配置, Bean标签的属性如下:
- id属性:用于给bean提供一个唯一标识。在一个标签内部, 标识必须唯一。
- class属性:用于指定创建Bean对象的全限定类名。
- name属性:用于给bean提供一个或多个名称。多个名称用空格分隔。
- factory-bean属性:用于指定创建当前bean对象的工厂bean的唯一标识。当指定了此属性之后,class属性失效。
- factory-method属性:用于指定创建当前bean对象的工厂方法, 如配合factory-bean属性使用,则class属性失效。如配合class属性使用, 则方法必须是static的。
- scope属性:用于指定bean对象的作用范围。通常情况下就是singleton。当要用到多例模式时,可以配置为prototype。
- init-method属性:用于指定bean对象的初始化方法, 此方法会在bean对象装配后调用。必须是一个无参方法。
- destory-method属性:用于指定bean对象的销毁方法, 此方法会在bean对象销毁前执行。它只能为scope是singleton时起作用。
拓展部分:
scope属性
(1)singleton 默认值
单例对象 :被标识为单例的对象在spring容器中只会存在一个实例
(2)prototype
多例原型:被标识为多例的对象,每次在获得才会被创建,每次创建都是新的对象
(3)request
Web环境下,对象与request生命周期一致
(4)session
Web环境下,对象与session生命周期一致
总结:绝大多数情况下,使用单例singleton(默认值),但是在与struts整合时候,务必要用prototype多例,因为struts2在每次请求都会创建一个新的Action,若为单例,在多请求情况下,每个请求找找spring拿的都是同一个action。
在application.xml配置文件添加bean
<!--scope:定义bean的作用范围
singleton:单例,IOC容器中只有一个该类对象,默认为singleton
prototype:原型(多例),每次使用该类的对象(getBean),都返回给你一个新的对象,
Spring只创建对象,不管理对象。绝大多数都是单例模式。
单例模式才能进行对象管理。如果是多例的,只是帮创建而已,并不帮管理。
多例不在管理当中肯定调用不到其管理方法
-->
<bean id="accountDao" class="com.lagou.edu.dao.impl.JdbcTemplateDaoImpl"
scope="singleton" init-method="init" destroy-method="destory">
这是一个单例模式,且指定初始化方法和销毁方法,到时候会调用实例化对象的对应的方法的。
spring会在对象创建之后立刻调用 init-method 对应注解为@PostConstruct
spring容器在关闭并销毁所有容器中的对象之前调用destory-method 对应注解为@PreDestory
在Dao的实现类中添加两个方法
public class JdbcAccountDaoImpl implements AccountDao {
public void init() {
System.out.println("初始化方法.....");
}
public void destory(){
System.out.println("销毁方法.....");
}
...
第5节 Spring DI依赖注入配置回顾
A、DI依赖注入的xml配置(依赖注入分类)
1、按照注入的方式分类
构造函数注入:顾名思义,就是利用带参构造函数实现对类成员的数据赋值。
set方法注入:它是通过类成员的set方法实现数据的注入。(使用最多的)
2、按照注入的数据类型分类
基本类型和String
注入的数据类型是基本类型或者是字符串类型的数据。
其他Bean类型
注入的数据类型是对象类型, 称为其他Bean的原因是, 这个对象是要求出现在loC容器中的。那么针对当前Bean来说, 就是其他Bean了。
复杂类型(集合类型)
注入的数据类型是Aarry, List, Set, Map, Properties中的一种类型。
依赖注入的配置实现之构造函数注入,顾名思义,就是利用构造函数实现对类成员的赋值。它的使用要求是,类中提供的构造函数参数个数必须和配置的参数个数一致,且数据类型匹配。同时需要注意的是, 当没有无参构造时, 则必须提供构造函数参数的注入, 否则Spring框架会报错。(构造注入可以有参注入,可以无参注入)
3.1、set注入时xml的配置
set注入使用property标签,如果注入的是另外一个bean那么使用ref属性, 如果注入的是普通值那么使用的是value属性
<!--set注入使用property标签,如果注入的是另外一个bean那么使用ref属性,
如果注入的是普通值那么使用的是value属性-->
<property name="ConnectionUtils" ref="connectionUtils"/>
<property name="name" value="zhangsan"/>
<property name="sex" value="1"/>
<property name="money" value="100.3"/>
3.2、set注入时代码块编写
private ConnectionUtils connectionUtils;
public void setConnectionUtils(ConnectionUtils connectionUtils) {
this.connectionUtils = connectionUtils;
}
4.1、构造器注入时xml的配置
name:按照参数名称注入,index按照参数索引位置注入, type构造函数的参数类型
<!--name:按照参数名称注入,index按照参数索引位置注入-->
<constructor-arg name="connectionUtils" index="1" ref="connectionUtils"/>
<constructor-arg name="name" index="2" type="java.lang.String" value="zhangsan"/>
<constructor-arg name="sex" index="3" type="java.lang.Integer" value="1"/>
<constructor-arg name="money" index="4" type="java.lang.Float" value="100.6"/>
4.2、构造器注入代码块编写
通过有参构造,传递参数进去。
public JdbcAccountDaoImpl(ConnectionUtils connectionUtils, String name, int sex, float money) {
this.connectionUtils = connectionUtils;
this.name = name;
this.sex = sex;
this.money = money;
}
5.1、set注入复杂数据类型
Array、Map
<!--set注入注入复杂数据类型-->
<property name="myArray">
<array>
<value>array1</value>
<value>array2</value>
<value>array3</value>
</array>
</property>
<property name="myMap">
<map>
<entry key="key1" value="value1"/>
<entry key="key2" value="value2"/>
</map>
</property>
5.2、set注入复杂数据类型的代码块编写
在纯xml配置下,普遍就是通过这类方式。
如果是通过注解,那么都不需要set方法,直接在属性上面添加@Autowired。下面会介绍到。
private String[] myArray;
private Map<String,String> myMap;
public void setMyArray(String[] myArray) {
this.myArray = myArray;
}
public void setMyMap(Map<String, String> myMap) {
this.myMap = myMap;
}
B、xml与注解相结合模式回顾
1、xml与注解相结合模式(注解+xml)
注意:
- 1) 实际企业开发中, 纯xml模式使用已经很少了
- 2) 引入注解功能, 不需要引入额外的jar
- 3) xml+注解结合模式, xml文件依然存在, 所以, spring IOC容器的启动仍然从加载xml开始
- 4) 哪些bean的定义写在xml中, 哪些bean的定义使用注解
写在xml中的bean是:第三方jar中的bean定义在xml, 比如德鲁伊数据库连接池
定义使用注解的bean是:自己开发的bean定义使用注解
2、xml中标签与注解的对应(IoC)
@Controller用于于控制层类、
@Service用于服务层类、
@Repository用于dao层类的bean
他们都有一个别名是@Component
1、自己开发的bean定义通过@Component转为注解,将bean的id添加进即可(添加到自定义类的上面)
2、维护其bean的依赖关系,xml中的ref,在注解中用@Autowired ( xml模式下是set注入,现在是自动装配)
set注入:对属性创建set方法
自动装配:只需要在属性上添加自动注入注解即可
原来的xml配置如下:
<bean id="connectionUtils" class="com.lagou.edu.utils.ConnectionUtils">
<property name="dataSource" ref="dataSource"/>
</bean>
转为注解模式配置如下:
@Component("connectionUtils")
public class ConnectionUtils {
@Autowired
private DataSource dataSource; // 将依赖的类注入进来
//.....
}
ConnectionUtils属于自定义的类,先在上面添加@Component注解,将原来在xml中配置的id填写上去,然后再对其依赖的属性通过@Autowired进行注入。实现自动装配。再看如下操作:
看我们如何将事务管理器从xml的配置文件上解脱,让它转到注解上来
原xml的配置如下:
<!--事务管理器-->
<bean id="transactionManager" class="com.lagou.edu.utils.TransactionManager">
<property name="ConnectionUtils" ref="connectionUtils"/>
</bean>
转为注解如下进行配置:
xml的id改为写到 @Component里面去,而connectionUtils熟悉通过添加@Autowired进行注入实例化对象
@Component("transactionManager")
public class TransactionManager {
// 按照类型来注入
@Autowired
private ConnectionUtils connectionUtils; // 注入连接工具类
3、DI 依赖注入的注解实现方式 @Autowired(推荐使用)
@Autowired为Spring提供的注解,需要导入包 org.springframework.beans.factory.annotation.Autowired。 @Autowired采取的策略为按照类型注入。
public class TransferServiceImpl {
@Autowired
private AccountDao accountDao;
}
4、@Qualifier配合@Autowired使用
如上代码所示, 这样装配回去spring容器中找到类型为AccountDao的类, 然后将其注入进来。这样会产生一个问题, 当一个类型有多个bean值的时候, 会造成无法选择具体注入哪一个的情况,这个时候我们需要配合着@Qualifier使用。@Qualifier告诉Spring具体去装配哪个对象。
public class TransferServiceImpl {
@Autowired
@Qualifier(name="accountDao") // 这个accountDao是一个具体的注解类的id
private AccountDao accountDao;
}
5、引用类型的注入
面试题: @AutoWired和@Resource的区别?
@AutoWired默认以类型进行查找,@Resource默认以名称进行查找
@AutoWired(required=false) + @Qualifier(“user”) == @Resource(name=”user”)
其中@Resource注解是jdk1.6后才有的
6、指定扫描路径base-package
(最后一步)第三方的xml和自己写的类的注解,此时xml需要被Spring框架去加载,启动IOC容器,那些注解是需要被识别吧,如何被识别呢?就是配置一个扫描,如何配置一个扫描呢?就需要引入Spring框架的context命名空间
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:context="http://www.springframework.org/schema/context"
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
http://www.springframework.org/schema/context
https://www.springframework.org/schema/context/spring-context.xsd">
<!--开启注解扫描,base-package指定扫描的包路径-->
<context:component-scan base-package="com.lagou.edu"/>
<!--引入外部资源文件-->
<context:property-placeholder location="classpath:jdbc.properties"/>
<!--第三方jar中的bean定义在xml中-->
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
<property name="driverClassName" value="${jdbc.driver}"/>
<property name="url" value="${jdbc.url}"/>
<property name="username" value="${jdbc.username}"/>
<property name="password" value="${jdbc.password}"/>
</bean>
C、纯注解模式回顾
纯注解模式
改造xml+注解模式, 将xml中遗留的内容全部以注解的形式迁移出去, 最终删除xml, 从Java配置类启动对应注解
@Configuration注解, 表明当前类是一个配置类
@ComponentScan注解, 替代context:component-scan
@PropertySource, 引入外部属性配置文件
@lmport引入其他配置类
@Value对变量赋值, 可以直接赋值, 也可以使用 ${ } 读取资源配置文件中的信息
@Bean将方法返回的对象 加入Spring lOC容器
xml配置类迁移出去,并删除xml。那么就要创建一个java类用于配置启动的(用注解替代xml)。
@Configuration // @Configuration 注解表明当前类是一个配置类
@ComponentScan({"com.lagou.edu"}) //扫描包下所有类
@PropertySource({"classpath:jdbc.properties"}) // 引入外部资源文件
/*@Import()*/ // 如果配置类有多个,可以统一的关联到此处来
public class SpringConfig {
//配置文件传进来了,需要用@Value进行读取${jdbc.driver}里面的值
@Value("${jdbc.driver}")
private String driverClassName;
@Value("${jdbc.url}")
private String url;
@Value("${jdbc.username}")
private String username;
@Value("${jdbc.password}")
private String password;
@Bean("dataSource") // 添加bean表明该方法的返回对象会加入Spring的IOC容器管理
public DataSource createDataSource(){
// 将读取出的值加入进来
DruidDataSource druidDataSource = new DruidDataSource();
druidDataSource.setDriverClassName(driverClassName);
druidDataSource.setUrl(url);
druidDataSource.setUsername(username);
druidDataSource.setPassword(password);
return druidDataSource;
}
// 此时连接池就进入到Spring的IOC容器进行管理
}
java模式下的对容器进行启动
@Test
public void testIoC() throws Exception {
// 通过读取classpath下的xml文件来启动容器(xml模式SE应用下推荐)
ApplicationContext applicationContext =
new AnnotationConfigApplicationContext(SpringConfig.class);
AccountDao accountDao = (AccountDao) applicationContext.getBean("accountDao");
System.out.println(accountDao);
}
web模式下容器又如何启动?
没有了xml的配置文件,那只能配置启动类的全限定类名,
<web-app>
<display-name>Archetype Created Web Application</display-name>
<!--告诉ContextloaderListener知道我们使用注解的方式启动ioc容器-->
<context-param>
<param-name>contextClass</param-name>
<param-value>org.springframework.web.context.support.AnnotationConfigWebApplicationContext</param-value>
</context-param>
<!--配置启动类的全限定类名 从原来的xml指定路径改为现在注解的 指定配置类的全限定类名-->
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>com.lagou.edu.SpringConfig</param-value>
</context-param>
<!--使用监听器启动Spring的IOC容器-->
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
</web-app>
第6节 高级特性之lazy-Init延迟加载
2.1 lazy-Init延迟加载
1、Bean的延迟加载(延迟创建)
延迟加载适合单例 Bean不适合多例Bean
ApplicationContext 容器的默认行为是在启动服务器时将所有 singleton bean 提前进行实例化。提前实例化就意味着作为初始化过程的一部分,ApplicationContext 实例会创建并配置所有的singleton bean。
立即加载就是容器启动把所有都实例化,并放到缓存中。
延迟加载就是当调用getBean的时候才去实例化这个Bean对象
比如:
lazy-init=”false”,立即加载,表示在spring启动时,立刻进行实例化
<!--lazy-init:延迟加载,true代表延迟,false代表立即加载,默认是false-->
<bean id="lazyResult" class="com.lagou.edu.pojo.Result"/>
<!--该bean默认的设置为lazy-init="false"-->
<bean id="lazyResult" class="com.lagou.edu.pojo.Result" lazy-init="false" />
如果不让singleton bean 在 ApplicationContext实现初始化时被提前实例化,那么可以将bean 设置为延迟实例化。
<bean id="lazyResult" class="com.lagou.edu.pojo.Result" lazy-init="true" />
设置 lazy-init 为 true ,为延迟加载,它的 bean 将不会在 ApplicationContext 启动时提前被实例化,而是第一次向容器通过 getBean 索取 bean 时实例化。
如果是注解模式的话,直接@Lazy即可
2、容器的初始化
/**
* 测试bean的lazy-init属性
*/
@Test
public void testBeanLazy(){
ClassPathXmlApplicationContext applicationContext =
new ClassPathXmlApplicationContext("classpath:applicationContext.xml");
Object lazyResult = applicationContext.getBean("lazyResult");
System.out.println(lazyResult);
applicationContext.close();
}
3、立即加载&延迟加载时调用栈分析
4、如果全局的Bean都设置延迟加载
<beans default-lazy-init="true" > ...</beans> // 这是全局的
5、假设情况
eg1、如果一个设置了立即加载的 bean1,引用了一个延迟加载的 bean2 ,那么 bean1 在容器启动时被实例化,而 bean2 由于被 bean1 引用,所以也被实例化,这种情况也符合延时加载的 bean 在第一次调用时才被实例化的规则
eg2、如果一个 bean 的 scope 属性为 scope=”pototype” 时,即使设置了 lazy-init=”false”,容器启动时也不会实例化bean,而是调用 getBean 方法实例化。(因为只有单例才会立即加载,多例不会)
6、应用场景
(1)开启延迟加载一定程度提高容器启动和运转性能
(2)对于不常使用的Bean设置延迟加载,这样偶尔使用的时候再加载,不必要从一开始该Bean就占用资源
(3)父Bean应用泛型,当类实例化时通过反射来确定具体类型: 需要设置父Bean为延迟加载
第7节 高级特性值FactoryBean
2.2 FactoryBean 和 BeanFactory
BeanFactory接口是容器的顶级接口,定义了容器的一些基础行为,负责生产和管理Bean的一个工厂,具体使用它下面的子接口类型,比如ApplicationContext;此处我们重点分析FactoryBean(工厂)
Spring中Bean有两种,一种是普通Bean,一种是工厂Bean(FactoryBean),FactoryBean可以创建生成某一个类型的Bean实例(返回给我们),也就是说我们可以借助于它自定义Bean的创建过程。
Bean创建的三种方式中的 静态方法 和 实例化方法 和 FactoryBean作用类似,FactoryBean使用较多,尤其在Spring框架一些组件中会使用,还有其他框架和Spring框架整合时使用
// 可以让我们自定义Bean的创建过程(完成复杂Bean的定义)
public interface FactoryBean<T> {
@Nullable
/* 返回FactoryBean创建的Bean实例,如果isSingleton返回true,
则该实例会放到Spring容器的单例对象缓存池中Map*/
T getObject() throws Exception;
@Nullable
// 返回FactoryBean创建的Bean类型
Class<?> getObjectType();
// 返回作用域是否单例
default boolean isSingleton() {
return true;
}
}
创建一个Company实例,并通过FactoryBean实例化Company,将实例添加到Spring IOC容器当中进行管理,怎么做呢?需要先创建一个CompanyFactoryBean来实现FactoryBean接口。
他是用来封装一些复杂的bean对象,让我们能够自定义
1、先创建一个Company类
public class Company {
private String name;
private String address;
private int scale;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getAddress() {
return address;
}
public void setAddress(String address) {
this.address = address;
}
public int getScale() {
return scale;
}
public void setScale(int scale) {
this.scale = scale;
}
@Override
public String toString() {
return "Company{" +
"name='" + name + '\'' +
", address='" + address + '\'' +
", scale=" + scale +
'}';
}
}
2、创建CompanyFactoryBean类并且实现FactoryBean接口
public class CompanyFactoryBean implements FactoryBean<Company> {
private String companyInfo; // 公司名称,地址,规模
public void setCompanyInfo(String companyInfo) {
this.companyInfo = companyInfo;
}
@Override
public Company getObject() throws Exception {
// 模拟创建复杂对象Company
Company company = new Company();
String[] strings = companyInfo.split(",");
company.setName(strings[0]);
company.setAddress(strings[1]);
company.setScale(Integer.parseInt(strings[2]));
return company;
}
@Override
public Class<?> getObjectType() {
return Company.class;
}
@Override
public boolean isSingleton() {
return true;
}
}
3、xml中bean的配置信息
<bean id="companyBean" class="com.lagou.edu.factory.CompanyFactoryBean">
<property name="companyInfo" value="拉勾,中关村,500"/>
</bean>
测试,获取FactoryBean产生的对象
// 在Test中测试
Object companyBean = applicationContext.getBean("companyBean");
System.out.println("bean:" + companyBean);
// 结果如下
bean:Company{name='拉勾', address='中关村', scale=500}
测试,获取FactoryBean,需要在id之前添加“&”
// 在Test中测试
Object companyBean = applicationContext.getBean("&companyBean");
System.out.println("bean:" + companyBean);
// 结果如下
bean:com.lagou.edu.factory.CompanyFactoryBean@53f6fd09
第8节 高级特性之后置处理器
2.3 后置处理器
Spring提供了两种后置处理bean的扩展接口,分别为 BeanPostProcessor 和BeanFactoryPostProcessor,两者在使用上是有所区别的。
一个是针对Bean工程的,一个是针对Bean对象的,但是一个产品生产出来是先有工厂,通过工厂来生产的。
工厂初始化完成后(BeanFactory)—>生产一个又一个的 Bean对象
在BeanFactory初始化之后可以使用BeanFactoryPostProcessor进行后置处理做一些事情
在Bean对象实例化(并不是Bean的整个生命周期完成)之后,可以使用BeanPostProcessor进行后置处理做一些事情
注意:对象不一定是springbean,而springbean一定是个对象
(走完这个流水线就称之为SpringBean)
SpringBean的生命周期
如果生命周期没有走完,算不算一个SpringBean
具体执行的代码块
public class Result implements BeanNameAware,BeanFactoryAware,
ApplicationContextAware,InitializingBean, DisposableBean {
private String status;
private String message;
public String getStatus() {
return status;
}
public void setStatus(String status) {
this.status = status;
}
public String getMessage() {
return message;
}
public void setMessage(String message) {
this.message = message;
}
@Override
public String toString() {
return "Result{" +
"status='" + status + '\'' +
", message='" + message + '\'' +
'}';
}
@Override
public void setBeanName(String name) {
System.out.println("注册我成为bean时定义的id:" + name);
}
@Override
public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
System.out.println("管理我的beanfactory为:" + beanFactory);
}
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
System.out.println("高级容器接口ApplicationContext:" + applicationContext);
}
@Override
public void afterPropertiesSet() throws Exception {
System.out.println("afterPropertiesSet......");
}
public void initMethod() {
System.out.println("init-method....");
}
@PostConstruct
public void postCoustrcut() {
System.out.println("postCoustrcut");
}
@PreDestroy
public void PreDestroy(){
System.out.println("PreDestroy...");
}
@Override
public void destroy() throws Exception {
System.out.println("destroy.....");
}
}
2.3.1 BeanPostProcessor
BeanPostProcessor是针对Bean级别的处理,可以针对某个具体的Bean.
该接口提供了两个方法, 分别在Bean的初始化方法前 和 初始化方法后执行, 具体这个初始化方法指的是什么方法, 类似我们在定义bean时, 定义了init-method所指定的方法
在init-method所指定的方法 之前执行,用的是before,
在init-method所指定的方法 之后执行,用的是after
重点提示:
定义一个类实现了BeanPostProcessor接口, 默认是会对整个Spring容器中所有的bean进行处理(换句话说所有的bean也就都会经过自定义的处理器中)。如果要对具体的某个bean处理, 可以通过方法参数判断, 两个类型参数分别为Object和String, 第一个参数是每个bean的实例, 第二个参数是每个bean的name或者id属性的值。所以我们可以通过第二个参数, 来判断我们将要处理的具体的bean。(如下代码所示)
注意:处理是发生在Spring容器的实例化和依赖注入之后。
流程图的第6步
<bean id="lazyResult" class="com.lagou.edu.pojo.Result" />
后置处理器里面的两个方法,参数是Object , String ,其中Object 是默认所有bean实例,如果要拦截具体的bean只能通过string来判断,如需要拦截Result对应的bean实例,那么就需要匹配上其id信息,也就是id=lazyResult
/*
* 拦截实例化之后的对象(实例化了并且属性注入了)
实现接口还不行,还需要添加@Component,才能让这个类被扫描到
*/
@Component
public class MyBeanPostProcessor implements BeanPostProcessor {
@Override
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
// 拦截具体的Bean
if("lazyResult".equalsIgnoreCase(beanName)) {
System.out.println("MyBeanPostProcessor before方法指定拦截处理lazyResult 的bean实例");
}
return bean;
}
@Override
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
if("lazyResult".equalsIgnoreCase(beanName)) {
System.out.println("MyBeanPostProcessor after方法指定拦截处理lazyResult 的bean实例");
}
return bean;
}
}
流程图第7步
initMethod属性的指定那个方法,如果不用这个属性配置,用@PostConstruct来代替也可以。
<bean id="lazyResult" class="com.lagou.edu.pojo.Result" init-method="initMethod"/>
public class Result implements BeanNameAware,BeanFactoryAware,
ApplicationContextAware,InitializingBean, DisposableBean {
private String status;
private String message;
public String getStatus() {
return status;
}
public void setStatus(String status) {
this.status = status;
}
public String getMessage() {
return message;
}
public void setMessage(String message) {
this.message = message;
}
@Override
public String toString() {
return "Result{" +
"status='" + status + '\'' +
", message='" + message + '\'' +
'}';
}
@Override
public void setBeanName(String name) {
System.out.println("注册我成为bean时定义的id:" + name);
}
@Override
public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
System.out.println("管理我的beanfactory为:" + beanFactory);
}
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
System.out.println("高级容器接口ApplicationContext:" + applicationContext);
}
@Override
public void afterPropertiesSet() throws Exception {
System.out.println("afterPropertiesSet......");
}
public void initMethod() {
System.out.println("init-method....");
}
@PostConstruct
public void postCoustrcut() {
System.out.println("postCoustrcut");
}
@PreDestroy
public void PreDestroy(){
System.out.println("PreDestroy...");
}
@Override
public void destroy() throws Exception {
System.out.println("destroy.....");
}
}
他们都是实例化之后做的一些初始化动作,有先后顺序之别。
2.3.2 BeanFactoryPostProcessor
BeanFactory级别的处理,是针对整个Bean的工厂进行处理,典型应用:PropertyPlaceholderConfifigurer
此接口只提供了一个方法,方法参数为ConfifigurableListableBeanFactory,该参数类型定义了一些方法,其中有个方法名为getBeanDefinition的方法,我们可以根据此方法,找到我们定义bean 的BeanDefinition对象。然后我们可以对定义的属性进行修改,以下是BeanDefinition中的方法
方法名字类似我们bean标签的属性, setBeanClassName对应bean标签中的class属性, 所以当我们拿到BeanDefinition对象时, 我们可以手动修改bean标签中所定义的属性值。
BeanDefinition对象:我们在XML中定义的bean标签, Spring解析bean标签成为一个JavaBean,这个JavaBean就是BeanDefinition
注意:调用BeanFactoryPostProcessor方法时,这时候bean还没有实例化,此时bean刚被解析成BeanDefinition对象