目录

Spring Beans

Spring Bean 的概念

由IoC容器管理的那些组成你应用程序的对象叫Bean, Bean就是由Spring容器初始化、装配及管理的对象,除此之外,bean就与应用程序中的其他对象没有什么区别了。那IoC怎样确定如何实例化Bean、管理Bean之间的依赖关系以及管理Bean呢?这就需要配置元数据,在Spring中由BeanDefinition代表

1
2
3
4
5
6
7
8
9
public interface HelloApi {
    void sayHello();
}
public class HelloImpl implements HelloApi {
    @Override
    public void sayHello() {
    	System.out.println("Hello World!");
    }
}

helloworld.xml 放在 resources 目录

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
<?xml version="1.0" encoding="UTF-8"?>
<beans
xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.0.xsd">
 <!-- id 表示你这个组件的名字,class表示组件类 -->
<bean id="hello" class="com.ynthm.springtest.HelloImpl"></bean>
</beans>
1
2
3
4
5
6
7
8
9
 @Test
public void testHelloWorld() {
     //1、读取配置文件实例化一个IoC容器
     ApplicationContext context = new ClassPathXmlApplicationContext("helloworld.xml");
     //2、从容器中获取Bean,注意此处完全“面向接口编程,而不是面向实现”
     HelloApi helloApi = context.getBean("hello", HelloApi.class);
     //3、执行业务逻辑
     helloApi.sayHello();
}

一、准备配置文件:就像前边Hello World配置文件一样,在配置文件中声明Bean定义也就是为Bean配置元数据。

二、由IoC容器进行解析元数据: IoC容器的Bean Reader读取并解析配置文件,根据定义生成BeanDefinition配置元数据对象,IoC容器根据BeanDefinition进行实例化、配置及组装Bean。

**三、实例化IoC容器:**由客户端实例化容器,获取需要的Bean。

在Spring Ioc容器的代表就是org.springframework.beans包中的BeanFactory接口,BeanFactory接口提供了IoC容器最基本功能;而org.springframework.context包下的ApplicationContext接口扩展了BeanFactory,还提供了与Spring AOP集成、国际化处理、事件传播及提供不同层次的context实现 (如针对web应用的WebApplicationContext)。简单说, BeanFactory提供了IoC容器最基本功能,而 ApplicationContext 则增加了更多支持企业级功能支持。ApplicationContext完全继承BeanFactory,因而BeanFactory所具有的语义也适用于ApplicationContext。

除了测试程序的代码外,也就是程序入口,所有代码都没有出现Spring任何组件,而且所有我们写的代码没有实现框架拥有的接口,因而能非常容易的替换掉Spring,是不是非入侵。

客户端代码完全面向接口编程,无需知道实现类,可以通过修改配置文件来更换接口实现,客户端代码不需要任何修改。是不是低耦合。

如果在开发初期没有真正的实现,我们可以模拟一个实现来测试,不耦合代码,是不是很方便测试。

Bean之间几乎没有依赖关系,是不是很容易重用。

Bean的配置

Spring IoC容器目的就是管理Bean,这些Bean将根据配置文件中的Bean定义进行创建,而Bean定义在容器内部由BeanDefinition对象表示,该定义主要包含以下信息:

●全限定类名(FQN):用于定义Bean的实现类;

●Bean行为定义:这些定义了Bean在容器中的行为;包括作用域(单例、原型创建)、是否惰性初始化及生命周期等;

●Bean创建方式定义:说明是通过构造器还是工厂方法创建Bean;

●Bean之间关系定义:即对其他bean的引用,也就是依赖关系定义,这些引用bean也可以称之为同事bean 或依赖bean,也就是依赖注入。

Bean定义只有“全限定类名”在当使用构造器或静态工厂方法进行实例化bean时是必须的,其他都是可选的定义。难道Spring只能通过配置方式来创建Bean吗?回答当然不是,某些SingletonBeanRegistry接口实现类实现也允许将那些非BeanFactory创建的、已有的用户对象注册到容器中,这些对象必须是共享的,比如使用DefaultListableBeanFactory 的registerSingleton() 方法。不过建议采用元数据定义。

