Spring | 3.12 基于java的容器配置

3.12 基于java的容器配置
3.12.1 基本概念:@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 MyService myService() {
		return new MyServiceImpl();
	}

}

上面的AppConfig类等价于下面的Spring < beans/> XML:

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

注意:完全@Configuration和“lite”@Beans模式?
当@Bean方法在没有使用@Configuration注解的类中声明时,它们被称为在“lite”模式下处理。例如,在@Component或普通旧类中声明的bean方法将被认为是“lite”。
与完整的@Configuration不同,lite @Bean方法不能轻松声明bean之间的依赖关系。通常,在“lite”模式下操作时,一个@Bean方法不应该调用另一个@Bean方法。
只在@Configuration类中使用@Bean方法是确保总是使用“full”模式的推荐方法。这将防止相同的@Bean方法意外地被多次调用,并有助于减少在“lite”模式下操作时难以跟踪的细微bug。

下面几节将深入讨论@Bean和@Configuration注解。但是,首先,我们将介绍使用基于java的配置创建spring容器的各种方法。

3.12.2 使用AnnotationConfigApplicationContext实例化Spring容器

文档Spring的AnnotationConfigApplicationContext是Spring 3.0中新增的。这个通用的ApplicationContext实现不仅可以接受@Configuration类作为输入,还可以接受普通的@Component类和用JSR-330元数据注解的类。
当@Configuration类作为输入提供时,@Configuration类本身注册为bean定义,类中所有声明的@Bean方法也注册为bean定义。
当提供@Component和JSR-330类时,它们被注册为bean定义,并且假设DI元数据(如@Autowired或@Inject)在这些类中使用。

结构简单

与在实例化ClassPathXmlApplicationContext时将Spring XML文件用作输入非常相似,@Configuration类可以在实例化AnnotationConfigApplicationContext时用作输入。这允许完全不使用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();
}

上面假设MyServiceImpl、Dependency1和Dependency2使用Spring dependency注入注解,比如@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上下文等价的XML声明:namespace

<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进行元注解的,所以它们是组件扫描的候选对象!在上面的例子中,假设AppConfig是在com.acme包中声明的(或下面的任何包),它将在调用scan()期间被获取,在refresh()时,它的所有@Bean方法将被处理并作为容器中的bean定义注册。

支持带有AnnotationConfigWebApplicationContext的web应用程序

AnnotationConfigApplicationContext的WebApplicationContext变体可以与AnnotationConfigWebApplicationContext一起使用。此实现可用于配置Spring ContextLoaderListener servlet侦听器、Spring MVC DispatcherServlet等。下面是配置典型Spring MVC web应用程序的web.xml片段。注意context-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.12.3 使用@Bean注解

@Bean是方法级别的注解,是XML < bean/>元素的直接模拟。注解支持< bean/>提供的一些属性,比如:init-method、destroy-method、自动装配和name。
你可以在@Configuration注解的类或@Component注解的类中使用@Bean注解。

声明一个bean

要声明一个bean,只需使用@Bean注解注解一个方法。你可以使用此方法在作为方法返回值指定的类型的ApplicationContext中注册bean定义。默认情况下,bean名称将与方法名称相同。下面是一个@Bean方法声明的简单例子:

@Configuration
public class AppConfig {

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

}

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

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

这两种声明都使名为transferService的bean在ApplicationContext中可用,绑定到类型为TransferServiceImpl的对象实例:

transferService -> com.acme.TransferServiceImpl
Bean的依赖关系

带@Bean注解的方法可以有任意数量的参数来描述构建该bean所需的依赖关系。例如,如果我们的TransferService需要一个AccountRepository,我们可以通过一个方法参数来实现依赖关系:

@Configuration
public class AppConfig {

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

}

解析机制与基于构造函数的依赖项注入非常相似,有关详细信息,请参阅相关部分。

接受生命周期回调

使用@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 Foo {
	public void init() {
		// initialization logic
	}
}

public class Bar {
	public void cleanup() {
		// destruction logic
	}
}

@Configuration
public class AppConfig {

	@Bean(initMethod = "init")
	public Foo foo() {
		return new Foo();
	}

	@Bean(destroyMethod = "cleanup")
	public Bar bar() {
		return new Bar();
	}

}

