跳转至

容器功能

1.Spring注入组件的注解

Spring中的传统注解@Component、@Controller、@Service、@Repository,在SpringBoot中仍然有效。

2.@Configuration

@Configuration是 Spring 3.0 添加的一个注解,用来代替原先 Spring 中的 applicationContext.xml 容器配置文件,所有这个配置文件里面能做到的事情都可以通过这个注解所在类来进行注册。

在SpringBoot项目中,依然可以使用Spring的容器文件来注入bean/获取bean,但是不推荐使用

2.1应用实例

例子:使用SpringBoot的注解@Configuration添加/注入组件

(1)Monster.java

package com.li.springboot.bean;

/**
 * @author 李
 * @version 1.0
 */
public class Monster {
    private Integer id;
    private String name;
    private Integer age;
    private String skill;

    //省略无参,全参构造器,getter,setter以及toString方法
}

(2)配置类:BeanConfig.java

package com.li.springboot.config;

import com.li.springboot.bean.Monster;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Scope;

/**
 * @author 李
 * @version 1.0
 * 
 * 1.@Configuration注解标识这是一个配置类,等价于spring的容器配置文件
 * 2.这时我们可以通过@Bean注解注入Bean对象到容器中
 * 3.当一个类被@Configuration标识,这个类也会被注入到容器中,因此也能在容器中获取这个类对象
 */
@Configuration
public class BeanConfig {
    /**
     * 1.@Bean表示给容器添加了一个组件,即Monster的bean对象
     * 2.name = "monster_n1" 在配置,注入Bean时指定的名字/id,如果没有设置,
     *   就使用方法名 monster01() :作为Bean的名字/id
     * 3.Monster :方法的返回类型就是注入的bean类型
     * 4.new Monster(200,"牛魔王",500,"芭蕉扇") 就是注入到容器中的具体 bean信息
     * 5.默认为单例对象,如果要做成多例的,需要添加注解@Scope("prototype")
     */
    @Bean(name = "monster_n1")
    public Monster monster01() {
        return new Monster(200, "牛魔王", 500, "芭蕉扇");
    }
}

(3)MainApp.java

package com.li.springboot;

import com.li.springboot.bean.Monster;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.ConfigurableApplicationContext;

/**
 * @author 李
 * @version 1.0
 */
@SpringBootApplication(scanBasePackages = {"com.li"})
public class MainApp {
    public static void main(String[] args) {
        ConfigurableApplicationContext ioc =
                SpringApplication.run(MainApp.class, args);

        Monster monster_n1 = ioc.getBean("monster_n1", Monster.class);
        Monster monster_n2 = ioc.getBean("monster_n1", Monster.class);
        System.out.println("monster_n1=" + monster_n1
                + ",n1.hashCode=" + monster_n1.hashCode());
        System.out.println("monster_n2=" + monster_n2
                + ",n2.hashCode=" + monster_n2.hashCode());
    }
}

测试结果:成功注入容器,并且默认为单例对象

monster_n1=Monster{id=200, name='牛魔王', age=500, skill='芭蕉扇'},n1.hashCode=1532800776
monster_n2=Monster{id=200, name='牛魔王', age=500, skill='芭蕉扇'},n2.hashCode=1532800776

2.2注意事项和细节

(1)配置类本身也是组件,因此也可以在容器中获取,并且默认也是单例的

(2)SpringBoot2新增特性:proxyBeanMethods--代理 bean 的方法。

  1. ProxyBeanMethods 可以指定 Full 模式和 Lite 模式

  2. Full模式:(proxyBeanMethods = true),保证修修饰的配置类中,每个@Bean 方法被调用多少次返回的组件都是单实例的,是代理方式

  3. Lite模式:(proxyBeanMethods = false),保证修饰的配置类中,每个@Bean方法被调用多少次返回的组件都是新创建的,多例对象,是非代理方式

  4. 特别说明:proxyBeanMethods 在调用 @Bean 方法时才生效,因此需要先获取BeanConfig 组件,再调用方法。而不是直接通过SpringBoot 主程序得到的容器来获取bean,直接通过ioc.getBean() 获取 Bean, proxyBeanMethods 值并不会生效

  5. 如何选择:组件依赖必须使用 Full 模式(默认),如果不需要组件依赖使用 Lite 模式。

  6. Lite 模式也称为轻量级模式,因为不检测依赖关系,运行速度快

