Spring5中文文档【9】IOC容器之基于 Java 的容器配置

前言

本系列基于最新5.3.10版本,大部分内容copy于官方文档…
官方文档地址

之前介绍了如何使用XML及注解的方式配置容器,接下来介绍使用Java 代码中来配置Spring 容器。

1. @Bean和@Configuration注解

Spring 基于Java 配置支持的核心注解是 @Configuration(类级别)和@Bean(方法级别)。

@Bean注解标识于方法返回一个实例,并初始化到Spring IoC容器进行管理。@Bean注解扮演着与<bean/>标签相同的角色。可以将@Bean与 Spring 中@Component注解一起使用 。但是一般与@Configuration一起使用。

被@Configuration标识的类表明它的主要目的是作为 bean 定义的来源。最简单的@Configuration类如下:

@Configuration
public class AppConfig {
    
    

    @Bean
    public MyService myService() {
    
    
        return new MyServiceImpl();
    }
}

前面的AppConfig类等效于以下 Spring XML <beans/>

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

2. 使用AnnotationConfigApplicationContext实例化 Spring 容器

Spring 3.0 中引入AnnotationConfigApplicationContext容器,这种通用的ApplicationContext实现不仅能够接受@Configuration类作为输入,还能够接受@Component类和用 JSR-330 元数据注解的类。

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

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

2.1 使用简单的构造函数

与ClassPathXmlApplicationContext在实例化时使用 Spring XML 文件作为输入的方式大致相同 ,可以在实例化AnnotationConfigApplicationContext时输入被@Configuration注解标识的类,这种方式完全无 XML 即可使用 Spring 容器,如以下示例所示:

首先添加Java配置类:

@Configuration
public class AnimalConfig {
    
    

    @Bean()
    public Animal animal() {
    
    
        return new Animal("AnimalConfig",18);
    }
}

然后在AnnotationConfigApplicationContext 的构造函数中传入当前@Configuration类,即可将@Bean标记方法返回的对象注入到IOC中。

AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(AnimalConfig.class);
        Animal animal = applicationContext.getBean("animal", Animal.class);
        animal.eat("mike");

AnnotationConfigApplicationContext不仅限于使用@Configuration类。任何@Component或 JSR-330 注解类都可以作为构造函数的参数输入。

2.2 使用编程方式构建容器

可以使用AnnotationConfigApplicationContext的无参数构造函数实例化容器 ,然后使用register()方法配置。这种方法在以编程方式构建AnnotationConfigApplicationContext。

以下示例显示了如何执行此操作:

  AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext();
        applicationContext.register(AnimalConfig.class);
        applicationContext.refresh();
        Animal animal = applicationContext.getBean("animal", Animal.class);
        animal.eat("mike");

2.3 启用组件扫描 scan(String…​)

要启用组件扫描,可以@Configuration类中添加@ComponentScan注解:

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

在前面的示例中,会扫描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);
}

2.4 支持 Web 应用程序 的AnnotationConfigWebApplicationContext

AnnotationConfigWebApplicationContext是AnnotationConfigApplicationContext的变体,在使用Spring MVC时,配置 ContextLoaderListener(servlet 侦听器)、Spring MVC DispatcherServlet等就是使用此实现类。

以下web.xml代码段配置了一个典型的 Spring MVC Web 应用程序(注意contextClasscontext-param 和 init-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>

3. 使用@Bean注解

@Bean是方法级别的注解,和 XML<bean/>元素起相同的作用。该注解支持<bean/>提供的一些属性,例如:

  • init-method

  • destroy-method

  • autowiring

  • name

可以在@Configuration或 @Component标识的类中使用@Bean注解。

3.1 声明一个 Bean

要声明 bean,可以对方法使用@Bean注解。默认情况下,bean 名称与方法名称相同。以下示例显示了@Bean方法声明:

@Configuration
public class AppConfig {
    
    

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

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

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

这两个声明的bean名称都为 的 transferService, ApplicationContext绑定到对象实例为TransferServiceImpl,如下面的文本所示:

transferService -> com.acme.TransferServiceImpl

还可以在使用@Bean的方法返回值使用接口(或基类)返回类型,如以下示例所示:

@Configuration
public class AppConfig {
    
    

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

3.2 Bean 依赖

如果@Bean注册的对象,需要依赖其他Bean时,可以通过该方法的参数注入,例如,TransferService 需要一个AccountRepository,可以使用方法参数传入该依赖项,如以下示例所示:

@Configuration
public class AppConfig {
    
    

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

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

3.3 接收生命周期回调

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

也完全支持常规的 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();
    }
}

3.4 指定 Bean 作用域范围

@Scope注解可以指定 bean 的范围,以前已经介绍过@Scope的用法。

    @Bean
    @Scope(value="session")
    public Animal animal() {
    
    
        return new Animal("AnimalConfig",18);
    }

3.5 自定义 Bean 命名

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

@Configuration
public class AppConfig {
    
    

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

3.6 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...
    }
}

3.7 Bean描述

有时,提供 bean 的更详细的文本描述会很有帮助。当 bean 被公开(可能通过 JMX)用于监视目的时,这可能特别有用。

要将描述添加到@Bean,您可以使用 @Description注解,如以下示例所示:

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

4. 使用@Configuration注解

@Configuration是一个类级别的注解,表明当前对象是 bean 定义的来源。

@Configuration类通过@Bean方法声明 bean 。@Configuration类方法的调用也可用于定义 bean 间的依赖关系。

4.1 注入bean 之间的依赖

当 bean 相互依赖时,可以直接调用方法即可,如下例所示:

@Configuration
public class AppConfig {
    
    

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

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

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

4.2 查找方法注入

查找方法注入是一项您应该很少使用的高级功能。在单例范围的 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 配置,您可以创建一个子类,其中CommandManager抽象createCommand()方法以查找新(原型)命令对象的方式被覆盖。以下示例显示了如何执行此操作:

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

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

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

5. 编写基于 Java 的配置

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

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

现在,不需要在实例化上下文时同时指定ConfigA.class和ConfigB.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);
}

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

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

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

有条件的启用或禁用一个完整的@Configuration类甚至单个@Bean方法通常很有用。一个常见的例子是@Profile只有在 Spring 中启用了特定配置文件时才使用激活 bean。

还有更灵活的注解@Conditional来完成条件匹配。

Condition接口的实现提供了一个matches(…​) 返回true或者alse的方法。例如,以下清单显示了Condition用于的实际 实现@Profile:

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

5.3 结合 Java 和 XML 配置

Spring 的@Configuration类支持并不旨在 100% 完全替代 Spring XML。例如 Spring XML 命名空间,仍然是配置容器的理想方式。

在XML是方便或必要的情况下,你有一个选择:要么通过实例化容器中的“XML为中心”的方式,例如, ClassPathXmlApplicationContext或通过使用它实例化一个“Java为中心”的方式 AnnotationConfigApplicationContext和@ImportResource注释根据需要导入 XML。

猜你喜欢

转载自blog.csdn.net/qq_43437874/article/details/120706543
今日推荐