Spring 基于 Java 的容器配置方式详解

目录

1、使用 AnnotationConfigApplicationContext 初始化 Spring 容器

(1)使用构造函数,简单构造一个容器

(2)构造容器时,使用 register(Class…​),传入注册类

(3)构造容器时,调用 scan(String…​) 方法,启用组件扫描

(4)构造支持 Web 应用的 AnnotationConfigWebApplicationContext

2、使用 @Bean 注解

(1)声明一个 Bean :@Configuration + @Bean / 使用接口的默认方法

(2)附加参数的 @Bean 方法

(3)@Bean 中的生命周期回调方法定义

3、使用 @Configuration 注解

(1)注入 @Configuration 内部的 bean 依赖

(2)基于原型 bean 的查找方法注入

(3)@Configuration 注解的工作流程

4、Java 配置中的其他注解

(1)使用 @Import 注解,从外部配置类加载 Bean 定义

(2)使用 @Conditional 注解按条件进行匹配

(5)使用 @ImportResource 注解,根据需要导入 XML 配置


        @Bean 注解,用来标识一个方法(工厂方法),该方法用来实例化、配置并初始化一个被 Spring 容器管理的新对象,@Bean 注解与 xml 配置中的  <bean/> 标签作用类似。虽然你可以把 @Bean 和 @Component 一起使用,但是,正常情况下,该注解更多的是和 @Configuration 一起使用。

        @Configuration 注解,用来标识一个类,指定该类为 Bean 定义的资源文件。此外,在被 @Configuration 注解的类中,内部 Bean 的依赖可以通过调用同一个类中 @Bean 方法来进行注入。最简单的 @Configuration 类如下所示:

@Configuration
public class AppConfig {

    @Bean //bean名称就是方法名称:myService
    public MyService myService() {
        return new MyServiceImpl();
    }
}

        完全 @Configuration 和精简 @Bean 模式

        当 @Bean 方法被定义在一个不是由 @Configuration 注解的类中时,就认为使用了精简模式(比如定义在被 @Component 注解的类中)。 精简模式下的  @Bean 方法不能使用内部的 Bean 依赖(不能调用同一个类中 @Bean 方法),同时也不能使用  CGLIB 代理进行增强。

        当 @Bean 方法被定义在一个由 @Configuration 注解的类中时,就认为使用了完全模式。在完全模式下,内部的 Bean 依赖被重定向到 Spring 容器的生命周期中进行管理,可以阻止 @Bean 方法被另一 @Bean 方法通过常规的 Java 代码进行调用,从而避免在精简模式下,出现一些比较难以追踪的 bug。// 所以还是推荐 @Bean + @Configuration 一起使用,此模式下,Bean 有一个完整的生命周期。

        下边介绍使用 Java 配置创建 Spring 容器的几种方法。

1、使用 AnnotationConfigApplicationContext 初始化 Spring 容器

        // 该节简单介绍了容器的一些实例化代码以及一个 Web 容器

        AnnotationConfigApplicationContext 是 ApplicationContext 接口的一个实现类,提供了多样化的功能,不仅支持 @Configuration 注解的 class 作为资源文件,也支持 @Component 注解的配置,同时还支持 JSR-330 规范的注解。

(1)使用构造函数,简单构造一个容器

        使用 xml 配置文件作为资源注入一样,需要实例化 ClassPathXmlApplicationContext 容器,使用 AnnotationConfigApplicationContext 跟上边情况类似,但是使用 @Configuration 注解标识配置文件,可以在完全不使用 xml 配置的情况下,实例化一个 Spring 容器:

public static void main(String[] args) {
    // AppConfig为@Configuration注解的class
    ApplicationContext ctx = new AnnotationConfigApplicationContext(AppConfig.class);
    MyService myService = ctx.getBean(MyService.class);
    myService.doStuff();
}

(2)构造容器时,使用 register(Class<?>…​),传入注册类

        你可以使用无参构造函数初始化一个 AnnotationConfigApplicationContext 实例,然后调用 register(Class<?>…​) 方法对该实例进行配置,示例如下:

public static void main(String[] args) {
    AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext();
    ctx.register(AppConfig.class, OtherConfig.class);
    ctx.register(AdditionalConfig.class);
    ctx.refresh();
    MyService myService = ctx.getBean(MyService.class);
    myService.doStuff();
}

(3)构造容器时,调用 scan(String…​) 方法,启用组件扫描