注意:默认情况下,使用具有公共close或shutdown方法的Java配置定义的bean会被自动征募到一个销毁回调函数中。如果你有一个公共close或shutdown方法,并且你不希望在容器关闭时调用它,那么只需将@Bean(destroyMethod="")添加到bean定义中,以禁用默认(推断)模式。
默认情况下,你可能希望对通过JNDI获得的资源这样做,因为它的生命周期是在应用程序外部管理的。特别是,请确保始终为DataSource执行此操作,因为在Java EE应用程序服务器上,数据源是有问题的。

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

@Bean方法,你通常会选择使用程序化的JNDI查找:直接使用Spring的JndiTemplate / JndiLocatorDelegate助手或者使用JNDI InitialContext,但不是JndiObjectFactoryBean变体,这将迫使你声明返回类型作为FactoryBean类型,而不是实际的目标类型,使其难以在其他@ bean中使用交叉引用调用方法,准备在方法内指向提供的资源。

当然,对于上面的Foo,在构造过程中直接调用init()方法同样有效:

@Configuration
public class AppConfig {
	@Bean
	public Foo foo() {
		Foo foo = new Foo();
		foo.init();
	    return foo;
	}

	// ...

}

注意:当你直接在Java中工作时,你可以对对象做任何你喜欢的事情,而不总是需要依赖容器生命周期!

指定bean范围
使用@Scope注解

你可以指定使用@Bean注解定义的bean应该具有特定的范围。你可以使用Bean作用域部分中指定的任何标准作用域。
默认的作用域是单例的,但是你可以用@Scope注解覆盖它:

@Configuration
public class MyConfiguration {

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

}
@Scope和作用域内的代理

Spring提供了一种通过作用域代理处理作用域依赖项的方便方法。使用XML配置时创建这样一个代理的最简单方法是< aop:scoped-proxy/>元素。使用@Scope注解在Java中配置bean可以提供与proxyMode属性相同的支持。默认值是no proxy (ScopedProxyMode. No),但是你可以指定ScopedProxyMode.TARGET_CLASS或ScopedProxyMode.INTERFACES。
如果你使用Java将XML参考文档(请参阅前面的链接)中的作用域代理示例移植到我们的@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属性覆盖此功能。

@Configuration
public class AppConfig {

	@Bean(name = "myFoo")
	public Foo foo() {
		return new Foo();
	}

}
Bean的别名

正如在3.3.1节“命名bean”中所讨论的,有时需要为单个bean指定多个名称,也称为bean别名。@Bean注解的name属性接受用于此目的的字符串数组。

@Configuration
public class AppConfig {

	@Bean(name = { "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 Foo foo() {
		return new Foo();
	}

}
3.12.4 使用@Configuration注解

@Configuration是一个类级注解,指示对象是bean定义的源。@Configuration类通过公共的@Bean注解方法声明bean。在@Configuration类上调用@Bean方法也可以用来定义bean之间的依赖关系。有关一般介绍,请参见第3.12.1节“基本概念:@Bean和@Configuration”。

注入bean间的依赖关系

当@ bean相互依赖时,表示这种依赖就像让一个bean方法调用另一个bean方法一样简单:

@Configuration
public class AppConfig {

	@Bean
	public Foo foo() {
		return new Foo(bar());
	}

	@Bean
	public Bar bar() {
		return new Bar();
	}

}

在上面的例子中,foo bean通过构造函数注入接收对bar的引用。

注意:这种声明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-configuration支持,你可以创建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 command() 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();
	}

}

在clientService1()和clientService2()中分别调用了一次clientDao()和一次clientDao()。因为这个方法创建了一个新的ClientDaoImpl实例并返回它,所以你通常会期望有两个实例(每个服务一个)。这肯定会有问题:在Spring中,实例化的bean默认有一个单例范围。这就是神奇之处:所有@Configuration类都是在启动时用CGLIB子类化的。在子类中,子方法在调用父方法并创建新实例之前,首先检查容器是否有缓存的bean(作用域)。注意,从Spring 3.2开始,不再需要将CGLIB添加到类路径中,因为CGLIB类已经在org.springframework.cglib下重新打包,并直接包含在spring-core JAR中。

