Spring IoC容器:基于Java的容器配置

https://docs.spring.io/spring-framework/reference/core/beans/java.html

本节介绍如何在Java代码中使用注解来配置Spring容器。

基本概念:@Bean和@Configuration

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

@Bean注解用于指示一个方法实例化、配置并初始化一个新的对象,以供Spring IoC容器管理。对于熟悉Spring <beans/> XML配置的人来说,@Bean注解扮演着与<bean/>元素相同的角色。你可以将带有@Bean注解的方法与任何Spring @Component一起使用。然而,它们最常与@Configuration bean一起使用。

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

@Configuration
public class AppConfig {

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

前面的AppConfig类等同于以下Spring XML:

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

完整的@Configuration与“轻量级”@Bean模式?

@Bean方法声明在没有用@Configuration注解的类中时,它们被称为“轻量级”模式。在没有用@Configuration注解的bean上声明的bean方法被认为是“轻量级”的。例如,服务组件可能通过在每个适用的组件类上添加一个@Bean方法来向容器公开管理视图。在这种情况下,@Bean方法是通用的工厂方法机制。

与完整的@Configuration不同,“轻量级”@Bean方法不能声明bean之间的依赖关系。相反,它们操作在其包含的组件的内部状态上,并且可以选择性地操作它们可能声明的参数。因此,这样的@Bean方法不应该调用其它@Bean方法。每个这样的方法实际上只是特定bean引用的一个工厂方法,没有任何特殊的运行时语义。这样做的好处是不需要在运行时应用CGLIB子类化,因此在类设计方面没有限制(即,包含类可以是final等等)。

在常见场景中,@Bean方法应该在@Configuration类中声明,确保始终使用“完整”模式,并且跨方法引用因此被重定向到容器的生命周期管理。这防止了通过常规Java调用意外地调用相同的@Bean方法,有助于减少在“轻量级”模式下运行时可能难以追踪的微妙错误。

使用AnnotationConfigApplicationContext实例化Spring容器

以下章节记录了Spring 3.0中引入的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();
}

前面的示例假设MyServiceImplDependency1Dependency2使用了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")
public class AppConfig  {
	// ...
}

经验丰富的Spring用户可能熟悉来自Spring的context:命名空间的XML声明,如下例所示:

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

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

@Configuration类是用@Component元注解的,因此它们是组件扫描的候选对象。在前述示例中,假设AppConfigcom.acme包内(或其下的任何包)声明,那么在调用scan()时会被捕获。在调用refresh()时,所有它的@Bean方法都会被处理并注册为容器内的bean定义。

使用AnnotationConfigWebApplicationContext支持Web应用程序

AnnotationConfigApplicationContextWebApplicationContext变体是AnnotationConfigWebApplicationContext。当配置Spring ContextLoaderListener servlet监听器、Spring MVC DispatcherServlet等时,可以使用此实现。以下web.xml代码片段配置了一个典型的Spring MVC Web应用程序(注意contextClass context-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>

对于编程场景,可以使用GenericWebApplicationContext作为AnnotationConfigWebApplicationContext的替代选项。

用@Bean注解

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

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

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

声明一个Bean

要声明一个bean,可以使用@Bean注解标记一个方法。使用这个方法在ApplicationContext中注册一个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>

两种声明都在ApplicationContext中提供了一个名为transferService的bean,绑定到了一个TransferServiceImpl类型的对象实例,如下图所示:

transferService -> com.acme.TransferServiceImpl

还可以使用默认方法定义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
	public TransferService transferService() {
		return new TransferServiceImpl();
	}
}

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

如果你始终通过声明的服务接口引用你的类型,那么你的@Bean返回类型可以安全地加入该设计决策。但是,对于实现多个接口的组件或可能通过其实现类型引用的组件,最好声明尽可能具体的返回类型(至少与引用bean的注入点所需的一样具体)。

Bean 依赖

带有@Bean注解的方法可以有任意数量的参数,这些参数描述了构建该bean所需的依赖项。例如,如果我们的TransferService需要一个AccountRepository,我们可以通过方法参数实例化该依赖项,如下例所示:

@Configuration
public class AppConfig {

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

解析机制与基于构造函数的依赖注入非常相似。

接收生命周期回调

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

常规的Spring生命周期回调也得到了完全支持。如果bean实现了InitializingBeanDisposableBeanLifecycle接口,它们的相应方法将被容器调用。

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

@Bean注解支持指定任意的初始化和销毁回调方法,就像Spring XML中的bean元素的init-methoddestroy-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();
	}
}

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

你可能希望默认情况下对于通过JNDI获取的资源这样做,因为它的生命周期是在应用程序之外管理的。特别是,确保始终对DataSource这样做,因为在Jakarta EE应用服务器上它是已知的问题。