为了启用组件扫描,可以在 @Configuration 注解的 class 上进行如下配置:

@Configuration
@ComponentScan(basePackages = "com.acme") 
public class AppConfig  {
    // ...
}

        AnnotationConfigApplicationContext 提供了与 @ComponentScan 注解相同功能的方法 scan(String…​),该方法同样支持容器扫描指定包路径下的组件类型的 calss(被 @Component 等注解标记的类), 示例如下:// 其中 refresh() 是一个很重要的方法

public static void main(String[] args) {
    AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext();
    ctx.scan("com.acme");
    ctx.refresh(); // 这是一个很重要的方法
    MyService myService = ctx.getBean(MyService.class);
}

(4)构造支持 Web 应用的 AnnotationConfigWebApplicationContext

        AnnotationConfigApplicationContext 的 WebApplicationContext 变体可通过 AnnotationConfigWebApplicationContext 获得。你可以在配置了 Spring ContextLoaderListener servlet 监听器、Spring MVC DispatcherServlet 等的时候,使用这个实现。下面的 web.xml 代码片段配置了一个典型的 Spring MVC web 应用程序:// 支持 Web 应用的容器拥有了更多可配置的属性

<web-app>
    <!-- 配置ContextLoaderListener使用AnnotationConfigWebApplicationContext
而不是默认的XmlWebApplicationContext -->
    <context-param>
        <param-name>contextClass</param-name>
        <param-value>
            org.springframework.web.context.support.AnnotationConfigWebApplicationContext
        </param-value>
    </context-param>

    <!-- contextConfigLocation的值必须由一个或多个有逗号或空格分隔的@Configuration类组成。使用完全限定的包路径主要为了支持组件扫描-->
    <context-param>
        <param-name>contextConfigLocation</param-name>
        <param-value>com.acme.AppConfig</param-value>
    </context-param>

    <!-- 像平常一样使用ContextLoaderListener引导根应用程序上下文 -->
    <listener>
        <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
    </listener>

    <!-- 像平常一样声明一个Spring MVC DispatcherServlet -->
    <servlet>
        <servlet-name>dispatcher</servlet-name>
        <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
        <!-- 配置DispatcherServlet使用AnnotationConfigWebApplicationContext
而不是默认的XmlWebApplicationContext -->
        <init-param>
            <param-name>contextClass</param-name>
            <param-value>
                org.springframework.web.context.support.AnnotationConfigWebApplicationContext
            </param-value>
        </init-param>
        <!-- 再一次, contextConfigLocation的值必须由一个或多个有逗号或空格分隔的@Configuration类组成。使用完全限定的包路径主要为了支持组件扫描 -->
        <init-param>
            <param-name>contextConfigLocation</param-name>
            <param-value>com.acme.web.MvcConfig</param-value>
        </init-param>
    </servlet>

    <!-- 映射请求:map all requests for /app/* to the dispatcher servlet -->
    <servlet-mapping>
        <servlet-name>dispatcher</servlet-name>
        <url-pattern>/app/*</url-pattern>
    </servlet-mapping>
</web-app>

2、使用 @Bean 注解

        @Bean 是一个方法级别的注解,它支持以下一些属性的配置:// 可以配置方法名称,是否自动配置,初始化回调方法、销毁回调方法等

@Target({ElementType.METHOD, ElementType.ANNOTATION_TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Bean {

	/**
	 * @AliasFor 用于为注解属性声明别名。
	 */
	@AliasFor("name")
	String[] value() default {};

	@AliasFor("value")
	String[] name() default {};

	@Deprecated
	Autowire autowire() default Autowire.NO;

	boolean autowireCandidate() default true;

	String initMethod() default "";

	String destroyMethod() default AbstractBeanDefinition.INFER_METHOD;

}

(1)声明一个 Bean :@Configuration + @Bean / 使用接口的默认方法

        下面的例子展示了 @Bean 的方法声明:

@Configuration
public class AppConfig {

    @Bean
    public TransferServiceImpl transferService() {
        return new TransferServiceImpl();
    }
}

        你还可以使用默认方法来定义 bean。这允许在默认方法上实现带有 bean 定义的接口来组合 bean 配置。// 这个是要知道的,提前定义一些 bean 

public interface BaseConfig {

    @Bean
    default TransferServiceImpl transferService() {
        return new TransferServiceImpl();
    }
}

