SpringBoot中bean


1、Bean的配置方式

(1)基于XML配置

在XML配置中,通过<bean> </bean>来定义Bean,通过id或name属性定义Bean的名称,如果未指定id和name属性,Spring则自动将全限定类名作为Bean的名称。通过<property>子元素或者p命名空间的动态属性为Bean注入值。还可以通过<bean>的init-method和destory-method属性指定Bean实现类的方法名来设置生命过程方法(最多指定一个初始化方法和销毁方法)。通过<bean>的scope指定Bean的作用范围。听过<bean>的lazy-init属性指定是否延迟初始化。

当Bean的实现类来源于第三方类库,比如DataSource、HibernateTemplate等,无法在类中标注注解信息,只能通过XML进行配置;而且命名空间的配置,比如aop、context等,也只能采用基于XML的配置。

<bean id=“loginUserDao” class=“com.chinalife.dao.impl.LoginUserDaoImpl”  
        lazy-init=“true” init-method=“myInit” destroy-method=“myDestroy”  
        scope=“prototype”>  
        ……   
</bean>

(2)基于注解的配置

@Component:标注一个普通的Spring Bean类(可以指定Bean名称,未指定时默认为小写字母开头的类名)

@Controller:标注一个SpringMVC控制器类

@Service:标注一个业务逻辑类

@Repository:标注一个DAO类

(3)基于Java类配置

在标注了@Configuration的java类中,通过在类方法标注@Bean定义一个Bean。使用在方法上,标注将该方法的返回值存储到 Spring 容器中。方法必须提供Bean的实例化逻辑。通过@Bean的name属性可以定义Bean的名称,未指定时默认名称为方法名。在方法处通过@Autowired使方法入参绑定Bean,然后在方法中通过代码进行注入;也可以调用配置类的@Bean方法进行注入。通过@Bean的initMethod或destroyMethod指定一个初始化或者销毁方法。通过Bean方法定义处标注@Scope指定Bean的作用范围。通过在Bean方法定义处标注@Lazy指定Bean的延迟初始化。

@Configuration  
public class Conf {   
    @Scope(“prototype”)   
    @Bean(“loginUserDao”)   
    public LoginUserDao loginUserDao() {   
        return new LoginUserDao();   
    }   
}  

2、Bean的注入方式

(1)字段注入

Field Injection,使用 @Autowired 对字段进行注入,该方式是最常用的注入方式,但其实 IDEA 是不推荐这种注入方式的

@Autowired
private Car car;

这种注入方式通过Java的反射机制实现,所以private的成员也可以被注入具体的对象。

(2)构造器注入

Constructor Injection是构造器注入,是我们日常最为推荐的一种使用方式。

@RestController
public class TestController {

    private final TestService testService;

    public TestController(TestService testService){
        this.testService = testService;
    }
}

这种注入方式很直接,通过对象构建的时候建立关系,所以这种方式对对象创建的顺序会有要求,当然Spring会为你搞定这样的先后顺序,除非你出现循环依赖,然后就会抛出异常。

(3)set方法注入

Setter Injection也会用到@Autowired注解,但使用方式与Field Injection有所不同,Field Injection是用在成员变量上,而Setter Injection的时候,是用在成员变量的Setter函数上。

@RestController
public class TestController {

    private TestService testService;

    @Autowired
    public void setTestService(TestService testService){
        this.testService = testService;
    }
}

这种注入方式也很好理解,就是通过调用成员变量的set方法来注入想要使用的依赖对象。

(4)三种依赖注入的对比

可靠性

从对象构建过程和使用过程,看对象在各阶段的使用是否可靠来评判:

  • Field Injection:不可靠
  • Constructor Injection:可靠
  • Setter Injection:不可靠

由于构造函数有严格的构建顺序和不可变性,一旦构建就可用,且不会被更改。

可维护性

主要从更容易阅读、分析依赖关系的角度来评判:

  • Field Injection:差
  • Constructor Injection:好
  • Setter Injection:差

还是由于依赖关键的明确,从构造函数中可以显现的分析出依赖关系,对于我们如何去读懂关系和维护关系更友好。

可测试性

当在复杂依赖关系的情况下,考察程序是否更容易编写单元测试来评判

  • Field Injection:差
  • Constructor Injection:好
  • Setter Injection:好

Constructor InjectionSetter Injection的方式更容易Mock和注入对象,所以更容易实现单元测试。

灵活性

主要根据开发实现时候的编码灵活性来判断:

  • Field Injection:很灵活
  • Constructor Injection:不灵活
  • Setter Injection:很灵活

由于Constructor Injection对Bean的依赖关系设计有严格的顺序要求,所以这种注入方式不太灵活。相反Field InjectionSetter Injection就非常灵活,但也因为灵活带来了局面的混乱,也是一把双刃剑。

循环关系的检测

对于Bean之间是否存在循环依赖关系的检测能力:

  • Field Injection:不检测
  • Constructor Injection:自动检测
  • Setter Injection:不检测