以下示例展示了如何防止对DataSource进行自动销毁回调:

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

此外,对于@Bean方法,你通常使用编程式JNDI查找,无论是使用Spring的JndiTemplate还是JndiLocatorDelegate帮助类,或者直接使用JNDI InitialContext,而不是使用JndiObjectFactoryBean变体(这将迫使你将返回类型声明为FactoryBean类型,而不是实际的目标类型,这使得在其它@Bean方法中引用这里提供的资源时更难以进行交叉引用调用)。

在上述示例中的BeanOne的情况下,直接在构造期间调用init()方法是同样有效的,如下例所示:

@Configuration
public class AppConfig {

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

	// ...
}

当直接使用Java工作时,可以随意处理对象,并不总是需要依赖容器的生命周期。

指定Bean的作用域

Spring包含了@Scope注解,以便可以指定bean的作用域。

使用 @Scope注解

可以指定使用@Bean注解定义的bean应该具有特定的作用域。可以使用任何标准作用域。

默认的作用域是singleton,但可以使用@Scope注解覆盖它,如下例所示:

@Configuration
public class MyConfiguration {

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

@Scope 和 scoped-proxy

Spring提供了一种通过作用域代理来处理作用域依赖的便捷方式。在使用XML配置创建这样的代理时,最简单的方法是使用<aop:scoped-proxy/>元素。使用@Scope注解在Java中配置你的bean可以提供等效的支持,并带有proxyMode属性。默认值为ScopedProxyMode.DEFAULT,通常表示除非在component-scan指令级别配置了不同的默认值,否则不应创建作用域代理。可以指定ScopedProxyMode.TARGET_CLASSScopedProxyMode.INTERFACESScopedProxyMode.NO

示例:

// 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属性覆盖此功能,如下例所示:

@Configuration
public class AppConfig {

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

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

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

使用@Configuration注解

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

注入bean之间的依赖关系(Injecting Inter-bean Dependencies)

当bean之间存在依赖关系时,表达这种依赖关系就像让一个bean方法调用另一个方法一样简单,如下例所示:

@Configuration
public class AppConfig {

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

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

在上述示例中,beanOne通过构造器注入接收了对beanTwo的引用。

声明bean之间依赖关系的这种方法仅在@Bean方法声明在@Configuration类中时有效。不能使用普通的@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()方法被重写,以便查找一个新的(原型)命令对象。以下示例展示了如何做到这一点:

@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()clientService1()clientService2()中各被调用了一次。由于此方法创建并返回了一个ClientDaoImpl的新实例,你通常会预期有两个实例(每个服务一个)。这肯定是有问题的:在Spring中,实例化的bean默认具有单例作用域。这就是魔法发生的地方:所有@Configuration类在启动时都会被CGLIB子类化。在子类中,子方法首先检查容器中是否有任何缓存的(有作用域的)bean,然后再调用父方法并创建一个新实例。

根据bean的作用域,行为可能会有所不同。我们这里讨论的是单例。

无需将CGLIB添加到你的类路径中,因为CGLIB类被重新打包到org.springframework.cglib包下,并直接包含在spring-core JAR中。

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

如果你希望避免任何CGLIB强加的限制,请考虑在非@Configuration类上声明你的@Bean方法(例如,在普通的@Component类上),或者通过使用@Configuration(proxyBeanMethods = false)注解你的配置类。然后,不会拦截@Bean方法之间的跨方法调用,因此你必须完全依赖于构造函数或方法级别的依赖注入。

组合基于Java的配置(Composing Java-based Configurations)

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

现在,当实例化上下文时,不再需要同时指定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类。

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

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

还有另一种方法可以实现相同的结果。@Configuration类最终只是容器中的另一个bean:这意味着它们可以利用@Autowired@Value注入以及其它特性,就像任何其它bean一样。

确保你以这种方式注入的依赖关系只属于最简单的类型。@Configuration类在上下文初始化期间处理得相当早,强制以这种方式注入依赖关系可能会导致意外的早期初始化。只要有可能,请像前面的示例一样使用基于参数的注入。

避免在同一配置类的@PostConstruct方法中访问本地定义的bean。这实际上导致了循环引用,因为非静态@Bean方法在语义上需要调用完全初始化的配置类实例。如果不允许循环引用(例如在Spring Boot 2.6+中),这可能会触发BeanCurrentlyInCreationException

另外,在使用@Bean定义BeanPostProcessorBeanFactoryPostProcessor时要特别小心。这些通常应该声明为静态@Bean方法,不会触发其包含的配置类的实例化。否则,由于可能会在AutowiredAnnotationBeanPostProcessor之前创建配置类作为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");
}

@Configuration类中使用构造函数注入仅在Spring Framework 4.3及更高版本中受支持。还要注意,如果目标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());
	}
}