@Configuration
public class AppConfig implements BaseConfig {

}

        把 @Bean 方法的返回类型,声明为接口类型,示例如下:// 可以通过类型进行匹配,名称不重要,当只有一个实现类时,具体实现也不重要,但有多个实现时,就需要注意了

@Configuration
public class AppConfig {

    @Bean //返回 TransferService 接口类型
    public TransferService transferService() {
        return new TransferServiceImpl();
    }
}

        但是,对于实现多个接口的组件,或者对于可能由其实现类型引用的组件,声明最具体的返回类型会更安全。

(2)附加参数的 @Bean 方法

        带有 @bean 注释的方法可以具有任意数量的参数,用于描述构建该 bean 所需的依赖项。

@Configuration
public class AppConfig {

    @Bean
    public TransferService transferService(AccountRepository accountRepository) {
        return new TransferServiceImpl(accountRepository);
    }
}

        解析机制与基于构造函数的依赖注入非常相似。// 如果 AccountRepository 不存在,会报错

(3)@Bean 中的生命周期回调方法定义

        任何使用 @Bean 注释定义的类都支持常规生命周期回调,并且可以使用 JSR-250 中的 @PostConstruct 和 @PreDestroy 注释。// 常规的生命周期方法可以在注解中指定

        常规的 Spring 生命周期回调也得到了充分的支持。如果 bean 实现了 InitializingBean、DisposableBean 或 Lifecycle,容器将调用它们各自的方法。

        感知接口的标准集(如 BeanFactoryAware、BeanNameAware、MessageSourceAware、ApplicationContextAware 等)也得到了完全支持。

public class BeanOne {
    public void init() {
        // initialization logic
    }
}

public class BeanTwo {
    public void cleanup() {
        // destruction logic
    }
}

@Configuration
public class AppConfig {
    @Bean(initMethod = "init")
    public BeanOne beanOne() {
        return new BeanOne();
    }

    @Bean(destroyMethod = "cleanup")
    public BeanTwo beanTwo() {
        return new BeanTwo();
    }
}

        当直接使用 Java 时,可以对对象做任何想做的事情,而不总是需要依赖容器生命周期。比如,在上述例子中的 BeanOne 中,在构造过程中直接调用 init() 方法,该方法同样有效,如下例所示:

@Configuration
public class AppConfig {
    @Bean
    public BeanOne beanOne() {
        BeanOne beanOne = new BeanOne();
        beanOne.init();
        return beanOne;
    }
    // ...
}

3、使用 @Configuration 注解

        @Configuration 是一个类级注释,指示对象是 bean 定义文件。

(1)注入 @Configuration 内部的 bean 依赖

        当 bean 之间存在依赖关系时,表达这种依赖关系就像让一个 bean 方法调用另一个 bean 方法一样简单,如下面的示例所示:// 该方式仅在 @Configuration 注解的类中有效

@Configuration
public class AppConfig {

    @Bean
    public BeanOne beanOne() {
        return new BeanOne(beanTwo());
    }

    @Bean
    public BeanTwo beanTwo() {
        return new BeanTwo();
    }
}

(2)基于原型 bean 的查找方法注入

        查找方法注入是一个高级特性,日常开发中很少使用。它的主要应用于当单个作用域的 bean 依赖于原型作用域的 bean 时的场景。点击这里,查看具体使用。

(3)@Configuration 注解的工作流程

        下面的例子,它显示了一个 @Bean 注释的方法被调用了两次:

@Configuration
public class AppConfig {
    @Bean
    public ClientService clientService1() {
        ClientServiceImpl clientService = new ClientServiceImpl();
        clientService.setClientDao(clientDao());
        return clientService;
    }

    @Bean
    public ClientService clientService2() {
        ClientServiceImpl clientService = new ClientServiceImpl();
        clientService.setClientDao(clientDao());
        return clientService;
    }

    @Bean
    public ClientDao clientDao() {
        return new ClientDaoImpl();
    }
}

        在 Spring 中,实例化的 bean 默认为单例作用域。所有 @Configuration 类在启动时被 CGLIB 的子类化。在子类中,调用父方法并创建新实例之前,子类的方法首先会检查容器中是否有(限定作用域的)bean 的缓存。如果有缓存,就不会再创建新的 bean,所以,一个 @Bean 注释的方法被调用了两次,它也只会产生一个 bean 。

4、Java 配置中的其他注解

(1)使用 @Import 注解,从外部配置类加载 Bean 定义

        <import/> 标签在 Spring XML 文件中可用于帮助模块化配置,同样,@Import 注解允许从另一个配置类加载 @Bean 定义,如下例所示:// @Import 注解在 Spring Boot 中非常有用