(3)配置类可以有多个,就和Spring可以有多个容器配置文件一样


例子:测试proxyBeanMethods--代理 bean 的方法。

以2.1的代码为例,Monster.java不变

(1)修改配置类的注解为@Configuration(proxyBeanMethods = true),其他不变

(2)修改MainApp.java:

package com.li.springboot;

import ...

/**
 * @author 李
 * @version 1.0
 */
@SpringBootApplication(scanBasePackages = {"com.li"})
public class MainApp {
    public static void main(String[] args) {
        //启动SpringBoot应用程序/项目
        ConfigurableApplicationContext ioc =
                SpringApplication.run(MainApp.class, args);

        //1.先得到BeanConfig组件
        BeanConfig beanConfig = ioc.getBean(BeanConfig.class);
        //2.通过beanConfig对象调用方法才能生效!!
        Monster monster_01 = beanConfig.monster01();
        Monster monster_02 = beanConfig.monster01();
        System.out.println("monster_01=" + monster_01 
                           + ",hashcode=" + monster_01.hashCode());
        System.out.println("monster_02=" + monster_02 
                           + ",hashcode=" + monster_02.hashCode());
    }
}

注意:proxyBeanMethods 在调用 @Bean 方法时才生效,因此需要先获取BeanConfig 组件,再调用方法。

测试结果:哈希值相同,说明获取的对象是同一个,为单例对象

monster_01=Monster{id=200, name='牛魔王', age=500, skill='芭蕉扇'},hashcode=1075996552
monster_02=Monster{id=200, name='牛魔王', age=500, skill='芭蕉扇'},hashcode=1075996552

(3)将配置类的注解改为:@Configuration(proxyBeanMethods = false),然后重新测试

测试结果:每次返回的对象都是新的对象,即多例对象

monster_01=Monster{id=200, name='牛魔王', age=500, skill='芭蕉扇'},hashcode=1164699452
monster_02=Monster{id=200, name='牛魔王', age=500, skill='芭蕉扇'},hashcode=594916129

3.@Import

@Import 是 Spring 3.0 添加的新注解,用来修饰 @Configuration 注解修饰的类,在 Spring Boot 里面应用很多,它可以将普通类导入到spring容器中做管理。

源码:

package org.springframework.context.annotation;

import ...

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Import {
    Class<?>[] value();
}

通过源码可以看到该注解可以指定class的数组,通过该数组注入指定类型的Bean

3.1应用实例

(1)创建两个Bean,Dog.java和Cat.java

package com.li.springboot.bean;

/**
 * @author 李
 * @version 1.0
 */
public class Dog {
}
package com.li.springboot.bean;

/**
 * @author 李
 * @version 1.0
 */
public class Cat {
}

(2)配置类BeanConfig.java

package com.li.springboot.config;

import com.li.springboot.bean.Cat;
import com.li.springboot.bean.Dog;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;

/**
 * @author 李
 * @version 1.0
 * 通过@Import方式注入了组件,默认的组件名字/id为类型的全类名:
 *  com.li.springboot.bean.Dog 和 com.li.springboot.bean.Cat
 */
@Import({Dog.class, Cat.class})
@Configuration//标识这是一个配置类,等价于配置文件
public class BeanConfig {

}

(3)MainApp.java

package com.li.springboot;

import ...

/**
 * @author 李
 * @version 1.0
 */
