Spring Bean配置与注入

1、Spring容器与Bean关系

image.png

Bean配置信息定义了Bean的实现及依赖关系,Spring容器根据各种形式的Bean配置信息在容器内部建立Bean定义注册表,然后根据注册表加载、实例化Bean,并建立Bean的依赖关系,最后将这些准备就绪的Bean放到Bean缓存池中,准备就绪的Bean以供外层应用程序调用。初始化Bean还可以用来做一些事情(例如Thrift发布服务,kafka启动消费客户端等)。

Spring IOC容器解决两个问题,一个是依赖查找,找到我们需要的Bean,而不是重复的new。第二个是控制反转,自动化查找过程,不需要通过名称来查找Bean,来提供遍历,这也给单元测试减少了很多工作。

2、Bean的配置

Bean的配置方式有三种:

  • 基于XML配置Bean
  • 注解方式定义
  • Java类方法定义

2.1 基于XML配置Bean

对于基于XML的配置,Spring 2.0以后使用Schema的格式,使得不同类型的配置拥有了自己的命名空间,使配置文件更具扩展性。

image.png
  • 默认命名空间:它没有空间名,用于Spring Bean的定义;
  • xsi命名空间:这个命名空间用于为每个文档中命名空间指定相应的Schema样式文件,是标准组织定义的标准命名空间;
  • aop命名空间:这个命名空间是Spring配置AOP的命名空间,是用户自定义的命名空间。

命名空间的定义分为两个步骤:第一步指定命名空间的名称;第二步指定命名空间的Schema文档样式文件的位置,用空格或回车换行进行分分隔。
在Spring容器的配置文件中定义一个简要Bean的配置片段如下所示:

image.png

一般情况下,Spring IOC容器中的一个Bean即对应配置文件中的一个<bean>,这种镜像对应关系应该容易理解。其中id为这个Bean的名称,通过容器的getBean("foo")即可获取对应的Bean,在容器中起到定位查找的作用,是外部程序和Spring IOC容器进行交互的桥梁。class属性指定了Bean对应的实现类,init-method和destroy-method会在初始化和销毁Bean时调用,property定义Bean属性。

<?xml version="1.0" encoding="UTF-8" ?>
<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd">
    <bean id="car" name="#car1" class="com.baobaotao.simple.Car" init-method="start" destroy-method="close">
        <property name="color" value="red" />
    </bean>
</beans>

2.2 注解方式定义

我们知道,Spring容器成功启动的三大要件分别是:Bean定义信息、Bean实现类以及Spring本身。如果采用基于XML的配置,Bean定义信息和Bean实现类本身是分离的,而采用基于注解的配置方式时,Bean定义信息即通过在Bean实现类上标注注解实现。下面定义一个Bean:

import org.springframework.beans.factory.BeanNameAware;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Service;

@Service
public class LogonService implements BeanNameAware{
    
    private LogDao logDao;
    private UserDao userDao;
    
    
    @Autowired
    public void setLogDao(LogDao logDao) {
        this.logDao = logDao;
    }
    
    @Autowired
    public void setUserDao(UserDao userDao) {
        System.out.println("auto inject");
        this.userDao = userDao;
    }
    
}

@Service注解在LogonService类声明处对类进行标注,它可以被Spring容器识别,Spring容器自动将POJO转换为容器管理的Bean。它和以下的XML配置是等效的(先需要定义logDao和userDao):

<bean id="userDao" class="LogonService"/>
    <property name="logDao" value="logDao" />
    <property name="userDao" value="userDao">
</bean>