@Configuration
public class ConfigA {
    @Bean
    public A a() {
        return new A();
    }
}

@Configuration
@Import(ConfigA.class)
public class ConfigB {
    @Bean
    public B b() {
        return new B();
    }
}

        通过上边这种形式,在初始化容器时,不需要同时指定 configA.class 和 configB.class 两个配置文件,仅仅只需要显式地指定 configB.class 就可以了, 如下例所示:

public static void main(String[] args) {
    ApplicationContext ctx = new AnnotationConfigApplicationContext(ConfigB.class);

    // now both beans A and B will be available...
    A a = ctx.getBean(A.class);
    B b = ctx.getBean(B.class);
}

        这种方法简化了容器的实例化,因为只需要处理一个类就可以了,而不需要在构造过程中去记住大量的 @Configuration 类。

        在 Spring Framework 4.2 中,@Import 也支持对常规组件类的引用,类似于调用 AnnotationConfigApplicationContext 中的 register() 方法。所以,如果你想要避免组件扫描,可以通过使用一些配置类作为入口点显式的定义所有组件// Spring Boot 的组件自动配置

@Import 中跨配置类的依赖关系

        前面的示例过于简单。在大多数实际场景中,bean 之间都存在着跨配置类的依赖关系。当使用 @Configuration 类时,Java 编译器会对配置模型施加约束,因为对其他 bean 的引用必须是有效的 Java 语法。// 是引用一个 bean 时,那个 bean 必须存在

        @Bean 方法可以有任意数量的参数来描述 bean 的依赖关系。例如,以下几个 @Configuration 类,每个类都取决于在其他类中声明的 bean : // 推荐使用形式

@Configuration
public class ServiceConfig {
    @Bean
    public TransferService transferService(AccountRepository accountRepository) {
        return new TransferServiceImpl(accountRepository);
    }
}

@Configuration
public class RepositoryConfig {
    @Bean
    public AccountRepository accountRepository(DataSource dataSource) {
        return new JdbcAccountRepository(dataSource);
    }
}

@Configuration
@Import({ServiceConfig.class, RepositoryConfig.class})
public class SystemTestConfig {
    @Bean
    public DataSource dataSource() {
        // return new DataSource
    }
}

public static void main(String[] args) {
    ApplicationContext ctx = new AnnotationConfigApplicationContext(SystemTestConfig.class);
    // everything wires up across configuration classes...
    TransferService transferService = ctx.getBean(TransferService.class);
    transferService.transfer(100.00, "A123", "C456");
}

        在上边配置中,ServiceConfig.calss 需要 RepositoryConfig.class 中定义的 AccountRepository 实例,RepositoryConfig.class 需要 SystemTestConfig.calss 中定义的 DataSource,当其中任何一个实例缺失时,都会导致容器加载失败。

        还有一种方法可以达到同样的效果。记住,@Configuration 类最终只是容器中的另一个 bean:这意味着它们可以像任何其他 bean 一样利用 @Autowired 注解或 @Value 注入以及其他的一些特性。

        注意:利用 @Autowired 注解或 @Value 注入,需要确保以这种方式注入的依赖项只是最简单的类型,因为 @Configuration 类在容器初始化过程中会被很早的进行处理,以这种方式强制注入依赖可能会导致依赖的 bean 意外的过早初始化。所以,应该尽可能使用基于参数的注入,如前面的示例所示。// 使用参数注入时,还可以懒加载

        下面的例子展示了如何将一个 bean 自动装配到另一个 bean:// 注入效果与前边示例一样

@Configuration
public class ServiceConfig {
    @Autowired
    private AccountRepository accountRepository;

    @Bean
    public TransferService transferService() {
        return new TransferServiceImpl(accountRepository);
    }
}

@Configuration
public class RepositoryConfig {
    private final DataSource dataSource;

    public RepositoryConfig(DataSource dataSource) {
        this.dataSource = dataSource;
    }

    @Bean
    public AccountRepository accountRepository() {
        return new JdbcAccountRepository(dataSource);
    }
}

@Configuration
@Import({ServiceConfig.class, RepositoryConfig.class})
public class SystemTestConfig {
    @Bean
    public DataSource dataSource() {
        // return new DataSource
    }
}