注意:根据bean的范围,行为可能有所不同。我们这里说的是单例。
注意:由于CGLIB在启动时动态添加特性,特别是配置类不能是最终的,所以存在一些限制。但是,从4.3开始,配置类上允许使用任何构造函数,包括使用@Autowired或单个非默认构造函数声明进行默认注入。如果你希望避免cglib强加的任何限制,请考虑在非-@Configuration类上声明@Bean方法,例如在普通的@Component类上声明@Bean方法。@Bean方法之间的交叉方法调用不会被拦截,因此你必须完全依赖于构造函数或方法级别的依赖注入。

3.12.5 编写基于java的配置
使用@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();
	}

}

现在,在实例化上下文时不需要同时指定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方法。如果你想避免组件扫描,使用几个配置类作为显式定义所有组件的入口点,这将特别有用。

在导入的@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:这意味着它们可以像其他bean一样利用@Autowired和@Value注入等等!

注意:确保以这种方式注入的依赖关系只是最简单的那种。@Configuration类在上下文初始化过程中很早就被处理,强制以这种方式注入依赖项可能会导致意外的早期初始化。只要可能,就像上面的例子一样,使用基于参数的注入。
注意: 另外,要特别注意通过@Bean定义的BeanPostProcessor和BeanFactoryPostProcessor。这些方法通常应该声明为静态@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;

	@Autowired
	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;在上面的示例中,在RepositoryConfig构造函数上不需要@Autowired。

在上面的场景中,使用@Autowired可以很好地工作,并提供了所需的模块性,但是准确地确定在哪里声明了autowired bean定义仍然有些模糊。例如,作为一个查看ServiceConfig的开发人员,你如何确切地知道@Autowired AccountRepository bean声明在哪里?它在代码中没有显式显示,这可能很好。请记住,Spring Tool Suite提供了一些工具,可以呈现显示如何连接所有内容的图表——这可能就是你所需要的全部内容。而且,你的Java IDE可以轻松地找到AccountRepository类型的所有声明和使用,并将快速显示返回该类型的@Bean方法的位置。
如果这种模糊性是不可接受的,并且你希望在IDE中从一个@Configuration类直接导航到另一个@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类及其依赖项与导航基于接口的代码的通常过程没有什么不同。

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

根据某些任意的系统状态,有条件地启用或禁用一个完整的@Configuration类,甚至单个的@Bean方法,这通常很有用。一个常见的例子是,只有在Spring环境中启用了特定的概要文件时,才使用@Profile注解来激活Bean(有关详细信息,请参阅3.13.1节“Bean定义概要文件”)。
@Profile注解实际上是使用一个灵活得多的名为@Conditional的注解实现的。@Conditional注解指示特定的org.springframework.context.annotation.Conditions实现, 注册@Bean之前应该考虑的条件。
Condition接口的实现只提供一个返回true或false的matches(…)方法。例如,下面是@Profile使用的实际Condition实现:

@Override
public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
	if (context.getEnvironment() != null) {
		// 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 javadocs。

结合Java和XML配置

Spring的@Configuration类支持的目标不是100%完全替代Spring XML。一些工具,如Spring XML名称空间,仍然是配置容器的理想方法。在XML方便或必要的情况下,你可以选择:要么以“以XML为中心”的方式实例化容器,例如使用ClassPathXmlApplicationContext,要么以“以java为中心”的方式实例化容器,根据需要使用AnnotationConfigApplicationContext和@ImportResource注解导入XML。

以xml为中心使用@Configuration类

最好从XML引导Spring容器,并以特别的方式包含@Configuration类。例如,在使用Spring XML的大型现有代码基中,根据需要创建@Configuration类并从现有XML文件中包含它们将更加容易。下面你将找到在这种“以xml为中心”的情况下使用@Configuration类的选项。
请记住,@Configuration类最终只是容器中的bean定义。在本例中,我们创建了一个名为AppConfig的@Configuration类,并将其作为< bean/>定义包含在system-test-config.xml中。因为打开了< context:annotation-config/>,容器将识别@Configuration注解并正确处理AppConfig中声明的@Bean方法。

@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 < bean/>没有声明id元素。虽然这样做是可以接受的,但是没有必要这样做,因为没有其他bean会引用它,而且不太可能通过名称显式地从容器中获取它。同样,对于DataSource bean——它只由类型自动生成,因此并不严格要求显式bean id。

因为@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保持在最低限度。

@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);
	// ...
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值