Spring提供了4个定义Bean的注解@Component,@Repository,@Service,@Controller。它们功能基本等效,所以也称这些注解为Bean的衍型注解:(类似于xml文件中定义Bean<bean id=" " class=" "/>

  • @Repository:用于对DAO实现类进行标注;
  • @Service:用于对Service实现类进行标注;
  • @Controller:用于对Controller实现类进行标注;
  • @Component:用于对其它组件类进行标注

之所以要在@Component之外提供这三个特殊的注解,是为了让注解类本身的用途清晰化,此外Spring将赋予它们一些特殊的功能。

2.3 Java类方法定义

在普通的POJO类中只要标注@Configuration注解,就可以为spring容器提供Bean定义的信息了,每个标注了@Bean的类方法都相当于提供了一个Bean的定义信息。

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
//①将一个POJO标注为定义Bean的配置类
@Configuration
public class AppConf {
        //②以下两个方法定义了两个Bean,以提供了Bean的实例化逻辑
    @Bean
    public UserDao userDao(){
       return new UserDao();    
    }
    
    @Bean
    public LogDao logDao(){
        return new LogDao();
    }
    //③定义了logonService的Bean
    @Bean
    public LogonService logonService(){
        LogonService logonService = new LogonService();
                //④将②和③处定义的Bean注入到LogonService Bean中
        logonService.setLogDao(logDao());
        logonService.setUserDao(userDao());
        return logonService;
    }
}

①处在APPConf类的定义处标注了@Configuration注解,说明这个类可用于为Spring提供Bean的定义信息。类的方法处可以标注@Bean注解,Bean的类型由方法返回值类型决定,名称默认和方法名相同,也可以通过入参显示指定Bean名称,如@Bean(name="userDao").直接在@Bean所标注的方法中提供Bean的实例化逻辑。

在②处userDao()和logDao()方法定义了一个UserDao和一个LogDao的Bean,它们的Bean名称分别是userDao和logDao。在③处,又定义了一个logonService Bean,并且在④处注入②处所定义的两个Bean。

因此,以上的配置和以下XML配置时等效的:

<bean id="userDao" class="UserDao"/>
<bean id="logDao" class="LogDao"/>
<bean id="logService" class="LogonService"
    p:logDao-ref="logDao" p:userDao-ref="userDao"/>
</bean>

基于java类的配置方式和基于XML或基于注解的配置方式相比,前者通过代码的方式更加灵活地实现了Bean的实例化及Bean之间的装配,但后面两者都是通过配置声明的方式,在灵活性上要稍逊一些,但是配置上要更简单一些。

3、Bean注入

Bean注入方式有两种:

  • 通过XML注入:注入的方式可以是属性注入,构造方法注入和工厂方法注入
  • 通过注解的方式注入:@Autowired,@Resource,@Required

3.1 XML注入

3.11 属性注入

属性注入即通过setXxx()方法注入Bean的属性值或依赖对象,由于属性注入方式具有可选择性和灵活性高的优点,因此属性注入是实际应用中最常采用的注入方式。

属性注入要求Bean提供一个默认的构造函数,并为需要注入的属性提供对应的Setter方法。Spring先调用Bean的默认构造函数实例化Bean对象,然后通过反射的方式调用Setter方法注入属性值。

import org.springframework.beans.factory.BeanNameAware;

public class LogonService implements BeanNameAware{

    private LogDao logDao;

    private UserDao userDao;

    public void setUserDao(UserDao userDao) {
        this.userDao = userDao;
    }

    public void setLogDao(LogDao logDao) {
        this.logDao = logDao;
    }
    
    public LogDao getLogDao() {
        return logDao;
    }
    public UserDao getUserDao() {
        return userDao;
    }
    
    public void setBeanName(String beanName) {
        System.out.println("beanName:"+beanName);        
    }
    
    public void initMethod1(){
        System.out.println("initMethod1");
    }
    public void initMethod2(){
        System.out.println("initMethod2");
    }
    
}

bean.xml配置

<?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"
       default-autowire="byName"
         >
    <bean id="logDao" class="LogDao"/>
    <bean id="userDao" class="UserDao"/>
   <bean class="LogonService">
       <property name="logDao" ref="logDao"></property>
       <property name="userDao" ref="userDao"></property>
   </bean>
</beans>

3.12 构造方法注入

使用构造函数注入的前提是Bean必须提供带参数的构造函数。例如

import org.springframework.beans.factory.BeanNameAware;

public class LogonService implements BeanNameAware{

    public LogonService(){}

    public LogonService(LogDao logDao, UserDao userDao) {
        this.logDao = logDao;
        this.userDao = userDao;
    }

    private LogDao logDao;

    private UserDao userDao;

    public void setUserDao(UserDao userDao) {
        this.userDao = userDao;
    }

    public void setLogDao(LogDao logDao) {
        this.logDao = logDao;
    }
    
    public LogDao getLogDao() {
        return logDao;
    }
    public UserDao getUserDao() {
        return userDao;
    }
    
    public void setBeanName(String beanName) {
        System.out.println("beanName:"+beanName);        
    }
    
    public void initMethod1(){
        System.out.println("initMethod1");
    }
    public void initMethod2(){
        System.out.println("initMethod2");
    }
    
}

bean.xml配置

<?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"
       default-autowire="byName">

    <bean id="logDao" class="LogDao"/>
    <bean id="userDao" class="UserDao"/>
   <bean class="LogonService">
      <constructor-arg  ref="logDao"></constructor-arg>
       <constructor-arg ref="userDao"></constructor-arg>
   </bean>

</beans>

3.13 工厂方法注入

非静态工厂方法:

<?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:p="http://www.springframework.org/schema/p"
    xsi:schemaLocation="http://www.springframework.org/schema/beans 
         http://www.springframework.org/schema/beans/spring-beans-3.0.xsd">

    <!-- 工厂方法-->
    <bean id="carFactory" class="com.baobaotao.ditype.CarFactory" />
    <bean id="car5" factory-bean="carFactory" factory-method="createHongQiCar">
    </bean>
</beans>

factory-bean是调用非静态工厂方法的bean,factory-method是工厂方法名称。

静态工厂方法:

<?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:p="http://www.springframework.org/schema/p"
    xsi:schemaLocation="http://www.springframework.org/schema/beans 
         http://www.springframework.org/schema/beans/spring-beans-3.0.xsd">

    <bean id="car6" class="com.baobaotao.ditype.CarFactory"
        factory-method="createCar"></bean>

</beans>

通过class.factory-method注入bean

3.2 注解的方式注入

3.21 注入属性

Spring通过@Autowired注解实现Bean的依赖注入,下面是一个例子:

import org.springframework.beans.factory.BeanNameAware;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Service;
//① 定义一个Service的Bean(不需要在XML中定义Bean)
@Service
public class LogonService implements BeanNameAware{
        //② 分别注入LogDao及UserDao的Bean(不需要在XML中定义property属性注入)
    @Autowired(required=false)
    private LogDao logDao;
    @Autowired
    @Qualifier("userDao")
    private UserDao userDao;
    
    public LogDao getLogDao() {
        return logDao;
    }
    public UserDao getUserDao() {
        return userDao;
    }
    
    public void setBeanName(String beanName) {
        System.out.println("beanName:"+beanName);        
    }
    
    public void initMethod1(){
        System.out.println("initMethod1");
    }
    public void initMethod2(){
        System.out.println("initMethod2");
    }
    
}

在①处,我们使用@Service将LogonService标注为一个Bean,在②处,通过@Autowired注入LogDao及UserDao的Bean。@Autowired默认按类型匹配的方式,在容器查找匹配的Bean,当有且仅有一个匹配的Bean时,Spring将其注入到@Autowired标注的变量中。默认情况下,@Autowired的required属性的值为true,即要求一定要找到匹配的Bean,否则将报异常。如果容器中有一个以上匹配的Bean时,则可以通过@Qualifier注解限定Bean的名称。

3.22 对类方法进行标注

import org.springframework.beans.factory.BeanNameAware;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Service;

@Service
public class LogonService implements BeanNameAware{
    
    private LogDao logDao;
    private UserDao userDao;
    
    
    @Autowired
    public void setLogDao(LogDao logDao) {
        this.logDao = logDao;
    }
    
    @Autowired
    @Qualifier("userDao")
    public void setUserDao(UserDao userDao) {
        System.out.println("auto inject");
        this.userDao = userDao;
    }
    
}

在默认情况下,Spring自动选择匹配入参类型的Bean进行注入,Spring允许对方法入参标注@Qualifier以指定注入Bean的名称。

3.23 标准注解的支持

Spring支持@Resource和@Inject注解,这两个标准注解和@Autowired注解的功能类型,都是对类变量及方法入参提供自动注入的功能。@Resource要求提供一个Bean名称的属性,如果属性为空,则自动采用标注处的变量名或方法名作为Bean的名称。而@Inject和@Autowired一样也是按类型匹配注入的Bean的,只不过它没有required属性。可见不管是@Resource还是@Inject注解,其功能都没有@Autowired丰富,因此除非必须,大可不必在乎这两个注解。(类似于Xml中使用<constructor-arg ref="logDao"></constructor-arg>或者<property name="logDao" ref="logDao"></property>进行注入,如果使用了@Autowired或者Resource等,这不需要在定义Bean时使用属性注入和构造方法注入了)

4、Bean的初始化方法及顺序

Spring 容器中的 Bean 是有生命周期的,Spring 允许 Bean 在初始化完成后以及销毁前执行特定的操作。下面是常用的三种指定特定操作的方法:

  • 通过实现InitializingBean/DisposableBean 接口来定制初始化之后/销毁之前的操作方法;
  • 通过<bean> 元素的 init-method/destroy-method属性指定初始化之后 /销毁之前调用的操作方法;
  • 在指定方法上加上@PostConstruct或@PreDestroy注解来制定该方法是在初始化之后还是销毁之前调用
import javax.annotation.PostConstruct;  
import javax.annotation.PreDestroy;  
  
import org.springframework.beans.factory.DisposableBean;  
import org.springframework.beans.factory.InitializingBean;  
import org.springframework.context.support.ClassPathXmlApplicationContext;  
  
public class InitAndDestroySeqBean implements InitializingBean,DisposableBean {  
  
    public InitAndDestroySeqBean(){  
        System.out.println("执行InitAndDestroySeqBean: 构造方法");  
    }  
      
    @PostConstruct  
    public void postConstruct() {    
       System.out.println("执行InitAndDestroySeqBean: postConstruct");    
    }    
      
    @Override  
    public void afterPropertiesSet() throws Exception {  
        System.out.println("执行InitAndDestroySeqBean: afterPropertiesSet");   
    }  
      
    public void initMethod() {  
        System.out.println("执行InitAndDestroySeqBean: init-method");  
    }  
  
    @PreDestroy  
    public void preDestroy()  {  
        System.out.println("执行InitAndDestroySeqBean: preDestroy");  
    }  
      
    @Override  
    public void destroy() throws Exception {  
        System.out.println("执行InitAndDestroySeqBean: destroy");  
    }  
      
    public void destroyMethod() {  
        System.out.println("执行InitAndDestroySeqBean: destroy-method");  
    }  
      
    public static void main(String[] args) {  
        ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("com/chj/spring/bean.xml");  
        context.close();  
    }  
}  

Spring配置文件:

<?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-2.5.xsd    
 http://www.springframework.org/schema/context    
 http://www.springframework.org/schema/context/spring-context-2.5.xsd">    
   
   <context:annotation-config/>    
     
   <bean id="initAndDestroySeqBean" class="com.chj.spring.InitAndDestroySeqBean" init-method="initMethod" destroy-method="destroyMethod"/>  
</beans> 
  • Bean在实例化的过程中:Constructor > @PostConstruct >InitializingBean > init-method
  • Bean在销毁的过程中:@PreDestroy > DisposableBean > destroy-method
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,657评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,662评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 158,143评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,732评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,837评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,036评论 1 291
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,126评论 3 410
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,868评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,315评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,641评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,773评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,470评论 4 333
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,126评论 3 317
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,859评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,095评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,584评论 2 362
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,676评论 2 351

推荐阅读更多精彩内容