public static void main(String[] args) {
    ApplicationContext ctx = new AnnotationConfigApplicationContext(SystemTestConfig.class);
    // everything wires up across configuration classes...
    TransferService transferService = ctx.getBean(TransferService.class);
    transferService.transfer(100.00, "A123", "C456");
}

        基于构造器注入,需要依赖项被过早的初始化,在处理 @Configuration 类时,依赖项就已经被实例化了。

明确的指定导入的 Bean 依赖,确定 bean 定义声明的确切位置

        在前面的场景中,使用 @Autowired 可以正常工作,而且提供了所需的模块化。但是并不能清晰的确定 bean 定义声明的确切位置。例如,作为查看 ServiceConfig 的开发人员,如何确切地知道 @Autowired AccountRepository  这个 bean 是在哪里声明的呢?

        如果上边这种模糊性是不可接受的,并且你希望对引入的 bean 有一个明确的索引,你可以使用以下代码进行定义:// 明确 bean 定义的位置

@Configuration
public class ServiceConfig {

    @Autowired
    private RepositoryConfig repositoryConfig;

    @Bean
    public TransferService transferService() {
        // navigate 'through' the config class to the @Bean method!
        return new TransferServiceImpl(repositoryConfig.accountRepository());
    }
}

        上边代码中,定义 AccountRepository 的位置是完全显式的。但是,ServiceConfig.class 却和 RepositoryConfig.class 紧密的耦合在了一起。所以为了进行权衡。通过使用基于接口或基于抽象类的 @Configuration 类,可以在一定程度上缓解这种紧密的耦合。// 使用接口或抽象类可以进行解耦合

@Configuration
public class ServiceConfig {

    @Autowired
    private RepositoryConfig repositoryConfig;

    @Bean
    public TransferService transferService() {
        return new TransferServiceImpl(repositoryConfig.accountRepository());
    }
}

@Configuration //接口,用来解耦合
public interface RepositoryConfig {

    @Bean
    AccountRepository accountRepository();
}

@Configuration
public class DefaultRepositoryConfig implements RepositoryConfig {

    @Bean
    public AccountRepository accountRepository() {
        return new JdbcAccountRepository(...);
    }
}

@Configuration
@Import({ServiceConfig.class, DefaultRepositoryConfig.class})  // import the concrete config!
public class SystemTestConfig {

    @Bean
    public DataSource dataSource() {
        // return DataSource
    }

}

public static void main(String[] args) {
    ApplicationContext ctx = new AnnotationConfigApplicationContext(SystemTestConfig.class);
    TransferService transferService = ctx.getBean(TransferService.class);
    transferService.transfer(100.00, "A123", "C456");
}

(2)使用 @Conditional 注解按条件进行匹配

        根据系统的要求,通常需要有条件地启用或禁用一个完整的 @Configuration 类甚至单个的 @Bean 方法。一个常见的例子是,在 Spring 环境中启用特定的配置文件时,可以使用 @Profile 注解来激活指定的 beans。

        @Profile 注释实际上是通过使用一个更灵活的注释 @Conditional 实现的。@Conditional 注解指示容器在注册 @Bean 之前,应该满足 org.springframework.context.annotation.Condition 的具体实现类定义的规则

        Condition 接口的实现提供了一个 matches(…) 方法,该方法返回 true 或 false。例如,下面代码展示了 @Profile 注解实现 Condition 接口的匹配过程:// 条件匹配

@Override
public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
    // Read the @Profile annotation attributes
    MultiValueMap<String, Object> attrs = metadata.getAllAnnotationAttributes(Profile.class.getName());
    if (attrs != null) {
        for (Object value : attrs.get("value")) {
            if (context.getEnvironment().acceptsProfiles(((String[]) value))) {
                return true;
            }
        }
        return false;
    }
    return true;
}

        @Conditional 注解的接口文档,请点击这里

(5)使用 @ImportResource 注解,根据需要导入 XML 配置

        Spring 的支持 @Configuration 类并不是为了完全取代 Spring XML。 Spring XML 中的一些工具,如命名空间,仍然是理想的配置容器的方式。所以,在 XML 配置方便或必要的情况下,你可以有诸多选择:比如,使用 ClassPathXmlApplicationContext 以 XML 配置文件为中心的实例化容器方式,或者使用 AnnotationConfigApplicationContext 以 Java 配置为中心的实例化容器方式,使用 Java 配置方式,可以使用 @ImportResource 注解根据需要导入 XML 配置

        更多组合方式,请查看官方文档

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

swadian2008

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值