ApplicationContext 容器的默认行为是在启动服务器时将所有的 singleton bean 提前进行实例化。
提前实例化意味着作为初始化过程的一部分,ApplicationContext 实例会创建并配置所有的singleton bean
如果我们想在Bean使用的时候再实例化它,SpringBean也可配置延迟加载,Spring为我们提供了以下两种方式来实现::
xml配置方式
<bean id="lazyResult" class="com.spring.myspring.pojo.Result" lazy-init="true"/>
注解方式
@Lazy 在类上添加@Lazy注解
应用场景:
开启延迟加载在一定程度上可以提高容器启动和运转性能
对应不常用使用的 Bean 设置延迟加载,需要使用的时候再加载,不必从一开始就加载资源
普通Bean 的初始化是在容器启动初始化阶段执行的,而被 lazy-init = true
修饰的 bean 则是在从容器里第一次进行 context.getBean() 时实例化的。
Spring 启动的时候会把所有的bean信息(包括注解和xml)解析转换成Spring 能够识别的BeanDefinition 并存到HashMap 里供后面的初始化使用,然后对每个BeanDefinition 进行处理,如果是延迟加载的bean ,则在容器初始化的阶段不处理,其他的单例bean则在容器初始化阶段进行实例化并注入依赖。
总结 :
BeanFactory 接口是IOC容器的顶级接口,定义了容器的一些基础行为,负责生产和管理Bean的一个工厂,具体使用它下面的子接口类型,比如 ApplicationContext;
我们重点来看下 FactoryBean:
Spring中Bean有两种,一种是普通Bean,一种是工厂Bean(FactoryBean),FactoryBean可以生产某一类型的Bean实例(返回给我们),也就是说我们可以借助它来定义Bean的创建过程,或者构建复杂对象。
Bean 创建的三种方式中,静态方法和实例方法和FactoryBean作用类似,FactoryBean使用较多,尤其在Spring框架一些组件中会使用,还有其他框架整合Spring时使用
FactoryBean 接口
/**
* 可以让我们自定义Bean的创建过程(完成复杂Bean的定义)
public interface FactoryBean {
/**
* Return an instance (possibly shared or independent) of the object
* managed by this factory.
* 返回FactoryBean创建的Bean实例,如果isSingleton返回true,则该实例会放到Spring容器的单例对象缓存池中的Map
*/
@Nullable
T getObject() throws Exception;
/**
* Return the type of object that this FactoryBean creates,
* 返回FactoryBean创建的Bean类型
*/
@Nullable
Class<?> getObjectType();
/**
* Is the object managed by this factory a singleton? That is,
* 返回作用域是否单例
**/
default boolean isSingleton() {
return true;
}
}
接下来,我们创建一个Config 类,来演示自定义类的创建过程:
Config 类:
public class Config {
private String url;
private int port;
private String username;
private String password;
private String charset;
private int timeout;
}
ConfigFactoryBean类
该类实现了FactoryBean接口,用于生产自定义的Config对象,生成Bean的逻辑写在getObject 方法里
public class ConfigFactoryBean implements FactoryBean<Config> {
private String info;
public void setInfo(String info) {
this.info = info;
}
@Override
public Config getObject() throws Exception {
Config config = new Config();
String[] strings = info.split(",");
config.setUrl(strings[0]);
config.setPort(Integer.parseInt(strings[1]));
config.setUsername(strings[2]);
config.setPassword(strings[3]);
config.setCharset(strings[4]);
config.setTimeout(Integer.parseInt(strings[5]));
return config;
}
@Override
public Class<?> getObjectType() {
return Config.class;
}
@Override
public boolean isSingleton() {
return true;
}
}
在applicationContext.xml文件里配置Bean
<bean id="config" class="com.spring.myspring.factory.ConfigFactoryBean">
// 注入info,用于构建config
<property name="info" value="localhost,3306,root,root,utf-8,3600"/>
bean>
测试
@Test
public void testFactoryBean(){
// 启动容器(容器初始化)
ClassPathXmlApplicationContext applicationContext = new ClassPathXmlApplicationContext("classpath:applicationContext.xml");
Object config = applicationContext.getBean("config");
System.out.println(config);
applicationContext.close();
}
输出:
Config{url='localhost', port=3306, username='root', password='root', charset='utf-8', timeout=3600}
可以看到,FactoryBean正常帮我们生产出来了Config对象
Spring 提供了两种后处理bean的扩展接口,分别为 BeanPostProcessor
和 BeanFactoryPostProcessor
,两者在使用上是有所区别的。
工厂初始化(BeanFactory) ----> Bean对象
在BeanFactory初始化之后可以使用BeanFactoryPostProcessor 进行后置处理做一些事情
在Bean对象实例化(并不是Bean的整个生命周期完成)之后可以使用BeanPostProcessor进行后置处理做一些事情
注意:对象不一定是springbean,而springbean一定是个对象
BeanPostProcessor 是针对Bean级别的处理,可以针对具体某个Bean
public interface BeanPostProcessor {
/**
* 初始化前执行
*/
@Nullable
default Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
return bean;
}
/**
* 初始后前执行
*/
@Nullable
default Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
return bean;
}
}
该接口提供了两个方法,分别在Bean的初始化方法前或初始化方法后执行,具体这个初始化方法指的是什么方法,类似我们在定义bean时,定义了init-method所指定的方法
定义一个类实现了 BeanPostProcessor ,默认是会对整个Spring容器中所有的Bean进行处理。如果要对具体某个Bean处理 ,可以通过方法参数判断,两个类型参数分别为Object和String。第一个参数是每个Bean的实例,第二个参数是每个bean的nam或者id属性的值。所以我们可以通过第二个参数,来判断我们要处理的具体的Bean。
/**
* 拦截实例化之后的对象(实例化了并且属性注入了)
*/
@Component
public class MyBeanPostProcessor implements BeanPostProcessor {
@Override
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
if("user".equalsIgnoreCase(beanName)){
System.out.println("**********postProcessBeforeInitialization 执行了**********");
}
return bean;
}
@Override
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
if("user".equalsIgnoreCase(beanName)){
System.out.println("**********postProcessAfterInitialization 执行了**********");
}
return bean;
}
}
我们来看下Bean的创建过程的执行顺序
<bean id="user" class="com.lagou.edu.pojo.User" init-method="initMethod">bean>
public class User {
private Integer id;
private String username;
public void initMethod(){
System.out.println("**********initMethod**********");
}
}
测试方法
@Test
public void testHandler(){
// 启动容器(容器初始化)
ClassPathXmlApplicationContext applicationContext = new ClassPathXmlApplicationContext("classpath:applicationContext.xml");
Object user = applicationContext.getBean("user");
System.out.println(user);
applicationContext.close();
}
输出:
**********postProcessBeforeInitialization 执行了**********
**********initMethod**********
**********postProcessAfterInitialization 执行了**********
可以看到 BeanPostProcessor的before和after是在initMethod 前后执行的
BeanFactory级别的处理,是针对整个Bean的工厂进行处理
@FunctionalInterface
public interface BeanFactoryPostProcessor {
void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException;
}
此接口只提供一个方法,参数为ConfigurableListableBeanFactory,该参数类型定义了一些方法
其中有个方法名为 getBeanDefinition 方法,我们可以根据此方法,找到我们定义Bean的BeanDefinition对象,
然后我们可以对定义的属性进行修改,以下是BeanDefinition中的方法
方法名字类似我们Bean标签的属性,setBeanClassName对应
标签中的class属性,所以我们当我们拿到BeanDefinition对象时,我们可以手动修改
标签中所定义的属性值。
BeanDefinition对象:我们在 XML 中定义的 bean标签,Spring 解析 bean 标签成为⼀个 JavaBean, 这个JavaBean 就是 BeanDefinition 注意:调⽤ BeanFactoryPostProcessor ⽅法时,这时候bean还没有实例化,此时 bean 刚被解析成 BeanDefinition对象