写点什么

Spring 5 中文解析核心篇 -IoC 容器之基于 Java 容器配置

用户头像
青年IT男
关注
发布于: 2020 年 09 月 04 日
Spring 5 中文解析核心篇-IoC容器之基于Java容器配置
1.12 基于 Java 容器配置


这个部分涵盖了在你的 Java 代码中怎样去使用注解配置 Spring 容器。它包含下面的主题:



1.12.1 基本概念:@Bean 和@Configuration


Spring 的新 Java 配置支持中的主要构件是@Configuration注解的类和@Bean注解的方法。


@Bean注解使用表示一个方法实例、配置和实例化 Spring IoC 容器管理的新对象。这些类似 Spring 的<beans/> XML 配置,@Bean注解扮演了<bean/>元素相同的角色。你可以使用@Bean注解方法替换任何 Spring 中@Component组件。然而,它们最常与 @Configuration 一起使用。注解类@Configuration注解表示它的主要目的是 bean 定义的源。此外,@Configuration类允许通过调用同一类中的其他@Bean方法来定义 Bean 间的依赖关系。下面最简单的@Configuration例子:


@Configurationpublic class AppConfig {
@Bean public MyService myService() { return new MyServiceImpl(); }}
复制代码


前面的 AppConfig 类是与下面的 Spring <beans/> XML 定义相等:


<beans>    <bean id="myService" class="com.acme.services.MyServiceImpl"/></beans>
复制代码


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

@Bean方法在类中被声明时,这些类没有被注解@Configuration,它们被称为以“精简”模式进行处理。在@Component或在简单的旧类中声明的 Bean 方法被认为是“精简版”,其中包含类的主要目的不同,而@Bean方法在那里具有某种优势。例如,服务组件在每个可应用的组件类上通过增加一个附加的 @Bean 方法暴露管理视图到容器。在这种场景下,@Bean方法是一种通用的工厂方法机制。

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

在常见的场景中,@Bean方法是在 @Configuration 类中声明的,确保总是使用完整模式,<u>因此交叉方法引用被重定向到容器的生命周期管理</u>。这可以防止通过常规 Java 调用意外调用相同的@Bean方法,这有助于减少在 lite 模式下操作时难以跟踪的细微错误。备注:在@Configuration类中调用其他@Bean方法会定向到容器的生命周期管理。


@Bean@Configuration在下面的部分深入讨论。首先,我们会覆盖基于 Java 注解创建 Spring 容器的各种方式。


1.12.2 通过使用AnnotationConfigApplicationContext初始化 Spring 容器


以下各节介绍了 Spring 3.0 中引入的 Spring 的AnnotationConfigApplicationContext。这个通用的 ApplicationContext 实现不仅能够接收@Configuration类作为输入,还能够接收普通的@Component类和使用 JSR-330 元数据注解的类


@Configuration类作为输入被提供,@Configuration类自身作为一个 bean 定义被注册并且所有在类中被@Bean声明的方法也作为 bean 的定义被注册到容器。


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


简单构造


与实例化ClassPathXmlApplicationContext时将 Spring XML 文件用作输入的方式几乎相同,实例化AnnotationConfigApplicationContext时可以将@Configuration类用作输入。如下面的示例所示,这允许完全不使用 XML 来使用 Spring 容器:


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


像前面提到的,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();}
复制代码


前面的例子假设MyServiceImplDependency1、和Dependency2使用 Spring 依赖注入注解例如:@Autowired


通过使用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();}
复制代码


通过scan(String…)扫描激活组件


扫描激活组件,你可以注解你的@Configuration类:


@Configuration@ComponentScan(basePackages = "com.acme") //1public class AppConfig  {    ...}
复制代码


  1. 这个注解激活组件扫描


经验丰富的 Spring 用户可能熟悉 Spring context 中等效的 XML 声明:命名空间,类似下面例子:

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


在前面的例子中,com.acm包被扫描去查找被注解@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);}
复制代码


记住@Configuration类是使用@Component元注解的,因此它们是组件扫描的候选者。在前面的例子中,假设 AppConfig 被声明在com.acme包中(或任何com.acme子包),它会在调用scan()期间被选出来。在refresh()后,其所有@Bean方法都将被处理并注册为容器内的 Bean 定义。


通过AnnotationConfigWebApplicationContext支持 Web 应用程序


AnnotationConfigWebApplicationContext提供了AnnotationConfigApplicationContextWebApplicationContext变体。当配置 Spring 的ContextLoaderListener servlet 监听器、Spring MVC DispatcherServlet 等等的时候,你可以使用这个实现,下面的web.xml片段配置一个典型的 Spring MVC web 应用程序(注意:contextClass使用context-paraminit-param):