@SpringBootApplication(scanBasePackages = {"com.li"})
public class MainApp {
    public static void main(String[] args) {
        //启动SpringBoot应用程序/项目
        ConfigurableApplicationContext ioc =
                SpringApplication.run(MainApp.class, args);
        //测试@Import
        Dog dogBean = ioc.getBean(Dog.class);
        Cat catBean = ioc.getBean(Cat.class);
        System.out.println("dogBean=" + dogBean);
        System.out.println("catBean=" + catBean);
    }
}

测试结果:

dogBean=com.li.springboot.bean.Dog@12477988
catBean=com.li.springboot.bean.Cat@2caf6912

4.@Conditional

  1. 条件装配:满足 Conditional 指定的条件,才进行组件注入
  2. 它可以修饰类,接口或者枚举类型,还可以修饰在方法上

  3. @Conditional 是一个根注解,它的下面有很多扩展注解

@Conditional扩展注解 作用
@ConditionalOnJava 当JVM版本为指定的版本范围时触发实例化
@ConditionalOnBean 当容器中至少存在一个指定name或class的Bean时,进行实例化
@ConditionalOnMissingBean 当容器中指定name或class的Bean都不存在时,进行实例化
@ConditionalOnExpression 基于SpEL表达式的条件判断,当为true的时候进行实例化
@ConditionalOnClass 当类路径下至少存在一个指定的class时,进行实例化
@ConditionalOnMissingClass 当容器中指定class都不存在时,进行实例化
@ConditionalOnSingleCandidate 当指定的Bean在容器中只有一个,或者有多个但是指定了首选的Bean时触发实例化
@ConditionalOnProperty 当指定的属性有指定的值时进行实例化
@ConditionalOnResource 当类路径下有指定的资源时触发实例化
@ConditionalOnWebApplication 当项目是一个Web项目时进行实例化
@ConditionalOnNotWebApplication 当项目不是一个Web项目时进行实例化
@ConditionalOnJndi 在JNDI存在的条件下触发实例化

4.1应用实例

演示在SpringBoot中通过@ConditionalOnBean来注入组件

(1)指定条件:当容器中有name="monster_nmw"组件时,才注入dog01,代码如下:

package com.li.springboot.config;

import ...

/**
 * @author 李
 * @version 1.0
 */

@Configuration
public class BeanConfig {
    @Bean
    public Monster monster01() {
        return new Monster(200, "牛魔王", 500, "芭蕉扇");
    }

    /**
     * 1.@ConditionalOnBean(name = "monster_nmw")
     *  表示当容器中存在id为monster_nmw的Bean时(对类型不做约束),
     *  dog01这个Dog Bean才能注入到容器中
     * 2.如果不符合条件,则不能注入容器
     */
    @Bean
    @ConditionalOnBean(name = "monster_nmw")
    public Dog dog01() {
        return new Dog();
    }
}

(2)MainApp.java

package com.li.springboot;

import ...

/**
 * @author 李
 * @version 1.0
 */
@SpringBootApplication(scanBasePackages = {"com.li"})
public class MainApp {
    public static void main(String[] args) {
        ConfigurableApplicationContext ioc =
                SpringApplication.run(MainApp.class, args);
        //演示@ConditionalOnBean的使用
        Dog dog01 = ioc.getBean("dog01", Dog.class);
        System.out.println("dog01=" + dog01);
    }
}

测试结果:运行出错,因为容器中没有名为monster_nmw的 Bean,因此不能注入Dog Bean,也就无法通过getBean获取到Dog Bean对象。

image-20230313212731793

5.@ImportResource

作用:原生功能的配置文件引入,可以直接将Spring传统的beans.xml导入到配置类中(可以导入多个xml),可以认为是SpringBoot对Spring的兼容。

5.1应用实例

演示将beans.xml导入到BeanConfig3.java配置类,并测试是否可以获取beans.xml注入的组件

(1)beans.xml

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="...">

    <!--使用传统的spring容器来配置bean-->
    <bean class="com.li.springboot.bean.Monster" id="monster03">
        <property name="id" value="1"/>
        <property name="age" value="100"/>
        <property name="name" value="摩奥默哀"/>
        <property name="skill" value="干饭"/>
    </bean>