Bean的命名

每个Bean可以有一个或多个id(或称之为标识符或名字),在这里我们把第一个id称为“标识符”,其余id叫做“别名”;这些id在IoC容器中必须唯一。

不指定id,只配置必须的全限定类名,由IoC容器为其生成一个标识,客户端必须通过接口“T getBean(Class requiredType)”获取Bean;

指定id,必须在Ioc容器中唯一;

1
<bean id="hello" class="com.ynthm.springtest.HelloImpl" />

指定name,这样name就是“标识符”,必须在Ioc容器中唯一;

指定id和name,id就是标识符,而name就是别名,必须在Ioc容器中唯一;

指定多个name,多个name用“,”、“;”、“ ”分割,第一个被用作标识符,其他的(alias1、alias2、alias3)是别名,所有标识符也必须在Ioc容器中唯一;

1
<bean name="hello;alias1;alias2" class="com.ynthm.springtest.HelloImpl" />

使用 <alias> 标签指定别名,别名也必须在IoC容器中唯一

使用基于XML的配置元数据时,在XML中id是一个真正的XML id属性,因此当其他的定义来引用这个id时就体现出id的好处了,可以利用XML解析器来验证引用的这个id是否存在,从而更早的发现是否引用了一个不存在的bean,而使用name,则可能要在真正使用bean时才能发现引用一个不存在的bean。

Spring Bean 生命周期

Spring 只帮我们管理单例模式 Bean 的完整生命周期,对于 prototype 的 bean ,Spring 在创建好交给使用者之后则不会再管理后续的生命周期。

/images/spring/bean-lifecycle.jpg

  • Spring容器 从XML 文件中读取bean的定义,并实例化bean。
  • Spring根据bean的定义填充所有的属性。
  • 如果bean实现了BeanNameAware 接口,Spring 传递bean 的ID 到 setBeanName方法。
  • 如果Bean 实现了 BeanFactoryAware 接口, Spring传递beanfactory 给setBeanFactory 方法。
  • 如果有任何与bean相关联的BeanPostProcessors,Spring会在postProcesserBeforeInitialization()方法内调用它们。
  • 如果bean实现IntializingBean了,调用它的afterPropertySet方法,如果bean声明了初始化方法,调用此初始化方法。
  • 如果有BeanPostProcessors 和bean 关联,这些bean的postProcessAfterInitialization() 方法将被调用。
  • 如果bean实现了 DisposableBean,它将调用destroy()方法。

生命周期介入

注解方式

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
@Component
public class AnnotationBean {
    private final static Logger LOGGER = LoggerFactory.getLogger(AnnotationBean.class);

    @PostConstruct
    public void start(){
        LOGGER.info("AnnotationBean start");
    }

    @PreDestroy
    public void destroy(){
        LOGGER.info("AnnotationBean destroy");
    }
}

InitializingBean, DisposableBean 接口

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
@Service
public class SpringLifeCycleService implements InitializingBean,DisposableBean{
    private final static Logger LOGGER = LoggerFactory.getLogger(SpringLifeCycleService.class);
    @Override
    public void afterPropertiesSet() throws Exception {
        LOGGER.info("SpringLifeCycleService start");
    }

    @Override
    public void destroy() throws Exception {
        LOGGER.info("SpringLifeCycleService destroy");
    }
}

自定义初始化和销毁方法

也可以自定义方法用于在初始化、销毁阶段调用:

public class SpringLifeCycle{

    private final static Logger LOGGER = LoggerFactory.getLogger(SpringLifeCycle.class);
    public void start(){
        LOGGER.info("SpringLifeCycle start");
    }

    public void destroy(){
        LOGGER.info("SpringLifeCycle destroy");
    }
}

@Configuration
public class LifeCycleConfig {
    @Bean(initMethod = "start", destroyMethod = "destroy")
    public SpringLifeCycle create(){
        SpringLifeCycle springLifeCycle = new SpringLifeCycle() ;

        return springLifeCycle ;
    }
}

以上是在 SpringBoot 中可以这样配置,如果是原始的基于 XML 也是可以使用:

1
2
<bean class="com.crossoverjie.spring.SpringLifeCycle" init-method="start" destroy-method="destroy">
</bean>

*实现 Aware 接口

*Aware 接口可以用于在初始化 bean 时获得 Spring 中的一些对象,如获取 Spring 上下文等。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
@Component
public class SpringLifeCycleAware implements ApplicationContextAware {
    private final static Logger LOGGER = LoggerFactory.getLogger(SpringLifeCycleAware.class);

    private ApplicationContext applicationContext ;

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        this.applicationContext = applicationContext ;
        LOGGER.info("SpringLifeCycleAware start");
    }
}

BeanPostProcessor 增强处理器

实现 BeanPostProcessor 接口,Spring 中所有 bean 在做初始化时都会调用该接口中的两个方法,可以用于对一些特殊的 bean 进行处理:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
@Component
public class SpringLifeCycleProcessor implements BeanPostProcessor {
    private final static Logger LOGGER = LoggerFactory.getLogger(SpringLifeCycleProcessor.class);

    /**
     * 预初始化 初始化之前调用
     * @param bean
     * @param beanName
     * @return
     * @throws BeansException
     */
    @Override
    public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
        if ("annotationBean".equals(beanName)){
            LOGGER.info("SpringLifeCycleProcessor start beanName={}",beanName);
        }
        return bean;
    }

    /**
     * 后初始化  bean 初始化完成调用
     * @param bean
     * @param beanName
     * @return
     * @throws BeansException
     */
    @Override
    public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
        if ("annotationBean".equals(beanName)){
            LOGGER.info("SpringLifeCycleProcessor end beanName={}",beanName);
        }
        return bean;
    }
}

有两个重要的bean 生命周期方法,第一个是setup , 它是在容器加载bean的时候被调用。第二个方法是 teardown 它是在容器卸载类的时候被调用。

bean 标签有两个重要的属性(init-method和destroy-method)。用它们你可以自己定制初始化和注销方法。它们也有相应的注解(@PostConstruct和@PreDestroy)。

instantiate bean对象实例化

②populate properties 封装属性

③如果Bean实现BeanNameAware 执行 setBeanName

④如果Bean实现BeanFactoryAware 或者 ApplicationContextAware 设置工厂 setBeanFactory 或者上下文对象 setApplicationContext

⑤如果存在类实现 BeanPostProcessor(后处理Bean) ,执行postProcessBeforeInitialization,BeanPostProcessor接口提供钩子函数,用来动态扩展修改Bean。(程序自动调用后处理Bean)

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
public class MyBeanPostProcessor implements BeanPostProcessor{
	public Object postProcessAfterInitialization(Object bean,String beanName)
throwsBeansException{
		System.out.println("第八步:后处理Bean,after初始化。");
		//后处理Bean,在这里加上一个动态代理,就把这个Bean给修改了。
		return bean;//返回bean,表示没有修改,如果使用动态代理,返回代理对象,那么就修改了。
	}
	public Object postProcessBeforeInitialization(Object bean,String beanName)
throwsBeansException{
		System.out.println("第五步:后处理Bean的:before初始化!!");
		//后处理Bean,在这里加上一个动态代理,就把这个Bean给修改了。
		return bean;//返回bean本身,表示没有修改。
	}
}

注意:这个前处理Bean和后处理Bean会对所有的Bean进行拦截。

⑥如果Bean实现InitializingBean 执行 afterPropertiesSet

⑦调用 <bean init-method="init"> 指定初始化方法 init

⑧如果存在类实现 BeanPostProcessor(处理Bean) ,执行postProcessAfterInitialization

⑨执行业务处理

⑩如果Bean实现 DisposableBean 执行 destroy

⑪调用 <bean destroy-method="customerDestroy"> 指定销毁方法 customerDestroy

实例化。Spring通过new关键字将一个Bean进行实例化,JavaBean都有默认的构造函数,因此不需要提供构造参数。

填入属性。Spring根据xml文件中的配置通过调用Bean中的setXXX方法填入对应的属性。

事件通知。Spring依次检查Bean是否实现了BeanNameAware、BeanFactoryAware、ApplicationContextAware、BeanPostProcessor、InitializingBean接口,如果有的话,依次调用这些接口。

