zoukankan      html  css  js  c++  java
  • 20191230 Spring官方文档(Core 1.12)

    1.12。基于Java的容器配置

    1.12.1。基本概念:@Bean和@Configuration

    @Bean批注用于指示方法实例化,配置和初始化要由Spring IoC容器管理的新对象。对于那些熟悉Spring的<beans/>的XML配置的人来说,@Bean注释的作用与元素相同。您可以对任何Spring @Component使用@Bean带注释的方法 。但是,它们最常与@Configuration bean一起使用。

    用@Configuration注释类表示其主要目的是作为Bean定义的来源。此外,@Configuration类允许通过调用同一类中的其他@Bean方法来定义Bean之间的依赖关系。

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

    如果在未使用@Configuration注释的类中声明@Bean方法 ,则将它们称为以“精简”模式进行处理。在一个@Component或甚至一个普通类中声明的Bean方法被认为是“精简”,其包含类的主要目的不同,而@Bean方法在那儿是一种额外的奖励。例如,服务组件可以通过每个适用组件类上的额外的@Bean方法向容器公开管理视图。在这种情况下,@Bean方法是通用的工厂方法机制。

    与完整的@Configuration不同,精简@Bean方法无法声明Bean间的依赖关系。取而代之的是,它们在其包含组件的内部状态上进行操作,并且还可以根据其可能声明的参数进行操作。因此,此类@Bean方法不应调用其他 @Bean方法。实际上,每个此类方法仅是用于特定bean引用的工厂方法,而没有任何特殊的运行时语义。这里的积极影响是,不必在运行时应用CGLIB子类,因此在类设计方面没有任何限制(也就是说,包含类可以是final,依此类推)。

    在常见情况下,@Bean方法将在@Configuration类中声明,以确保始终使用“完全”模式,因此跨方法引用将重定向到容器的生命周期管理。这样可以防止通过常规Java调用意外地调用相同的 @Bean方法,从而有助于减少在“精简”模式下运行时难以追查的细微错误。

    1.12.2。使用AnnotationConfigApplicationContext实例化Spring容器

    AnnotationConfigApplicationContext实现不仅可以接受@Configuration类作为输入,还可以接受普通@Component类和使用JSR-330元数据注释的类。

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

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

    // 基本用法
    ApplicationContext ctx = new AnnotationConfigApplicationContext(AppConfig.class);
    
    // 手动注册配置类
    AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext();
    ctx.register(AppConfig.class, OtherConfig.class);
    ctx.register(AdditionalConfig.class);
    ctx.refresh();
    
    // 以编程方式启用扫描功能
    AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext();
    ctx.scan("com.acme");
    ctx.refresh();
    

    通过AnnotationConfigWebApplicationContext支持Web应用程序

    <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>
    

    1.12.3。使用@Bean注释

    可以在@Configuration注解类或@Component注解类中使用@Bean注释。

    声明一个Bean

    默认情况下,bean名称与方法名称相同。

    可以@Bean使用接口(或基类)返回类型声明您的方法。

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

    非惰性单例bean根据其声明顺序进行实例化,因此您可能会看到不同的类型匹配结果,具体取决于另一个组件何时尝试按未声明的类型进行匹配(例如@Autowired TransferServiceImpl,仅在实例化transferService bean之后才解析)。

    如果您通过声明的服务接口一致地引用类型,则 @Bean返回类型可以安全地加入该设计决策。但是,对于实现多个接口的组件或由其实现类型潜在引用的组件,声明可能的最具体的返回类型(至少与引用您的bean的注入点所要求的具体类型一样)更为安全。

    Bean依赖

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

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

    接收生命周期回调

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

    还完全支持常规的Spring 生命周期回调。如果bean实现InitializingBeanDisposableBeanLifecycle,则容器将调用它们各自的方法。

    也完全支持标准*Aware接口集(例如BeanFactoryAwareBeanNameAwareMessageSourceAwareApplicationContextAware等)。

    @Bean注释支持指定任意初始化和销毁回调方法,就像Spring XML中的init-methoddestroy-method属性的bean元素,如下面的示例所示:

    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配置定义的具有公共closeshutdown方法的bean会自动通过销毁回调进行登记。如果您有公共 close或shutdown方法,并且不希望在容器关闭时调用它,则可以添加@Bean(destroyMethod="")到bean定义中以禁用默认(inferred)模式。

    默认情况下,您可能要对通过JNDI获取的资源执行此操作,因为其生命周期是在应用程序外部进行管理的。特别是,请确保始终对DataSource进行操作,因为这在Java EE应用程序服务器上是有问题的。

    以下示例显示了如何防止对DataSource的自动销毁回调 :

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

    此外,对于@Bean方法,通常使用程序化JNDI查找,方法是使用Spring JndiTemplateJndiLocatorDelegate辅助方法,或者直接使用JNDI InitialContext, 但不使用JndiObjectFactoryBean变体(这将迫使您将返回类型声明为FactoryBean类型,而不是实际的目标类型,这使得它很难在打算引用此处提供的资源的其他@Bean方法中用于交叉引用调用)。

    对于BeanOne前面注释中的示例,在构造期间直接调用init()方法同样有效,如以下示例所示:

    @Bean
    public BeanOne beanOne() {
        BeanOne beanOne = new BeanOne();
        beanOne.init();
        return beanOne;
    }
    

    当您直接使用Java工作时,您可以对对象执行任何操作,而不必总是依赖于容器生命周期。

    指定Bean作用域

    默认范围是singleton,但是您可以使用@Scope注释覆盖它,如以下示例所示:

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

    Spring提供了一种通过作用域代理处理作用域依赖性的便捷方法 。使用XML配置时创建此类代理的最简单方法是<aop:scoped-proxy/>元素。使用@Scope注释在Java中配置bean 可以为proxyMode属性提供同等的支持。默认值为无代理(ScopedProxyMode.NO),但您可以指定ScopedProxyMode.TARGET_CLASSScopedProxyMode.INTERFACES

    如果从XML参考文档将作用域代理示例移植 到我们的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;
    }
    

    自定义Bean命名

    默认情况下,配置类使用@Bean方法的名称作为结果bean的名称。但是,可以使用name属性覆盖此功能,如以下示例所示:

    @Bean(name = "myThing")
    public Thing thing() {
        return new Thing();
    }
    

    Bean别名

    有时希望为单个Bean提供多个名称,称为Bean别名。 为此@Bean注释的name属性接受String数组。以下示例显示了如何为bean设置多个别名:

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

    Bean描述

    有时,提供有关bean的更详细的文本描述会很有帮助。当暴露出bean(可能通过JMX)以进行监视时,这特别有用。

    要将说明添加到@Bean,可以使用 @Description 批注,如以下示例所示:

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

    1.12.4。使用@Configuration注释

    @Configuration是类级别的注释,指示对象是Bean定义的源。@Configuration类通过公共@Bean注释方法声明bean 。对@Configuration类的@Bean方法的调用也可以用于定义Bean之间的依赖关系。

    注入bean间的依赖关系

    当bean相互依赖时,表示依赖关系就像让一个bean方法调用另一个依赖一样简单,如以下示例所示:

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

    仅当在@Configuration类中声明@Bean方法时,此声明bean间依赖关系的方法才有效。您不能使用普通@Component类声明bean间的依赖关系。

    查找方法注入

    如前所述,查找方法注入是一项高级功能,您应该很少使用。在单例作用域的bean对原型作用域的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();
    }
    

    通过使用Java配置,可以创建一个覆盖抽象createCommand()方法的CommandManager的子类,该方法将以某种方式查找新的(原型)命令对象。以下示例显示了如何执行此操作:

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

    有关基于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();
        }
    }
    

    由于clientDao()方法创建的新实例ClientDaoImpl并返回它,因此通常希望有两个实例(每个服务一个)。那肯定是有问题的:在Spring中,实例化的bean在默认情况下具有singleton作用域。这就是神奇的地方:所有@Configuration类在启动时都使用CGLIB子类化。在子类中,子方法在调用父方法并创建新实例之前,首先检查容器中是否有任何缓存(作用域)的bean。

    根据bean的范围,行为可能有所不同。我们在这里谈论单例。

    从Spring 3.2开始,不再需要将CGLIB添加到您的类路径中,因为CGLIB类已经被重新打包在org.springframework.cglib下并直接包含在spring-core JAR中。

    由于CGLIB在启动时会动态添加功能,因此存在一些限制。特别是,配置类不能是 final 的。但是,从4.3版本开始,配置类上允许使用任何构造函数,包括使用@Autowired或单个非默认构造函数声明进行默认注入。

    如果您希望避免任何CGLIB施加的限制,请考虑@Bean 在非@Configuration类上声明您的方法(例如,在普通@Component类上声明)。@Bean然后不会截获方法之间的跨方法调用,因此您必须专门依赖那里的构造函数或方法级别的依赖项注入。

    1.12.5。组成基于Java的配置

    Spring的基于Java的配置功能使您可以编写批注,这可以降低配置的复杂性。

    使用@Import注释

    就像在Spring XML文件中使用<import/>元素来帮助模块化配置一样,@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();
        }
    }
    

    从Spring Framework 4.2开始,@Import还支持对常规组件类的引用,类似于AnnotationConfigApplicationContext.register方法。如果要通过使用一些配置类作为入口点来显式定义所有组件,从而避免组件扫描,则此功能特别有用。

    注入对导入@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");
    }
    

    还有另一种方法可以达到相同的结果。请记住,请记住,@Configuration类最终仅是容器中的另一个bean:这意味着他们可以利用 @Autowired@Value注入等功能相同的其它bean。

    以这种方式注入的依赖项只是最简单的一种。在上下文初始化期间很早就处理了@Configuration 类,并且强制以这种方式注入依赖项可能会导致意外的早期初始化。如上例所示,尽可能使用基于参数的注入。

    另外,请特别注意BeanPostProcessorBeanFactoryPostProcessor的@Bean定义。通常应将那些声明为static @Bean方法,而不触发其包含的配置类的实例化。否则,@Autowired和@Value不在配置类上起作用,因为它是被作为一个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");
    }
    

    从Spring Framework 4.3开始仅支持@Configuration类中的构造方法注入。还要注意,如果目标bean仅定义一个构造函数无需指定@Autowired。

    有条件地包含@Configuration类或@Bean方法

    基于某些任意系统状态,有条件地启用或禁用完整的@Configuration类甚至单个@Bean方法通常很有用。一个常见的示例是仅在Spring Environment中启用了特定 profile 时才使用@Profile注释激活Bean。

    @Profile注释是通过使用一种称为更灵活的 @Conditional 注释实际执行。

    Condition接口的实现提供了matches(..) 返回true或false的方法。

    结合Java和XML配置

    Spring的@Configuration类支持并非旨在100%完全替代Spring XML。 某些工具(例如Spring XML名称空间)仍然是配置容器的理想方法。 在使用XML方便或有必要的情况下,您可以选择:使用ClassPathXmlApplicationContext以“以XML为中心”的方式实例化容器,或使用AnnotationConfigApplicationContext以“Java中心”的方式实例化容器。 @ImportResource批注可根据需要导入XML。

    以XML为中心的@Configuration类使用

    @Configuration类声明为纯Spring <bean/>元素

    <beans>
        <context:annotation-config/>
        <bean class="com.acme.AppConfig"/>
    </beans>
    

    使用<context:component-scan />拾取@Configuration

    在这种情况下,我们无需显式声明 <context:annotation-config/>,因为<context:component-scan/>启用了相同的功能。

    <context:component-scan base-package="com.acme"/>
    

    以@Configuration 类为中心的XML与 @ImportResource

    @ImportResource("classpath:/com/acme/properties-config.xml")
    
    properties-config.xml
    <beans>
        <context:property-placeholder location="classpath:/com/acme/jdbc.properties"/>
    </beans>
    

    可以通过@PropertySource引入属性文件

  • 相关阅读:
    Linux 简介
    5设计模式之桥接模式(结构模式)
    2设计模式之简单工厂模式(构造模式)
    3异步和多线程
    1设计模式之单例模式
    性能测试知多少---吞吐量
    NumberFormat DecimalFormat
    Java 005 枚举
    log4j
    Java Basic
  • 原文地址:https://www.cnblogs.com/huangwenjie/p/12121368.html
Copyright © 2011-2022 走看看