</beans>

(2)BeanConfig3.java配置类

package com.li.springboot.config;

import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.ImportResource;

/**
 * @author 李
 * @version 1.0
 */
@Configuration
/*
  导入beans.xml文件(可以导入多个xml)
  使用@ImportResource的locations属性,它可以接收多个值,
  路径可以从类路径或者从根目录开始指定
  这样就可以获取到beans.xml文件中配置的bean
*/
@ImportResource(locations = "classpath:beans.xml")
public class BeanConfig3 {
}

(3)MainApp.java

package com.li.springboot;

import ...

/**
 * @author 李
 * @version 1.0
 */
@SpringBootApplication(scanBasePackages = {"com.li"})
public class MainApp {
    public static void main(String[] args) {
        ConfigurableApplicationContext ioc =
                SpringApplication.run(MainApp.class, args);
        //演示@ImportResource的使用
        Monster monster03 = ioc.getBean("monster03", Monster.class);
        System.out.println(monster03);
    }
}

测试结果:成功从spring的配置文件中拿到配置的bean

Monster{id=1, name='摩奥默哀', age=100, skill='干饭'}

6.配置绑定

配置绑定的作用:使用Java读取到SpringBoot核心配置文件application.properties的内容,并且把它封装到Javabean中。

我们在上一篇文章中已经讲过,在SpringBoot的自动配置包中,一般是XxxAutoConfiguration.java,对应XxxProperties.java,XxxProperties.java又会从配置文件applicatio.properties中去读取设置,如果没有设置值,就保持默认值:

image-20230313183049968

但如果在application.properties中设置的不是自动配置包Bean中对应的属性呢?

也就是说,我们可以自定义Bean,然后在application.properties配置,SpringBoot也可以将配置的属性值封装到自定义的Bean,然后放入容器中。

6.1应用实例

演示将 application.properties 指定的 k-v 和 Javabean 绑定

(1)Furn.java(Javabean)

package com.li.springboot.bean;

import org.springframework.stereotype.Component;
import org.springframework.stereotype.Component;

/**
 * @author 李
 * @version 1.0
 */
@Component
@ConfigurationProperties(prefix = "furn01")//设置匹配的前缀
public class Furn {
    private Integer id;
    private String name;
    private Double price;
    //省略无参构造器,setter,getter,toString方法
}

(2)application.properties

# 设置Furn的属性k-v
# furn01 是用于指定/区别不同的绑定对象,这样可以在绑定Furn bean属性值时,通过前缀进行区分
# id、name、price 就是要绑定的 Furn Bean的属性名
furn01.id=100
furn01.name=sofa
furn01.price=899.9

(3)MainApp.java测试

package com.li.springboot;

import ...

/**
 * @author 李
 * @version 1.0
 */
@SpringBootApplication(scanBasePackages = {"com.li"})
public class MainApp {
    public static void main(String[] args) {
        ConfigurableApplicationContext ioc =
                SpringApplication.run(MainApp.class, args);
        //演示绑定配置自定义Bean
        Furn furn = ioc.getBean("furn", Furn.class);
        System.out.println(furn);
    }
}

测试结果:成功从容器中获取到该Bean

Furn{id=100, name='sofa', price=899.9}

6.2注意事项和使用细节

  1. 配置绑定还有第二种方式,如下:不在Javabean中设置@Component注解,在配置类中设置@EnableConfigurationProperties({xx.class,yy.class})
...
@Configuration
/*开启配置绑定功能,将某个Bean组件自动注册到容器中*/
@EnableConfigurationProperties(Furn.class)
public class BeanConfig {
    ...
}
  1. 如果application.properties有中文,需要转成Unicode编码写入,否则会出现乱码

  2. 使用注解@ConfigurationProperties有时候会提示如下信息,但不影响使用。

image-20230314173948101

如果要解决提示信息,在pom.xml增加依赖即可:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-configuration-processor</artifactId>
    <optional>true</optional>
</dependency>