Spring IOC容器注解大全—基于Java的容器配置(@Bean 、 @Configuration、@PropertySource)

本文涉及的产品
云解析 DNS,旗舰版 1个月
全局流量管理 GTM,标准版 1个月
公共DNS(含HTTPDNS解析),每月1000万次HTTP解析
简介: Spring IOC容器注解大全—基于Java的容器配置(@Bean 、 @Configuration、@PropertySource)

本节介绍了如何在你的Java代码中使用注解来配置Spring容器。它包括以下主题。

    • 基本概念:@Bean 和 @Configuration
    • 通过使用 AnnotationConfigApplicationContext 实例化Spring容器
    • 使用 @Bean 注解
    • 使用 @Configuration 注解
    • 构建基于Java的配置
    • Bean定义配置
    • PropertySource 抽象
    • 使用 @PropertySource
    • 声明中的占位符解析

    一. 基本概念:@Bean@Configuration

    Spring的Java配置支持的核心工件是 @Configuration 注解的类和 @Bean 注解的方法。

    @Bean 注解用来表示一个方法实例化、配置和初始化了一个新的对象,由Spring IoC容器管理。对于那些熟悉Spring的 <beans/> XML配置的人来说,@Bean 注解的作用与 <bean/> 元素的作用相同。你可以在任何Spring @Component 中使用 @Bean 注解的方法。然而,它们最常被用于 @Configuration Bean。

    @Configuration 来注解一个类,表明它的主要目的是作为Bean定义的来源。此外, @Configuration 类允许通过调用同一个类中的其他 @Bean 方法来定义bean间的依赖关系。最简单的 @Configuration 类如下

    @Configuration
    public class AppConfig {
        @Bean
        public MyServiceImpl myService() {
            return new MyServiceImpl();
        }
    }

    image.gif

    前面的 AppConfig 类等同于下面的 Spring <beans/> XML:

    <beans>
        <bean id="myService" class="com.acme.services.MyServiceImpl"/>
    </beans>

    image.gif

    完整的 @Configuration 与 "精简的" @Bean 模式?

    @Bean 方法被声明在没有 @Configuration 注解的类中时,它们被称为以 "精简" 模式处理。在 @Component 或甚至在一个普通的类中声明的Bean方法被认为是 "精简" 的,包含类的主要目的不同,而 @Bean 方法是那里的一种奖励。例如,服务组件可以通过每个适用的组件类上的一个额外的 @Bean 方法向容器暴露管理视图。在这种情况下,@Bean 方法是一种通用的工厂方法机制。

    与完整的 @Configuration 不同,精简的 @Bean 方法不能声明bean间的依赖关系。相反,它们对其包含的组件的内部状态进行操作,也可以选择对其可能声明的参数进行操作。因此,这样的 @Bean 方法不应该调用其他的 @Bean 方法。每个这样的方法从字面上看只是一个特定的Bean引用的工厂方法,没有任何特殊的运行时语义。这里的正面效果是,在运行时不需要应用CGLIB子类,所以在类的设计方面没有任何限制(也就是说,包含的类可以是 final 的等等)。

    在常见的情况下,@Bean 方法要在 @Configuration 类中声明,确保始终使用 "完整" 模式,因此跨方法引用会被重定向到容器的生命周期管理。这可以防止同一个 @Bean 方法被意外地通过普通的Java调用来调用,这有助于减少在 "精简" 模式下操作时很难追踪的细微Bug。

    下面几节将深入讨论 @Bean@Configuration 注解。然而,首先,我们将介绍通过使用基于Java的配置来创建spring容器的各种方法。

    二. 通过使用 AnnotationConfigApplicationContext 实例化Spring容器

    下面的章节记录了Spring的 AnnotationConfigApplicationContext,它在Spring 3.0中引入。这个多功能的 ApplicationContext 实现不仅能够接受 @Configuration 类作为输入,还能够接受普通的 @Component 类和用JSR-330元数据注解的类。

    @Configuration 类被提供为输入时,@Configuration 类本身被注册为Bean定义,该类中所有声明的 @Bean 方法也被注册为Bean定义。

    @Component 和JSR-330类被提供时,它们被注册为bean定义,并且假定DI元数据如 @Autowired@Inject 在必要时被用于这些类。

    1、简单构造

    与实例化 ClassPathXmlApplicationContext 时使用Spring XML文件作为输入一样,你可以在实例化 AnnotationConfigApplicationContext 时使用 @Configuration 类作为输入。这使得Spring容器的使用完全不需要XML,正如下面的例子所示:

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

    image.gif

    如前所述,AnnotationConfigApplicationContext 不限于只与 @Configuration 类一起工作。任何 @Component 或JSR-330注解的类都可以作为输入提供给构造函数,正如下面的例子所示:

    public static void main(String[] args) {
        ApplicationContext ctx = new AnnotationConfigApplicationContext(MyServiceImpl.class, Dependency1.class, Dependency2.class);
        MyService myService = ctx.getBean(MyService.class);
        myService.doStuff();
    }

    image.gif

    前面的例子假设 MyServiceImplDependency1Dependency2 使用Spring的依赖注入注解,如 @Autowired

    2、通过使用 register(Class<?>…) 以编程方式构建容器。

    你可以通过使用无参数构造函数来实例化 AnnotationConfigApplicationContext,然后通过 register() 方法来配置它。这种方法在以编程方式构建 AnnotationConfigApplicationContext 时特别有用。下面的例子展示了如何做到这一点:

    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();
    }

    image.gif

    3、用 scan(String…) 启用组件扫描。

    为了启用组件扫描,你可以对你的 @Configuration 类添加如下注解:

    @Configuration
    @ComponentScan(basePackages = "com.acme") (1)
    public class AppConfig  {
        // ...
    }
    (1)这个注解可以实现组件扫描。

    image.gif

    在前面的例子中,com.acme 包被扫描以寻找任何 @Component 注解的类,这些类被注册为容器中的Spring Bean定义。AnnotationConfigApplicationContext 暴露了 scan(String…) 方法,以实现同样的组件扫描功能,如下例所示:

    public static void main(String[] args) {
        AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext();
        ctx.scan("com.acme");
        ctx.refresh();
        MyService myService = ctx.getBean(MyService.class);
    }

    image.gif

    请记住,@Configuration 类是用 @Component元注解的,所以它们是组件扫描的候选者。在前面的例子中,假设 AppConfig 是在 com.acme 包(或下面的任何包)中声明的,它在调用 scan() 时被选中。在 refresh() 时,它的所有 @Bean 方法都被处理并注册为容器中的 bean 定义。

    4、用 AnnotationConfigWebApplicationContext 支持Web应用程序

    AnnotationConfigApplicationContext 的一个 WebApplicationContext 变体可以用 AnnotationConfigWebApplicationContext。你可以在配置Spring ContextLoaderListener servlet listener、Spring MVC DispatcherServlet 等时使用这个实现。下面的 web.xml 片段配置了一个典型的Spring MVC Web应用(注意使用 contextClasscontext-paraminit-param):

    <web-app>
        <!-- Configure ContextLoaderListener to use AnnotationConfigWebApplicationContext
            instead of the default XmlWebApplicationContext -->
        <context-param>
            <param-name>contextClass</param-name>
            <param-value>
                org.springframework.web.context.support.AnnotationConfigWebApplicationContext
            </param-value>
        </context-param>
        <!-- Configuration locations must consist of one or more comma- or space-delimited
            fully-qualified @Configuration classes. Fully-qualified packages may also be
            specified for component-scanning -->
        <context-param>
            <param-name>contextConfigLocation</param-name>
            <param-value>com.acme.AppConfig</param-value>
        </context-param>
        <!-- Bootstrap the root application context as usual using ContextLoaderListener -->
        <listener>
            <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
        </listener>
        <!-- Declare a Spring MVC DispatcherServlet as usual -->
        <servlet>
            <servlet-name>dispatcher</servlet-name>
            <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
            <!-- Configure DispatcherServlet to use AnnotationConfigWebApplicationContext
                instead of the default XmlWebApplicationContext -->
            <init-param>
                <param-name>contextClass</param-name>
                <param-value>
                    org.springframework.web.context.support.AnnotationConfigWebApplicationContext
                </param-value>
            </init-param>
            <!-- Again, config locations must consist of one or more comma- or space-delimited
                and fully-qualified @Configuration classes -->
            <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>

    image.gif

    对于编程用例,GenericWebApplicationContext 可以作为 AnnotationConfigWebApplicationContext 的替代

    三.使用 @Bean 注解

    @Bean 是一个方法级注解,是XML <bean/> 元素的直接类似物。该注解支持 <bean/> 所提供的一些属性,例如:

      • init-method
      • destroy-method
      • autowiring
      • name.

      你可以在 @Configuration 或 @Component 注解的类中使用 @Bean 注解

      1、声明一个 Bean

      为了声明一个Bean,你可以用 @Bean 注解来注解一个方法。你可以用这个方法在 ApplicationContext 中注册一个Bean定义,该类型被指定为该方法的返回值。默认情况下,Bean的名字和方法的名字是一样的。下面的例子显示了一个 @Bean 方法声明:

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

      image.gif

      前面的配置完全等同于下面的Spring XML。

      <beans>
          <bean id="transferService" class="com.acme.TransferServiceImpl"/>
      </beans>

      image.gif

      这两个声明使 ApplicationContext 中一个名为 transferService 的Bean可用,并与 TransferServiceImpl 类型的对象实例绑定,正如下面的文字图片所示:

      transferService -> com.acme.TransferServiceImpl

      image.gif

      你也可以使用 default 方法来定义Bean。这允许通过在默认方法上实现带有Bean定义的接口来组成Bean配置:

      public interface BaseConfig {
          @Bean
          default TransferServiceImpl transferService() {
              return new TransferServiceImpl();
          }
      }
      @Configuration
      public class AppConfig implements BaseConfig {
      }

      image.gif

      你也可以用一个接口(或基类)的返回类型来声明你的 @Bean 方法,如下例所示:

      @Configuration
      public class AppConfig {
          @Bean
          public TransferService transferService() {
              return new TransferServiceImpl();
          }
      }

      image.gif

      然而,这将提前类型预测的可见性限制在指定的接口类型(TransferService)。然后,只有在受影响的 singleton Bean被实例化后,容器才知道完整的类型(TransferServiceImpl)。非lazy单体Bean根据它们的声明顺序被实例化,所以你可能会看到不同的类型匹配结果,这取决于另一个组件何时试图通过非声明的类型进行匹配(比如 @Autowired TransferServiceImpl,它只在 transferService Bean被实例化后才会解析)

      2、Bean 依赖

      一个 @Bean 注解的方法可以有任意数量的参数,描述构建该Bean所需的依赖关系。例如,如果我们的 TransferService 需要一个 AccountRepository,我们可以用一个方法参数将这种依赖关系具体化,如下例所示:

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

      image.gif

      解析机制与基于构造函数的依赖注入基本相同

      3、接收生命周期的回调

      任何用 @Bean 注解定义的类都支持常规的生命周期回调,并且可以使用JSR-250的 @PostConstruct 和 @PreDestroy 注解。更多细节请参见 JSR-250注解。

      常规的Spring 生命周期 回调也被完全支持。如果一个bean实现了 InitializingBean、DisposableBean 或 Lifecycle,它们各自的方法就会被容器调用。

      标准的 *Aware 接口集(如 BeanFactoryAware、BeanNameAware、MessageSourceAware、ApplicationContextAware 等)也被完全支持。

      @Bean 注解支持指定任意的初始化和销毁回调方法,就像Spring XML在 bean 元素上的 init-method 和 destroy-method 属性一样,如下例所示:

      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();
          }
      }

      image.gif

      默认情况下,用Java配置定义的具有 public 的 closeshutdown 方法的Bean会自动被列入销毁回调。如果你有一个 public 的 closeshutdown 方法,并且你不希望它在容器关闭时被调用,你可以在你的Bean定义中添加 @Bean(destroyMethod = "") 来禁用默认 (inferred) 模式。

      你可能想对你用JNDI获取的资源默认这样做,因为它的生命周期是在应用程序之外管理的。特别是,要确保总是对 DataSource 这样做,因为它在Jakarta EE应用服务器上已知是有问题的。

      下面的例子显示了如何阻止一个 DataSource 的自动销毁回调。

      @Bean(destroyMethod = "")
      public DataSource dataSource() throws NamingException {
          return (DataSource) jndiTemplate.lookup("MyDS");
      }

      image.gif

      另外,对于 @Bean 方法,你通常使用程序化的JNDI查找,要么使用Spring的 JndiTemplateJndiLocatorDelegate helper,要么直接使用JNDI InitialContext,但不使用 JndiObjectFactoryBean 变体(这将迫使你将返回类型声明为 FactoryBean 类型,而不是实际的目标类型,使得它难以用于其他 @Bean 方法中的交叉引用调用,这些方法打算引用这里提供的资源)。

      就前文例子中的 BeanOne 而言,在构造过程中直接调用 init() 方法同样有效,正如下面的例子所示:

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

      image.gif

      4、指定 Bean 的 Scope

      Spring包括 @Scope 注解,这样你就可以指定Bean的 scope。

      使用 @Scope 注解

      你可以指定你用 @Bean 注解定义的 Bean 应该有一个特定的 scope。你可以使用 Bean Scopes部分中指定的任何一个标准 scope。

      默认的scope是 singleton,但你可以用 @Scope 注解来覆盖它,如下例所示:

      @Configuration
      public class MyConfiguration {
          @Bean
          @Scope("prototype")
          public Encryptor encryptor() {
              // ...
          }
      }

      image.gif

      @Scope 和 scoped-proxy

      Spring提供了一种通过 scope 代理 来处理scope 依赖的便捷方式。使用XML配置时,创建这种代理的最简单方法是 <aop:scoped-proxy/> 元素。在Java中用 @Scope 注解配置你的Bean,提供了与 proxyMode 属性相当的支持。默认是 ScopedProxyMode.DEFAULT,这通常表示不应该创建任何 scope 代理,除非在组件扫描指令级别配置了不同的默认值。你可以指定 ScopedProxyMode.TARGET_CLASS、ScopedProxyMode.INTERFACES 或 ScopedProxyMode.NO。

      如果你把XML参考文档中的 scope 代理例子(见 scope 代理)移植到我们使用Java的 @Bean 上,它类似于以下内容:

      // an HTTP Session-scoped bean exposed as a proxy
      @Bean
      @SessionScope
      public UserPreferences userPreferences() {
          return new UserPreferences();
      }
      @Bean
      public Service userService() {
          UserService service = new SimpleUserService();
          // a reference to the proxied userPreferences bean
          service.setUserPreferences(userPreferences());
          return service;
      }

      image.gif

      自定义Bean的命名

      默认情况下,配置类使用 @Bean 方法的名称作为结果Bean的名称。然而,这个功能可以通过 name 属性来重写,正如下面的例子所示:

      @Configuration
      public class AppConfig {
          @Bean("myThing")
          public Thing thing() {
              return new Thing();
          }
      }

      image.gif

      Bean 别名

      正如在 Bean 命名中所讨论的,有时最好给一个Bean起多个名字,也就是所谓的Bean别名。@Bean 注解的 name 属性接受一个 String 数组来实现这一目的。下面的例子展示了如何为一个Bean设置若干别名:

      @Configuration
      public class AppConfig {
          @Bean({"dataSource", "subsystemA-dataSource", "subsystemB-dataSource"})
          public DataSource dataSource() {
              // instantiate, configure and return DataSource bean...
          }
      }

      image.gif

      Bean 描述(Description)

      有时,为 Bean 提供更详细的文本描述是有帮助的。当Bean被暴露(也许是通过JMX)用于监控目的时,这可能特别有用。 为了给 @Bean 添加描述,你可以使用 @Description 注解,如下图所示:

      @Configuration
      public class AppConfig {
          @Bean
          @Description("Provides a basic example of a bean")
          public Thing thing() {
              return new Thing();
          }
      }

      image.gif

      四. 使用 @Configuration 注解

      @Configuration 是一个类级注解,表示一个对象是Bean定义的来源。@Configuration 类通过 @Bean 注解的方法声明bean。对 @Configuration 类上的 @Bean 方法的调用也可以用来定义bean间的依赖关系。

      1、注入bean间的依赖

      当Bean相互之间有依赖关系时,表达这种依赖关系就像让一个Bean方法调用另一个一样简单,正如下面的例子所示:

      @Configuration
      public class AppConfig {
          @Bean
          public BeanOne beanOne() {
              return new BeanOne(beanTwo());
          }
          @Bean
          public BeanTwo beanTwo() {
              return new BeanTwo();
          }
      }

      image.gif

      在前面的例子中,beanOne 通过构造函数注入收到了对 beanTwo 的引用。

      这种声明bean间依赖关系的方法只有在 @Configuration 类中声明了 @Bean 方法时才有效。你不能通过使用普通的 @Component 类来声明bean间的依赖关系。

      2、查询方法注入

      如前所述, 查询方法注入是一个高级功能,你应该很少使用。在 singleton scope 的Bean对 prototype scope 的Bean有依赖性的情况下,它是很有用的。为这种类型的配置使用Java提供了实现这种模式的自然手段。下面的例子展示了如何使用查找方法注入:

      public abstract class CommandManager {
          public Object process(Object commandState) {
              // grab a new instance of the appropriate Command interface
              Command command = createCommand();
              // set the state on the (hopefully brand new) Command instance
              command.setState(commandState);
              return command.execute();
          }
          // okay... but where is the implementation of this method?
          protected abstract Command createCommand();
      }

      image.gif

      通过使用Java配置,你可以创建一个 CommandManager 的子类,其中抽象的 createCommand() 方法被重载,这样它就可以查找到一个新的(prototype) command 对象。下面的例子显示了如何做到这一点:

      @Bean
      @Scope("prototype")
      public AsyncCommand asyncCommand() {
          AsyncCommand command = new AsyncCommand();
          // inject dependencies here as required
          return command;
      }
      @Bean
      public CommandManager commandManager() {
          // return new anonymous implementation of CommandManager with createCommand()
          // overridden to return a new prototype Command object
          return new CommandManager() {
              protected Command createCommand() {
                  return asyncCommand();
              }
          }
      }

      image.gif

      3、关于基于Java的配置如何在内部工作的进一步信息

      考虑一下下面的例子,它显示了一个 @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();
          }
      }

      image.gif

      clientDao()clientService1()clientService2() 中被调用了一次。由于该方法创建了一个新的 ClientDaoImpl 实例并将其返回,你通常会期望有两个实例(每个服务都有一个)。这肯定是有问题的:在Spring中,实例化的Bean默认有一个 singleton scope。这就是神奇之处。所有的 @Configuration 类都是在启动时用 CGLIB 子类化的。在子类中,子方法首先检查容器中是否有任何缓存(scope)的Bean,然后再调用父方法并创建一个新实例。

      由于CGLIB在启动时动态地添加功能,所以有一些限制。特别是,配置类不能是 final 的。然而,配置类的任何构造函数都是允许的,包括使用 @Autowired 或单一的非默认构造函数声明进行默认注入。

      如果你想避免任何CGLIB施加的限制,可以考虑在非 @Configuration 类中声明你的 @Bean 方法(例如,在普通的 @Component 类中声明),或者用 @Configuration(proxyBeanMethods = false) 来注释你的配置类。这样,@Bean 方法之间的跨方法调用就不会被拦截,所以你必须完全依赖构造函数或方法级别的依赖注入

      五. 构建基于Java的配置

      Spring基于Java的配置功能让你可以编写注解,这可以降低配置的复杂性。

      1、使用 @Import 注解

      就像 <import/> 元素在Spring XML文件中被用来帮助模块化配置一样,@Import 注解允许从另一个配置类中加载 @Bean 定义,如下例所示:

      @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();
          }
      }

      image.gif

      现在,在实例化上下文时不需要同时指定 ConfigA.classConfigB.class,而只需要明确提供 ConfigB,正如下面的例子所示:

      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);
      }

      image.gif

      这种方法简化了容器的实例化,因为只需要处理一个类,而不是要求你在构建过程中记住潜在的大量 @Configuration 类。

      2、在导入的 @Bean 定义上注入依赖

      前面的例子是可行的,但也是简单的。在大多数实际情况下,Bean在配置类之间有相互依赖的关系。当使用XML时,这不是一个问题,因为不涉及编译器,你可以声明 ref="someBean" 并相信Spring会在容器初始化过程中解决这个问题。当使用 @Configuration 类时,Java编译器会对配置模型进行约束,即对其他Bean的引用必须是有效的Java语法。

      幸运的是,解决这个问题很简单。一个 @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");
      }

      image.gif

      还有一种方法可以达到同样的效果。记住,@Configuration 类最终只是容器中的另一个Bean。这意味着它们可以像其他Bean一样利用 @Autowired@Value 注入以及其他功能。

      下面的例子显示了一个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");
      }

      image.gif

      从Spring Framework 4.3开始,@Configuration 类中的构造函数注入才被支持。还要注意的是,如果目标Bean只定义了一个构造函数,就不需要指定 @Autowired

      在前面的场景中,使用 @Autowired 效果很好,并提供了所需的模块化,但确定自动注入的Bean定义到底在哪里声明,还是有些模糊。例如,作为一个查看 ServiceConfig 的开发者,你怎么知道 @Autowired AccountRepository Bean到底是在哪里声明的?它在代码中并不明确,而这可能就很好。请记住, Spring Tools for Eclipse提供的工具可以呈现图形,显示一切是如何注入的,这可能就是你所需要的。另外,你的Java IDE可以很容易地找到 AccountRepository 类型的所有声明和使用,并快速显示返回该类型的 @Bean 方法的位置。

      在不能接受这种模糊性的情况下,你希望在你的IDE中从一个 @Configuration 类直接导航到另一个,可以考虑自动注入配置类本身。下面的例子展示了如何做到这一点:

      @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());
          }
      }

      image.gif

      在前面的情况下,AccountRepository 的定义是完全明确的。然而,ServiceConfig 现在与 RepositoryConfig 紧密耦合了。这就是权衡的结果。通过使用基于接口或基于抽象类的 @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");
      }

      image.gif

      现在,ServiceConfig 与具体的 DefaultRepositoryConfig 是松散耦合的,内置的IDE工具仍然有用。你可以轻松获得 RepositoryConfig 实现的类型层次。这样一来,浏览 @Configuration 类和它们的依赖关系就变得与浏览基于接口的代码的通常过程没有什么不同。

      如果你想影响某些Bean的启动创建顺序,可以考虑将其中一些Bean声明为 @Lazy(在第一次访问时创建,而不是在启动时创建)或者声明为 @DependsOn 某些其他Bean(确保特定的其他Bean在当前Bean之前创建,超出后者的直接依赖关系)。

      3、有条件地包括 @Configuration 类或 @Bean 方法

      根据一些任意的系统状态,有条件地启用或禁用一个完整的 @Configuration 类,甚至是单个的 @Bean 方法,往往是很有用的。一个常见的例子是使用 @Profile 注解来激活Bean,只有在Spring Environment 中启用了特定的配置文件时。

      @Profile 注解实际上是通过使用一个更灵活的注解来实现的,这个注解叫做@Conditional  

      @Conditional 注解指出了特定的 org.springframework.context.annotation.Condition 实现,在注册 @Bean 之前应该参考这些实现。

      Condition 接口的实现提供了一个 matches(…) 方法,它返回 truefalse。例如,下面的列表显示了用于 @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;
      }

      image.gif

      六. Environment 抽象

      Environment 接口是一个集成在容器中的抽象,它对 application environment 的两个关键方面进行建模:配置文件(profiles) 和 属性(properties)。

      profile是一个命名的、逻辑上的bean定义组,只有在给定的profile处于活动状态时才会在容器中注册。无论是用 XML 定义的还是用注解定义的,Bean 都可以被分配给一个profile。Environment 对象在profile方面的作用是确定哪些profile(如果有的话)是当前活动(active)的,以及哪些profile(如果有的话)应该是默认活动的。

      属性(Properties)在几乎所有的应用程序中都扮演着重要的角色,它可能来自各种来源:properties 文件、JVM系统属性、系统环境变量、JNDI、Servlet上下文参数、特设的 Properties 对象、Map 对象等等。与属性有关的 Environment 对象的作用是为用户提供一个方便的服务接口,用于配置属性源并从它们那里解析属性。

      1. Bean定义配置

      Bean定义配置(Bean definition profiles) 在核心容器中提供了一种机制,允许在不同的环境中注册不同的bean。“环境”这个词对不同的用户来说意味着不同的东西,而这个功能可以帮助许多用例,包括。

        • 在开发中针对内存中的数据源工作,而在QA或生产中从JNDI查找相同的数据源。
        • 仅在将应用程序部署到 performance 环境中时才注册监控基础设施。
        • 为 customer A 与 customer B 的部署注册定制的bean实现。

        考虑一下实际应用中的第一个用例,它需要一个 DataSource。在一个测试环境中,配置可能类似于以下:

        @Bean
        public DataSource dataSource() {
            return new EmbeddedDatabaseBuilder()
                .setType(EmbeddedDatabaseType.HSQL)
                .addScript("my-schema.sql")
                .addScript("my-test-data.sql")
                .build();
        }

        image.gif

        现在考虑如何将这个应用程序部署到QA或production环境中,假设该应用程序的数据源是在生产应用服务器的JNDI目录下注册的。我们的 dataSource bean现在看起来如下:

        @Bean(destroyMethod = "")
        public DataSource dataSource() throws Exception {
            Context ctx = new InitialContext();
            return (DataSource) ctx.lookup("java:comp/env/jdbc/datasource");
        }

        image.gif

        问题是如何根据当前环境在使用这两种变化之间切换。随着时间的推移,Spring用户已经设计出了许多方法来完成这一工作,通常是依靠系统环境变量和包含 ${placeholder} 标记的XML <import/> 语句的组合,根据环境变量的值解析到正确的配置文件路径。Bean定义配置是一个核心的容器功能,为这个问题提供了一个解决方案。

        如果我们把前面的例子中显示的环境特定的Bean定义的用例进行概括,我们最终需要在某些情况下注册某些Bean定义,但在其他情况下不需要。你可以说,你想在情况A中注册某种类型的bean定义,而在情况B中注册另一种类型。

        1.1使用 @Profile

        @Profile注解让你表明当一个或多个指定的配置文件处于活动状态时,一个组件就有资格注册。使用我们前面的例子,我们可以重写 dataSource 配置如下:

        @Configuration
        @Profile("development")
        public class StandaloneDataConfig {
            @Bean
            public DataSource dataSource() {
                return new EmbeddedDatabaseBuilder()
                    .setType(EmbeddedDatabaseType.HSQL)
                    .addScript("classpath:com/bank/config/sql/schema.sql")
                    .addScript("classpath:com/bank/config/sql/test-data.sql")
                    .build();
            }
        }

        image.gif

        @Configuration
        @Profile("production")
        public class JndiDataConfig {
            @Bean(destroyMethod = "") (1)
            public DataSource dataSource() throws Exception {
                Context ctx = new InitialContext();
                return (DataSource) ctx.lookup("java:comp/env/jdbc/datasource");
            }
        }
        (1)@Bean(destroyMethod = "") 禁用默认的销毁方法推理。

        image.gif

        如前所述,对于 @Bean 方法,你通常会选择使用程序化的JNDI查找,通过使用Spring的 JndiTemplate/JndiLocatorDelegat helper 或前面显示的直接使用JNDI InitialContext,但不使用 JndiObjectFactoryBean 的变体,这将迫使你将返回类型声明为 FactoryBean 类型。

        profile 字符串可以包含一个简单的 profile 名称(例如,production)或一个 profile 表达式。profile 表达式允许表达更复杂的 profile 逻辑(例如,production & us-east)。profile 表达式中支持以下运算符。

          • !: profile的 NOT 逻辑
          • &: profile的 AND 的逻辑
          • |: profile的 OR 的逻辑

          你不能在不使用括号的情况下混合使用 &| 运算符。例如,production & us-east | eu-central 不是一个有效的表达。它必须表示为 production & (us-east | eu-central)

          你可以使用 @Profile 作为 元注解,以创建一个自定义的组成注解。下面的例子定义了一个自定义的 @Production 注解,你可以把它作为 @Profile("production") 的直接替换:

          @Target(ElementType.TYPE)
          @Retention(RetentionPolicy.RUNTIME)
          @Profile("production")
          public @interface Production {
          }

          image.gif

          如果一个 @Configuration 类被标记为 @Profile,所有与该类相关的 @Bean 方法和 @Import 注解都会被绕过,除非一个或多个指定的 profiles 处于激活状态。如果一个 @Component@Configuration 类被标记为 @Profile({"p1", "p2"}),该类不会被注册或处理,除非 profiles "p1" 或 "p2" 已经被激活。如果一个给定的profiles前缀为NOT操作符(!),那么只有在该profiles没有激活的情况下,才会注册被注解的元素。例如,给定 @Profile({"p1", "!p2"}),如果profile 'p1' 被激活或 profile 'p2' 未被激活,注册将发生。

          @Profile 也可以在方法层面上声明,以便只包括一个配置类的一个特定Bean(例如,对于一个特定Bean的备选变体),正如下面的例子所示:

          @Configuration
          public class AppConfig {
              @Bean("dataSource")
              @Profile("development") (1)
              public DataSource standaloneDataSource() {
                  return new EmbeddedDatabaseBuilder()
                      .setType(EmbeddedDatabaseType.HSQL)
                      .addScript("classpath:com/bank/config/sql/schema.sql")
                      .addScript("classpath:com/bank/config/sql/test-data.sql")
                      .build();
              }
              @Bean("dataSource")
              @Profile("production") (2)
              public DataSource jndiDataSource() throws Exception {
                  Context ctx = new InitialContext();
                  return (DataSource) ctx.lookup("java:comp/env/jdbc/datasource");
              }
          }
          (1)StandaloneDataSource 方法只在 development profile 中可用。
          (2)jndiDataSource 方法只在 production profile 中可用。

          image.gif

          对于 @Bean 方法的 @Profile,一个特殊的情况可能适用。在同一Java方法名的重载 @Bean 方法的情况下(类似于构造函数重载),@Profile 条件需要在所有重载的方法上一致声明。如果条件不一致,那么在重载的方法中,只有第一个声明的条件才是重要的。因此,@Profile 不能被用来选择具有特定参数签名的重载方法而不是另一个。同一个Bean的所有工厂方法之间的解析遵循Spring在创建时的构造函数解析算法。

          如果你想定义具有不同概况条件的备选Bean,请使用不同的Java方法名,通过使用 @Bean name 属性指向同一个Bean名称,如前面的例子所示。如果参数签名都是一样的(例如,所有的变体都有无参数的工厂方法),这是首先在一个有效的Java类中表示这种安排的唯一方法(因为一个特定名称和参数签名的方法只能有一个)。

          1.2 默认 Profile

          默认 profile 代表默认启用的 profile。考虑一下下面的例子:

          @Configuration
          @Profile("default")
          public class DefaultDataConfig {
              @Bean
              public DataSource dataSource() {
                  return new EmbeddedDatabaseBuilder()
                      .setType(EmbeddedDatabaseType.HSQL)
                      .addScript("classpath:com/bank/config/sql/schema.sql")
                      .build();
              }
          }

          image.gif

          如果没有激活profile,就会创建 dataSource。你可以把它看作是为一个或多个Bean提供默认定义的一种方式。如果任何profile被启用,默认的profile就不应用。

          你可以通过在环境中使用 setDefaultProfiles() 来改变默认配置文件的名称,或者通过声明性地使用 spring.profiles.default 属性。

          2. PropertySource 抽象

          Spring的 Environment 抽象提供了对可配置的属性源层次结构的搜索操作。考虑一下下面的列表

          ApplicationContext ctx = new GenericApplicationContext();
          Environment env = ctx.getEnvironment();
          boolean containsMyProperty = env.containsProperty("my-property");
          System.out.println("Does my environment contain the 'my-property' property? " + containsMyProperty);

          image.gif

          在前面的片段中,我们看到了一种询问Spring的高级方式,即询问 my-property 属性是否为当前环境所定义。为了回答这个问题,Environment 对象在一组 PropertySource 对象上执行搜索。PropertySource 是对任何key值对来源的简单抽象,Spring的 StandardEnvironment 配置了两个 PropertySource 对象—一个代表JVM系统属性集合(System.getProperties()),一个代表系统环境变量集合(System.getenv())。

          具体来说,当你使用 StandardEnvironment 时,如果运行时存在 my-property 系统属性或 my-property 环境变量,调用 env.containsProperty("my-property") 会返回 true

          最重要的是,整个机制是可配置的。也许你有一个自定义的属性源,你想集成到这个搜索中。要做到这一点,实现并实例化你自己的 PropertySource,并将其添加到当前环境的 PropertySources 集合中。下面的例子展示了如何做到这一点:

          ConfigurableApplicationContext ctx = new GenericApplicationContext();
          MutablePropertySources sources = ctx.getEnvironment().getPropertySources();
          sources.addFirst(new MyPropertySource());

          image.gif

          在前面的代码中,MyPropertySource 在搜索中被加入了最高优先级。如果它包含 my-property 属性,该属性将被检测并返回,而不是任何其他 PropertySource 中的任何 my-property 属性。 MutablePropertySources API暴露了许多方法,允许精确地操作属性源(property sources)的集合。

          3. 使用 @PropertySource

          @PropertySource注解为向Spring的 Environment 添加 PropertySource 提供了一种方便的声明性机制。

          给定一个包含键值对 testbean.name=myTestBean 的名为 app.properties 的文件,下面的 @Configuration 类以这样一种方式使用 @PropertySource,即调用 testBean.getName() 返回 myTestBean:

          @Configuration
          @PropertySource("classpath:/com/myco/app.properties")
          public class AppConfig {
              @Autowired
              Environment env;
              @Bean
              public TestBean testBean() {
                  TestBean testBean = new TestBean();
                  testBean.setName(env.getProperty("testbean.name"));
                  return testBean;
              }
          }

          image.gif

          任何存在于 @PropertySource 资源位置的 ${…} 占位符都会根据已经针对环境(environment)注册的属性源集合进行解析,如下例所示:

          @Configuration
          @PropertySource("classpath:/com/${my.placeholder:default/path}/app.properties")
          public class AppConfig {
              @Autowired
              Environment env;
              @Bean
              public TestBean testBean() {
                  TestBean testBean = new TestBean();
                  testBean.setName(env.getProperty("testbean.name"));
                  return testBean;
              }
          }

          image.gif

          假设 my.placeholder 存在于一个已经注册的属性源中(例如,系统属性或环境变量),那么该占位符将被解析为相应的值。如果没有,那么就使用 default/path 作为默认值。如果没有指定默认值,并且一个属性不能被解析,就会抛出一个 IllegalArgumentException


          Doker 技术人的数码品牌!!!

          文章下方有交流学习区!一起学习进步!也可以前往官网,加入官方微信交流群!!!你的支持和鼓励是我创作的动力❗❗❗

          官网:Doker 多克; 官方旗舰店首页-Doker 多克创新官方店-淘宝网全品大优惠优惠!!!

          目录
          相关文章
          |
          11天前
          |
          XML Java 数据格式
          SpringBoot入门(8) - 开发中还有哪些常用注解
          SpringBoot入门(8) - 开发中还有哪些常用注解
          29 0
          |
          18天前
          |
          XML JSON Java
          SpringBoot必须掌握的常用注解!
          SpringBoot必须掌握的常用注解!
          41 4
          SpringBoot必须掌握的常用注解!
          |
          19天前
          |
          人工智能 前端开发 Java
          基于开源框架Spring AI Alibaba快速构建Java应用
          本文旨在帮助开发者快速掌握并应用 Spring AI Alibaba,提升基于 Java 的大模型应用开发效率和安全性。
          基于开源框架Spring AI Alibaba快速构建Java应用
          |
          19天前
          |
          存储 缓存 Java
          Spring缓存注解【@Cacheable、@CachePut、@CacheEvict、@Caching、@CacheConfig】使用及注意事项
          Spring缓存注解【@Cacheable、@CachePut、@CacheEvict、@Caching、@CacheConfig】使用及注意事项
          63 2
          |
          19天前
          |
          JSON Java 数据库
          SpringBoot项目使用AOP及自定义注解保存操作日志
          SpringBoot项目使用AOP及自定义注解保存操作日志
          34 1
          |
          14天前
          |
          存储 安全 Java
          springboot当中ConfigurationProperties注解作用跟数据库存入有啥区别
          `@ConfigurationProperties`注解和数据库存储配置信息各有优劣,适用于不同的应用场景。`@ConfigurationProperties`提供了类型安全和模块化的配置管理方式,适合静态和简单配置。而数据库存储配置信息提供了动态更新和集中管理的能力,适合需要频繁变化和集中管理的配置需求。在实际项目中,可以根据具体需求选择合适的配置管理方式,或者结合使用这两种方式,实现灵活高效的配置管理。
          11 0
          |
          前端开发 Java 数据库
          Java面试题 - Spring
          Java面试题 - Spring
          146 0
          |
          4月前
          |
          Java 应用服务中间件 开发者
          Java面试题:解释Spring Boot的优势及其自动配置原理
          Java面试题:解释Spring Boot的优势及其自动配置原理
          119 0
          |
          Java 容器 Spring
          【java常见的面试题】什么是Spring IOC 和DI ?
          Java基础的面试题【SSM框架篇】
          187 0
          |
          设计模式 前端开发 Java
          Java经典面试题:Spring中用到了哪些设计模式?
          一位应届毕业生被问到这样一道面试题,说Spring用到了哪些设计模式?其实只要Spring使用得够熟练,回答这道题还是非常轻松的。因为Spring的命名非常规范,基本上从类名就可以看得出来用到了哪些设计模式。 今天,我给大家分享一下我的理解。
          103 1