<web-app>    <!--配置ContextLoaderListener使用AnnotationConfigWebApplicationContext替换默认的XmlWebApplicationContext-->    <context-param>        <param-name>contextClass</param-name>        <param-value>            org.springframework.web.context.support.AnnotationConfigWebApplicationContext        </param-value>    </context-param>
<!-- 配置路径必须包含一个或多个以逗号或空格分隔的完全限定的@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> <!--同样,配置路径必须包含一个或多个逗号或空格分隔且完全限定符的@Configuration类 --> <init-param> <param-name>contextConfigLocation</param-name> <param-value>com.acme.web.MvcConfig</param-value> </init-param> </servlet>
<!-- 为所有/app/*请求映射到dispatcher servlet --> <servlet-mapping> <servlet-name>dispatcher</servlet-name> <url-pattern>/app/*</url-pattern> </servlet-mapping></web-app>
复制代码


1.12.3 使用 @Bean 注解


@Bean是一个方法级别注解并且是 XML <bean/>元素的直接类比物。这个注解支持一些通过<bean/>提供的属性,例如: init-method 、destroy-method 、 autowiring


你可以在@Configuration@Component注解的类上使用@Bean注解。


声明 Bean


去声明一个 bean,你可以为一个方法注解@Bean。你使用这个方法在ApplicationContext中去注册一个方法返回值指定类型的 bean 定义。<u>默认情况,bean 名称是相同方法名称</u>。下面的例子显示一个@Bean方法声明:


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

复制代码


前面的配置与下面的 Spring XML 完全等效:


<beans>    <bean id="transferService" class="com.acme.TransferServiceImpl"/></beans>
复制代码


这两者声明都使一个名为transferService的 bean 在ApplicationContext中可用,并绑定一个TransferServiceImpl类型的对象实例,类似下面文本图片显示:


transferService -> com.acme.TransferServiceImpl
复制代码


你也可以声明你的@Bean方法为一个接口(或者基类)返回类型,类似下面例子展示:


@Configurationpublic class AppConfig {
@Bean public TransferService transferService() { return new TransferServiceImpl(); }}
复制代码


但是,这将高级类型预测的可见性限制为指定的接口类型(TransferService)。然而,全类型(TransferServiceImpl)在容器只有一个,受影响的单例 bean 被初始化。非懒加载单例 bean 根据它们的声明顺序获取已经初始化实例,当其他组件尝试通过非声明类型去匹配时,你可能看到不同类型匹配结果依赖(例如,@Autowired  TransferServiceImpl,仅在实例化transferService bean 之后才解析)。


如果你始终通过声明的服务接口引用你的类型,那么你的@Bean返回类型可以安全地加入该设计决策。但是,对于实现多个接口的组件或由其实现类型潜在引用的组件,声明最具体的返回类型(至少与引用你的 bean 的注入点所要求的具体类型一样)更为安全。 备注:意思是如果通过实现类型引用组件时,在定义@Bean方法时返回类型要是具体的实现类型。


Bean 依赖


一个被@Bean注解的方法可以有任意个参数,这些参数描述了需要的依赖去构建 bean。例如,如果我们的TransferService需要一个AccountRepository,我们可以通过方法参数来实现这种依赖关系,类似下面例子:


@Configurationpublic class AppConfig {
@Bean public TransferService transferService(AccountRepository accountRepository) { return new TransferServiceImpl(accountRepository); }}
复制代码


解析机制与基于构造函数的依赖注入几乎相同。查看更详细相关部分


接受生命周期回调


任何被声明@Bean注解的类支持普通的生命周期回调并且可以使用 JSR-250 的@PostConstruct和 @PreDestroy 注解。查看JSR-250注解更多的详情。


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


标准的 set*Aware 接口(例如,BeanFactoryAware,、BeanNameAwareMessageSourceAware,、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 }}
@Configurationpublic class AppConfig {
@Bean(initMethod = "init") public BeanOne beanOne() { return new BeanOne(); }
@Bean(destroyMethod = "cleanup") public BeanTwo beanTwo() { return new BeanTwo(); }}
复制代码


默认情况下,这些 bean 通过 Java 配置定义它们有公共的closeshutdown方法自定地加入到销毁回调中。如果你有一个公共的closeshutdown方法并且当容器被关闭时不想被回调,你应该增加@Bean(destroyMethod="")到你的 bean 定义中去禁止默认的(inferred)模式。

默认情况下,你可能要对通过 JNDI 获取的资源执行此操作,因为其生命周期在应用程序外部进行管理的。特别是,要确保始终为数据源执行此操作,因为在 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()方法,类似下面例子:


@Configurationpublic class AppConfig {
@Bean public BeanOne beanOne() { BeanOne beanOne = new BeanOne(); beanOne.init(); return beanOne; }
// ...}
复制代码


当你直接地工作在 Java 中,你可以对你的对象做任何事而不需要依赖容器生命周期。


指定 Bean 作用域


Spring 包括@Scope注解,因此你可以使用 bean 的作用域。


使用@Scope注解


你可以指定你的 bean 定义通过@Bean注解同时也可以指定一个作用域。你可以使用在Bean作用域部分中任何标准的作用域指定。


默认作用域是singleton,但是你可以覆盖这个通过@Scope注解,类似下面例子显示:


@Configurationpublic class MyConfiguration {
@Bean @Scope("prototype") public Encryptor encryptor() { // ... }}
复制代码


@Scopescoped-proxy


Spring 提供通过作用域代理处理作用域依赖的便捷方式。当使用 XML 配置< aop:scoped-proxy/>是最简单创建一个代理方式。使用@Scope注解在 Java 中配置 bean,可以通过proxyMode属性提供同等的支持。默认是没有代理(ScopedProxyMode.NO),但是你可以指定ScopedProxyMode.TARGET_CLASSScopedProxyMode.INTERFACES


如果你使用 Java,将 XML 参考文档中的作用域代理示例(请参阅作用域代理)移植到我们的@Bean,它类似于以下内容:


// Http session作用域bean 暴露为代理@Bean@SessionScopepublic UserPreferences userPreferences() {    return new UserPreferences();}
@Beanpublic Service userService() { UserService service = new SimpleUserService(); // 引用UserPreferences service.setUserPreferences(userPreferences()); return service;}
复制代码


自定义 Bean 名称


默认情况下,配置类使用@Bean方法的名称作为 bean 名称。这个功能可以被覆盖,通过@Bean的 name 属性,类似下面例子:


@Configurationpublic class AppConfig {
@Bean(name = "myThing") public Thing thing() { return new Thing(); }}
复制代码


Bean 别名


类似在bean的命名中讨论,有时候给一个简单 bean 多个名称,也称为 Bean 别名。@Bean注解的 name 属性接受一个字符串数组为这个别名。下面例子展示怎样去设置 bean 的别名:


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

复制代码


@Bean 描述


有时候,有助于提供有关 bean 的更详细的文本描述。当这些 bean 被暴露为监控目的时,是非常有用的。


去增加一个描述到@Bean,你可以使用 @Description 注解,类似下面例子展示:


@Configurationpublic class AppConfig {
@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和@Configuration


bean 间注入的依赖关系


当 Bean 彼此依赖时,表达这种依赖就像让一个 bean 方法调用另一个一样简单,类似下面例子展示:


@Configurationpublic class AppConfig {
@Bean public BeanOne beanOne() { return new BeanOne(beanTwo()); }
@Bean public BeanTwo beanTwo() { return new BeanTwo(); }}
复制代码


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


仅仅当@Bean方法被声明在@Configuration类中时,这方法声明 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 配置,在该子类中,你可以创建一个CommandManager子类,抽象的createCommand()方法将被覆盖,以使其查找新的(原型)command 对象。通过使用 Java 配置。下面例子展示怎样去做:


@Bean@Scope("prototype")public AsyncCommand asyncCommand() {    AsyncCommand command = new AsyncCommand();    // inject dependencies here as required    return command;}
@Beanpublic 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注解的方法被调用两次。


@Configurationpublic 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()在clientService1()被调用一次并且在clientService2()也调用一次。因为这个方法创建一个新的ClientDaoImpl实例并且返回它,你通常希望有两个实例(每个服务一个)。那肯定是有问题的:在 Spring 中,默认情况实例化 bean 作用域是singleton。这就是神奇之处:所有@Configuration类在运行时是 CGLIB 的子类。在子类中,在调用父方法创建实例之前,子类方法首先检查容器缓存 bean。


这种行为根据你的 bean 作用域可能不同。我们在这里讨论的单例 bean。

在 Spring3.2 以后,不在需要添加 CGLIB 到你的类路径下,因为 CGLIB 类已经被重新打包在org.springframework.cglib下并且直接地包含在spring-core jar 中。

由于 CGLIB 在启动时会动态添加功能,因此存在一些限制。特别地,配置类不能是final。然而,在 Spring4.3 以后,任何构造函数在配置类上是被允许的,包括@Autowired或没有默认参数的构造函数申明默认注入的使用。

如果你想避免 CGLIB 的限制,考虑在你非@Configuration类(例如,一个普通的@Component替换)上声明你的@Bean方法。在@Bean方法之间跨方法调用不会被拦截,因此你必在须构造函数或方法级别只依赖需要注入的。


代码示例:com.liyong.ioccontainer.starter.BaseJavaConfigIocContainer


1.12.5 编写基于 Java 的配置


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


使用 @Import 注解


类似<import/>元素被使用在 Spring XML 文件中去帮助模块化配置,@Import注解允许去其他配置类中加载@Bean定义,类似下面的例子展示:


@Configurationpublic class ConfigA {
@Bean public A a() { return new A(); }}
@Configuration@Import(ConfigA.class)public class ConfigB {
@Bean public B b() { return new B(); }}
复制代码


当初始化上下文时,不需要去指定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);}
复制代码


这个方式简化容器的实例化,仅仅一个类需要去处理,而不是在构造期间潜在地记住大量的@Configuration类。


在 Spring4.2 以后,@Import也支持引用常规的组件类,类似AnnotationConfigApplicationContext.register方法。如果你想避免组件扫描,这是非常有用地,通过使用一些配置类作为入口点显示定义所有组件。


在被导入的@Bean定义中注入依赖


前面的示例有效,但过于简单。在大多数时间场景中,Bean 在配置类之间相互依赖。当使用 XML 时,这不是问题,因为不涉及编译器并且你可以声明 ref="someBean" 并信任 Spring 在容器初始化期间进行处理。当使用@Configuration类时,Java 编译器在配置模型上约束,因为对其他 bean 引用必须是有效的 Java 语法。


幸好地,解决这个问题是非常简单的。像我们以前讨论过的@Bean方法可以有任意数量的参数,这些参数描述了 bean 的依赖。考虑下面更真实的场景对于这些@Configuration类,每一个 bean 在其他类中被定义:


@Configurationpublic class ServiceConfig {
@Bean public TransferService transferService(AccountRepository accountRepository) { return new TransferServiceImpl(accountRepository); }}
@Configurationpublic 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类是在上下文初始化期间非常早地处理的,并且强制以这种方式注入依赖项可能导致意外的早期初始化。如上例所示,尽可能使用基于参数的注入。

另外,通过@Bean定义BeanPostProcessor和 BeanFactoryPostProcessor 时要特别小心。这些应该通常地被声明为static @Bean方法,不要触发它们包含的配置类初始化。除此之外,@Autowired@Value可能在配置类上不能工作,因为它作为 bean 实例被创建早于 AutowiredAnnotationBeanPostProcessor


下面例子展示一个 bean 怎样被装配到其他 bean:


@Configurationpublic class ServiceConfig {
@Autowired private AccountRepository accountRepository;
@Bean public TransferService transferService() { return new TransferServiceImpl(accountRepository); }}
@Configurationpublic 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类中构造方法注入仅支持在 Spring4.3 以后。注意:如果目标 bean 定义仅有一个构造函数,那么不需要去指定@Autowired


代码示例:com.liyong.ioccontainer.starter.BeanAndConfigurationImportContainer


全限定导入 bean 以更容易导航


在前面的场景中,使用@Autowired工作很好并且提供期望的模块化,但是确定自动装配 bean 的定义是在哪里声明的仍然有些模棱两可。例如,作为一个开发者看到ServiceConfig,怎样确切的知道@Autowired AccountRepository bean 在哪里定义的?它在代码中不是明确的,这可能很好。记住, Spring Tools 为 Eclipse 提供可以渲染图形的工具,这些图形显示了所有的对象是怎样连接的,这可能是你需要的。你的 Java IDE 能更容易地找到所有声明和使用AccountRepository类型并且快速地显示@Bean方法的路径以及返回类型。


如果这种歧义是不可接受的,并且你希望从 IDE 内部直接从一个@Configuration类导航到另一个@Configuration类,请考虑自动装配配置类本身。下面例子显示怎样去做:


@Configurationpublic 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现在紧紧的耦合到RepositoryConfig。这就是需要权衡的。通过使用基于接口或基于抽象类的@Configuration类,可以在某种程度上缓解这种紧密耦合。考虑下面例子:


@Configurationpublic class ServiceConfig {
@Autowired private RepositoryConfig repositoryConfig;
@Bean public TransferService transferService() { return new TransferServiceImpl(repositoryConfig.accountRepository()); }}
@Configurationpublic interface RepositoryConfig {
@Bean AccountRepository accountRepository();}
@Configurationpublic 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");}

复制代码


现在,ServiceConfig与具体的DefaultRepositoryConfig松散耦合,并且内建 IDE 工具仍然非常有用:你可以容易地获取RepositoryConfig实现的层级。通过这种方式,导航@Configuration类及其依赖项与导航基于接口的代码的通常过程没有什么不同。


如果你想流畅启动创建这些 bean 的顺序,考虑声明它们为@Lazy(用于首次访问而不是启动时创建)或像@DependsOn其他 bean(确保其他指定 bean 在当前 bean 被创建之前 ,而不受后者直接依赖关系的影响)。


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


根据某些系统状态,有条件地启用或禁用完整的@Configuration类甚至单个@Bean方法,通常很有用。说明:可以启用/禁止@Configuration类或者@Configuration类中的@Bean方法。一个常用的例子是去使用@Profile注解去激活 bean,仅仅当在 Spring 中的Environment指定的profile被激活时(查看 Bean 定义 Profiles 详情)。


@Profile注解通过使用一个非常灵活的注解叫做 @Conditional实现的。@Conditional注解指示在注册@Bean之前应咨询特定org.springframework.context.annotation.Condition实现。


Condition接口的实现接口提供一个matches(…)方法,它会返回 true 或 false。例如,下面的清单显示Condition@Profile真实实现:


@Overridepublic 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文档更多详细。


结合 Java 和 XML 配置


Spring 的@Configuration类不能100%的完成替换 Spring XML。一些工具(如 Spring XML namespaces)仍然是配置容器的理想方法。在使用 XML 方便或有必要的情况下,你可以选择:使用“ClassPathXmlApplicationContext以“ XML 中心”方式实例化容器,或通过使用AnnotationConfigApplicationContext和“ Java 配置中心”方式实例化容器。 @ImportResource注解可根据需要导入 XML。


以 XML 为中心的@Configuration类的使用


从 XML 引导 Spring 容器并以特别的方式包含@Configuration类可能更好。例如,在使用 Spring XML 的大型现有代码库中,根据需要创建@Configuration类并且现有 XML 文件中将它们包含在内变得更加容易。在本节的后面,我们将介绍在这种“以 XML 为中心”的情况下使用@Configuration类的选项。


  • 声明@Configuration类似普通的 Spring<bean/>元素

  • 使用 **<context:component-scan/>**选择@Configuration


@Configuration以类为中心的 XML 与@ImportResource的结合使用


在应用中,@Configuration类是配置容器的主要机制,但是仍然可能需要至少使用一些 XML。在这个场景中,你可以使用@ImportResource和定义你需要的 XML。这样做实现了“以 Java 为中心”的方法来配置容器,并使 XML 保持在最低限度。下面例子展示了怎样去使用@ImportResource注解去实现“以 Java 为中心”配置并在需要的时候使用 XML:


@Configuration@ImportResource("classpath:/com/acme/properties-config.xml")public class AppConfig {
@Value("${jdbc.url}") private String url;
@Value("${jdbc.username}") private String username;
@Value("${jdbc.password}") private String password;
@Bean public DataSource dataSource() { return new DriverManagerDataSource(url, username, password); }}
复制代码


properties-config.xml


<beans>    <context:property-placeholder location="classpath:/com/acme/jdbc.properties"/></beans>
复制代码


jdbc.properties


public static void main(String[] args) {    ApplicationContext ctx = new AnnotationConfigApplicationContext(AppConfig.class);    TransferService transferService = ctx.getBean(TransferService.class);    // ...}
复制代码


代码示例:com.liyong.ioccontainer.starter.BaseJavaConfigAndXmlIocContainer


作者


个人从事金融行业,就职过易极付、思建科技、某网约车平台等重庆一流技术团队,目前就职于某银行负责统一支付系统建设。自身对金融行业有强烈的爱好。同时也实践大数据、数据存储、自动化集成和部署、分布式微服务、响应式编程、人工智能等领域。同时也热衷于技术分享创立公众号和博客站点对知识体系进行分享。


博客地址: http://youngitman.tech


CSDN: https://blog.csdn.net/liyong1028826685


微信公众号: 


发布于: 2020 年 09 月 04 日阅读数: 77
用户头像

青年IT男

关注

站在巨人肩上看得更远! 2018.04.25 加入

从事金融行业,就职过易极付、思建科技、网约车平台等一流技术团队,目前就职于银行负责支付系统建设。对金融行业有强烈的爱好。实践大数据、数据存储、自动化集成和部署、分布式微服务、响应式编程、人工智能等领域

评论

发布
暂无评论
Spring 5 中文解析核心篇-IoC容器之基于Java容器配置