性能表现

不同的注入方式,对性能的影响

  • Field Injection:启动快
  • Constructor Injection:启动慢
  • Setter Injection:启动快

主要影响就是启动时间,由于Constructor Injection有严格的顺序要求,所以会拉长启动时间。

结果一目了然,Constructor Injection在很多方面都是优于其他两种方式的,所以Constructor Injection通常都是首选方案!

3、初始化和销毁方法

(1)初始化和销毁方法

初始化方法:

1、实现InitializingBean,重写afterPropertiesSet方法

2、直接使用initBean方法,需要指定init-method

3、使用@PostConstruct注解

销毁方法:

1、实现 DisposableBean,重写destroy方法;

2、直接使用destroy方法,需要指定destroy-method

3、使用@PreDestroy注解

public class InitSortTest implements InitializingBean,DisposableBean{

    private String name;

    /**
     * 构造方法
     * @param name
     */
    public InitSortTest(String name) {
        this.name = name;
        System.out.println("InitSortTest name = [" + name + "]");
    }

//------------------3种初始化方法--------------------------------------    

    //实现InitializingBean,重写afterPropertiesSet方法
    @Override
    public void afterPropertiesSet() throws Exception {
        System.out.println("InitializingBean afterPropertiesSet 执行");
    }

    //直接使用initBean方法,需要指定init-method
    public  void initBean(){
        System.out.println("initBean  执行");
    }

    //使用@PostConstruct注解
    @PostConstruct
    public void postConstruct() {
        System.out.println("@PostConstruct 执行");
    }

//------------------3种销毁方法--------------------------------------

    //实现 DisposableBean,重写destroy方法;
    @Override
    public void destroy() throws Exception {
        System.out.println("DisposableBean destroy 执行");
    }

    //直接使用destroy方法,需要指定destroy-method
    public void destroyBean() {
        System.out.println("destroyBean 执行");
    }

    //使用@PreDestroy注解
    @PreDestroy
    public void preDestroy() {
        System.out.println("@PreDestroy 执行");
    }
}

(2)创建配置类

@Configuration
public class InitConfig {

    //指定初始化和销毁方法
    @Bean(initMethod = "initBean",destroyMethod = "destroyBean")
    public  InitSortTest  initSortTest() {
        return new InitSortTest("测试");
    }
}

(3)结论

初始化顺序:构造方法 → @PostConstruct → afterPropertiesSet → initBean。

销毁顺序 :@PreDestroy → DisposableBean destroy → destroyBean

4、@Autowired和@Resource

spring不但支持自己定义的@Autowired注解,还支持几个由JSR-250规范定义的注解,它们分别是@Resource@PostConstruct 以及 @PreDestroy

@Resource 的作用相当于 @Autowired只不过 @Autowired 按byType自动注入,而 @Resource 默认按 byName自动注入罢了。

@Resource有两个属性是比较重要的,分是name和type,Spring将@Resource注解的name属性解析为bean的名字,而type属性则解析为bean的类型。所以如果使用name属性,则使用byName的自动注入策略,而使用type属性时则使用byType自动注入策略。如果既不指定name也不指定type属性,这时将通过反射机制使用byName自动注入策略。

@Resource装配顺序:

  1. 如果同时指定了name和type,则从Spring上下文中找到唯一匹配的bean进行装配,找不到则抛出异常
  2. 如果指定了name,则从上下文中查找名称(id)匹配的bean进行装配,找不到则抛出异常
  3. 如果指定了type,则从上下文中找到类型匹配的唯一bean进行装配,找不到或者找到多个,都会抛出异常
  4. 如果既没有指定name,又没有指定type,则自动按照byName方式进行装配;如果没有匹配,则回退为一个原始类型进行匹配,如果匹配则自动装配;

@Autowired 与@Resource的区别:

1、 @Autowired与@Resource都可以用来装配bean. 都可以写在字段上,或写在setter方法上。

2、 @Autowired默认按类型装配(这个注解是属业spring的),默认情况下必须要求依赖对象必须存在,如果要允许null值,可以设置它的required属性为false,如:@Autowired(required=false) ,如果我们想使用名称装配可以结合@Qualifier注解进行使用,如下:

@Autowired
@Qualifier("baseDao")
private BaseDao baseDao;

3、@Resource(这个注解属于J2EE的),默认按照名称进行装配,名称可以通过name属性进行指定,如果没有指定name属性,当注解写在字段上时,默认取字段名进行安装名称查找,如果注解写在setter方法上默认取属性名进行装配。当找不到与名称匹配的bean时才按照类型进行装配。但是需要注意的是,如果name属性一旦指定,就只会按照名称进行装配。

@Resource(name="baseDao")
private BaseDao baseDao;

推荐使用:@Resource注解在字段上,这样就不用写setter方法了,并且这个注解是属于J2EE的,减少了与spring的耦合。这样代码看起就比较优雅。


  目录