在前述情况中,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");
}

现在,ServiceConfig相对于具体的DefaultRepositoryConfig是松散耦合的,内置的IDE工具仍然有用:你可以轻松获取RepositoryConfig实现的类型层次结构。这样,导航@Configuration类及其依赖关系与通常的基于接口代码导航过程没有什么不同。

如果你希望影响某些bean的启动创建顺序,请考虑将其中一些bean声明为@Lazy(在首次访问时创建,而不是在启动时)或声明为@DependsOn某些其它bean(确保在当前bean之前创建特定的其他bean,超出后者直接依赖关系所暗示的范围)。

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

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

@Profile注解实际上是通过使用一个更灵活的注解@Conditional来实现的。@Conditional注解指示在注册@Bean之前应该咨询哪些特定的org.springframework.context.annotation.Condition实现。

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

结合Java和XML配置

Spring的@Configuration类支持并不是旨在完全替代Spring XML。有些功能,如Spring XML命名空间,仍然是配置容器的理想方式。在XML方便或必要的情况下,可以选择:要么通过使用ClassPathXmlApplicationContext等以“以XML为中心”的方式实例化容器,要么通过使用AnnotationConfigApplicationContext@ImportResource注解以“以Java为中心”的方式实例化它,并根据需要导入XML。

以XML为中心的@Configuration类使用

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

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

@Configuration类最终是容器中的bean定义。在本系列示例中,我们创建了一个名为AppConfig@Configuration类,并将其作为<bean/>定义包含在system-test-config.xml中。因为<context:annotation-config/>已经打开,容器识别@Configuration注解并正确处理AppConfig中声明的@Bean方法。

以下示例显示了Java中的普通配置类:

@Configuration
public class AppConfig {

	@Autowired
	private DataSource dataSource;

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

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

以下示例显示了样例system-test-config.xml文件的一部分:

<beans>
	<!-- enable processing of annotations such as @Autowired and @Configuration -->
	<context:annotation-config/>
	<context:property-placeholder location="classpath:/com/acme/jdbc.properties"/>

	<bean class="com.acme.AppConfig"/>

	<bean class="org.springframework.jdbc.datasource.DriverManagerDataSource">
		<property name="url" value="${jdbc.url}"/>
		<property name="username" value="${jdbc.username}"/>
		<property name="password" value="${jdbc.password}"/>
	</bean>
</beans>

以下示例显示了一个可能的jdbc.properties文件:

jdbc.url=jdbc:hsqldb:hsql://localhost/xdb
jdbc.username=sa
jdbc.password=
public static void main(String[] args) {
	ApplicationContext ctx = new ClassPathXmlApplicationContext("classpath:/com/acme/system-test-config.xml");
	TransferService transferService = ctx.getBean(TransferService.class);
	// ...
}

system-test-config.xml文件中,AppConfig 没有声明id元素。这样做是可以接受的,由于没有其它bean引用它,并且不太可能通过名称从容器中显式获取它。同样,DataSource bean只通过类型自动装配,因此不需要显式的bean id

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

因为@Configuration使用了@Component元注解,所以@Configuration注解的类自动成为组件扫描的候选者。使用与前面示例中描述的相同场景,我们可以重新定义system-test-config.xml以利用组件扫描。注意,在这种情况下,我们不需要显式声明<context:annotation-config/>,因为<context:component-scan/>启用了相同的功能。

以下示例显示了修改后的system-test-config.xml文件:

<beans>
	<!-- picks up and registers AppConfig as a bean definition -->
	<context:component-scan base-package="com.acme"/>
	<context:property-placeholder location="classpath:/com/acme/jdbc.properties"/>

	<bean class="org.springframework.jdbc.datasource.DriverManagerDataSource">
		<property name="url" value="${jdbc.url}"/>
		<property name="username" value="${jdbc.username}"/>
		<property name="password" value="${jdbc.password}"/>
	</bean>
</beans>

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

@Configuration类是配置容器的主要机制的应用程序中,仍然可能需要使用至少一些XML。在这些场景中,可以使用@ImportResource并仅定义所需的XML。这样做实现了配置容器的“以Java为中心”的方法,并将XML的使用降到最低。以下示例(包括配置类、定义bean的XML文件、properties 文件和main 类)显示了如何使用@ImportResource注解实现根据需要使用XML的“以Java为中心”的配置:

@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

jdbc.url=jdbc:hsqldb:hsql://localhost/xdb
jdbc.username=sa
jdbc.password=
public static void main(String[] args) {
	ApplicationContext ctx = new AnnotationConfigApplicationContext(AppConfig.class);
	TransferService transferService = ctx.getBean(TransferService.class);
	// ...
}
  • 30
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值