使用。应用程序可以正常使用这个Bean了。

销毁。如果Bean实现了DisposableBean接口,就调用其destroy方法。

1
<bean id="lifeCycleBean" class="cn.itcast.spring.d_lifecycle.LifeCycleBean" init-method="setup" destroy-method="teardown"></bean>
  • destroy-method 只对 scope=“singleton” 有效
  • 销毁方法,必须关闭ApplicationContext对象(手动调用),才会被调用
1
2
ClassPathXmlApplicationContext applicationContext = new ClassPathXmlApplicationContext("applicationContext.xml");
applicationContext.close();

加载过程

1.容器寻找Bean的定义信息并且将其实例化。

2.如果允许提前暴露工厂,则提前暴露这个bean的工厂,这个工厂主要是返回该未完全处理的bean.主要是用于避免单例属性循环依赖问题.

3.受用依赖注入,Spring按照Bean定义信息配置Bean的所有属性。

4.如果Bean实现了BeanNameAware接口,工厂调用Bean的**setBeanName()**方法传递Bean的ID。

5.如果Bean实现了BeanFactoryAware接口,工厂调用**setBeanFactory()**方法传入工厂自身。

6.如果BeanPostProcessor和Bean关联,那么它们的**postProcessBeforeInitialzation()**方法将被调用。

7.如果Bean指定了init-method方法,它将被调用。

8.如果有BeanPostProcessor和Bean关联,那么它们的postProcessAfterInitialization()方法将被调用

9.最后如果配置了destroy-method方法则注册DisposableBean.

到这个时候,Bean已经可以被应用系统使用了,并且将被保留在Bean Factory中知道它不再需要。 有两种方法可以把它从Bean Factory中删除掉。

1.如果Bean实现了DisposableBean接口,destory()方法被调用。

2.如果指定了订制的销毁方法,就调用这个方法。

Bean 的作用域

  • singleton : bean在每个Spring ioc 容器中只有一个实例。
  • prototype:一个bean的定义可以有多个实例。
  • request:每次http请求都会创建一个bean,该作用域仅在基于web的Spring ApplicationContext情形下有效。
  • session:在一个HTTP Session中,一个bean定义对应一个实例。该作用域仅在基于web的Spring ApplicationContext情形下有效。
  • global-session:在一个全局的HTTP Session中,一个bean定义对应一个实例。该作用域仅在基于web的Spring ApplicationContext情形下有效。

缺省的Spring bean 的作用域是Singleton.

Spring框架中的单例bean不是线程安全的。

Bean 的自动装配

Spring 容器能够自动装配相互合作的bean,这意味着容器不需要配置,能通过Bean工厂自动处理bean之间的协作。

  • no:默认的方式是不进行自动装配,通过显式设置ref 属性来进行装配。
  • **byName:**通过参数名 自动装配,Spring容器在配置文件中发现bean的autowire属性被设置成byname,之后容器试图匹配、装配和该bean的属性具有相同名字的bean。
  • **byType::**通过参数类型自动装配,Spring容器在配置文件中发现bean的autowire属性被设置成byType,之后容器试图匹配、装配和该bean的属性具有相同类型的bean。如果有多个bean符合条件,则抛出错误。
  • constructor:这个方式类似于byType, 但是要提供给构造器参数,如果没有确定的带参数的构造器参数类型,将会抛出异常。
  • **autodetect:**首先尝试使用constructor来自动装配,如果无法工作,则使用byType方式。

自动装配的局限性是:

  • 重写: 你仍需用 配置来定义依赖,意味着总要重写自动装配。
  • 基本数据类型:你不能自动装配简单的属性,如基本数据类型,String字符串,和类。
  • **模糊特性:**自动装配不如显式装配精确,如果有可能,建议使用显式装配。

Spring 的内部 bean

当一个bean仅被用作另一个bean的属性时,它能被声明为一个内部bean,为了定义inner bean,在Spring 的 基于XML的 配置元数据中,可以在 元素内使用 元素,内部bean通常是匿名的,它们的Scope一般是prototype。