1.10 Classpath扫描和组件管理
1.10.1 @Component
和其他模型注解
- @Repository
标记数据存储角色的类或接口,如DAO,它的作用之一是自动翻译异常。 - Controller
- Service
- Component
1.10.2 元注解
元注解是一个简单的注解,可以用来定义其他的注解。如@Component
就是一个元注解。如@Service
就是在它基础上定义的。
元注解还可以被用来创建组合注解。如@RestController
就是·@Controller和
@ResponseBody`的组合注解。
1.10.3 自动扫描并注解bean
想自动扫描,要在@Configuration
类上配置@ComponentScan
注解,并指定basePackages
属性,在多个时用逗号,分号或空格分隔。它相当于xml配置中的<context:component-scan base-package="org.example"/>
(它自动的开启了<context:annotation-config/>
配置)。
1.10.4 在自动扫描中使用Filter
在定义@ComponentScan
时,可以能过includeFilters
或者excludeFilters
进行筛选,这两个属性与xml配置中<context:component-scan/>元素的子元素
<include-filter/>和
<exclude-filter/>`分别对应。
Filter类型:
Filter类型 | 示例 | 说明 |
---|---|---|
annotation | org.example.SomeAnnotation | 目标组件类级别上显示的注解 |
assignable | org.example.SomeClass | |
aspectj | org.example..*Service+ | 匹配AspectJ类型表达式的目标组件 |
regex | org.example.Default.* | 正则表达式所匹配的目标组件 |
custom | org.example.MyTypeFilter | 自定义的o.s.c.type.TypeFilter 接口的实现 |
示例:
@Configuration
@ComponentScan(basePackages = "org.example",
includeFilters = @Filter(type = FilterType.REGEX, pattern = ".*Stub.*Repository"),
excludeFilters = @Filter(Repository.class))
public class AppConfig {
...
}
// 对应的xml
<beans>
<context:component-scan base-package="org.example">
<context:include-filter type="regex"
expression=".*Stub.*Repository"/>
<context:exclude-filter type="annotation"
expression="org.springframework.stereotype.Repository"/>
</context:component-scan>
</beans>
也可通过useDefaultFilters=false
或use-default-filters=false
来禁用默认filters.这实际上是禁用了自动发现带有@Component, @Repository, @Service, @Controller, or @Configuration
注解的类。
1.10.5 在组件内定义bean元数据
spring的组件可以为容器提供bean的配置元数据.即在@Configuration
类中使用@Bean
注解定义工厂方法定义bean.还可以与其组合使用@Qualifier
,Scope
, @Lazy
及自定义注解.
示例:
@Component
public class FactoryMethodComponent {
private static int i;
@Bean
@Qualifier("public")
public TestBean publicInstance() {
return new TestBean("publicInstance");
}
// use of a custom qualifier and autowiring of method parameters
// 将privateInstance bean的age属性值注入给了country参数.
// 对于@Value注解,在解析表达式时表达式解析器会预先查找bean的名称
@Bean
protected TestBean protectedInstance(
@Qualifier("public") TestBean spouse,
@Value("#{privateInstance.age}") String country) {
TestBean tb = new TestBean("protectedInstance", 1);
tb.setSpouse(spouse);
tb.setCountry(country);
return tb;
}
@Bean
private TestBean privateInstance() {
return new TestBean("privateInstance", i++);
}
@Bean
@RequestScope
public TestBean requestScopedInstance() {
return new TestBean("requestScopedInstance", 3);
}
}
从spring 4.3开始,你可以声明一个InjectionPoin
类型(或其子类型)的参数,以便在创建这个bean时你能够访问请求注入点.但这仅用于bean实例的创建.因此这对于创建prototype
类型的bean很有意义.对于其他作用域,这个工厂方法永远都只能在创建新的bean时才能看见这个注入点.
示例:
@Component
public class FactoryMethodComponent {
@Bean @Scope("prototype")
public TestBean prototypeInstance(InjectionPoint injectionPoint) {
return new TestBean("prototypeInstance for " + injectionPoint.getMember());
}
}
@Bean
注解的方法在常规组件和@Configuration
类中的处理方法是不同的.即在@Component
类中是不会用CGLIB增强来拦截属性或方法的调用. CGLIB代理是通过调用@Configuration
类中的@Bean
方法中的字段或方法来创建协作对象的bean元数据引用的.
- 可以将
@Bean
方法声明为静态方法,这样无需创建包含它的类的实例就可以创建它们.这对于BeanFactoryPostProcessor
或BeanPostProcessor
是有意义的,因为这些bean要在容器生命周期的早期被实例化.- 静态的
@Bean
方法不会被拦截,即使在@Configuration
类也一样,这是由CGLIB的技术设计所决定的,即CGLIB只会代理非静态的public
方法.- 在
@Configuration
类中的常规@Bean
方法不能声明为private
或final
.- 在指定的组件或配置类的基类中,
@Bean
方法也能被发现,这就与JAVA 8接口(被组件或配置类实现的接口)中的默认方法一样.这为复杂的组件实现提供了很大的灵活性.- 一个类中可以包含同一个bean的多个
@Bean
方法,这时容器会选择有最多个参数的@Bean
方法.这与选择构造函数时的算法一样.
1.10.6 定义组件的bean名称
spring容器在自动扫描组件时, 其bean名称是由BeanNameGenerator
生成的, 默认情况下, 所有包含name
属性的组件注解在被扫描时,其name
属性的值都将作为bean名称.
自定义bean名称生成策略
- 继承
BeanNameGenerator
接口, 注意必须要提供无参构造方法. - 注册
@Configuration
@ComponentScan(basePackages = "org.example", nameGenerator = MyNameGenerator.class)
public class AppConfig {
...
}
// xml:
<beans>
<context:component-scan base-package="org.example"
name-generator="org.example.MyNameGenerator" />
</beans>
1.10.7 定义组件的作用域
spring容器bean的默认作用域是单例的.可通过@Scope
属性定义其他作用域.
可用于组件类或@Bean
方法上.
示例:
@Scope("prototype")
@Repository
public class MovieFinderImpl implements MovieFinder {
// ...
}
自定义作用域
- 实现
ScopeMetadataResolver
接口, 实现类必须要包含默认的无参构造方法. - 注册
@Configuration
@ComponentScan(basePackages = "org.example", scopeResolver = MyScopeResolver.class)
public class AppConfig {
...
}
xml:
<beans>
<context:component-scan base-package="org.example" scope-resolver="org.example.MyScopeResolver"/>
</beans>
当自定义非单例作用域时,要为作用域对象生成代理对象,这时要配置component-scan元素的scoped-proxy属性,它有三个选项: no
, interfaces
, targetClass
.
示例:
@Configuration
@ComponentScan(basePackages = "org.example", scopedProxy = ScopedProxyMode.INTERFACES)
public class AppConfig {
...
}
<beans>
<context:component-scan base-package="org.example" scoped-proxy="interfaces"/>
</beans>
1.10.8 @Qualifier
@Component
@Qualifier("Action")
public class ActionMovieCatalog implements MovieCatalog {
// ...
}
1.10.9 生成组件索引
尽管通过类路径扫描组件是非常快的,在启动大型应用时,提供静态的组件列表会大大提高应用启动速度,但此时容器的所有模块都必须使用这种机制.这时,容器使用组件索引而不是从类路径去加载它.
添加依赖:
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context-indexer</artifactId>
<version>5.0.10.BUILD-SNAPSHOT</version>
<optional>true</optional>
</dependency>
这将生成一个META-INF/spring.components
文件来包含这个jar.
当一个索引只对部分库有用而不能在整个应用中构建,此时可通过设置spring.index.ignore
为ture
来忽略索引(即回归到类路径).
1.11 使用JSR-330标准注解
从spring 3.0 开始, Spring提供了JSR-330标准注解支持, 它们的使用方法与Spring的注解一样. 但是这需要添加以下依赖:
<dependency>
<groupId>javax.inject</groupId>
<artifactId>javax.inject</artifactId>
<version>1</version>
</dependency>
1.11.1 使用@Inject
和@Named
进行依赖注入
使用@Inject
代替@Autowired
@Inject
注解可以用于字段,构造函数,方法级别.还可通过@Named
注解指定依赖的bean名称.
示例:
public class SimpleMovieLister {
private MovieFinder movieFinder;
@Inject
public void setMovieFinder(@Named("main") MovieFinder movieFinder) {
this.movieFinder = movieFinder;
}
// ...
}
1.11.2 使用@Named
或@ManagedBean
注解代替@Component
示例:
@Named("movieListener")
// @ManagedBean("movieListener") could be used as well
public class SimpleMovieLister {
private MovieFinder movieFinder;
@Inject
public void setMovieFinder(MovieFinder movieFinder) {
this.movieFinder = movieFinder;
}
// ...
}
开启@Named
或@ManagedBean
进行组件扫描与spring的注解一样,即:
@Configuration
@ComponentScan(basePackages = "org.example")
public class AppConfig {
...
}
注: JSR-330和JSR-250的@ManagedBean
注解是不能用来进行自定义注解的.
1.11.3 JSR-330注解的局限
使用标准注解时,有些特性是不可用的:
Spring | JSR-330 | 注意 |
---|---|---|
@Autowired |
@Inject |
@Inject没有required属性,可以与JAVA8的Optional一起使用 |
@Component |
@Named/@ManagedBean |
JSR-330不能作为元注解 |
@Scope("singleton") |
@Singleton |
JSR-330默认作用域是prototype, <br>但在spring容器中默认是单例的, <br>以与spring保持一致, 要使用其他作用域,请使用spring的@Scope注解 |
@Qualifier |
@Qualifier/@Named |
JSR-330的@Qualifier注解仅作为元注解使用 |
@Value |
- |
|
@Required |
- |
|
@Lazy |
- |
|
ObjectFactory |
Provider |
后者具有更短的get方法名 |
1.12 基于java的容器配置
1.12.1 基本概念: @Bean和@Configuration
@Bean
相当于xml配置中的<bean/>
元素.
在@Configuration
类中的@Bean
方法可以调用另一个@Bean
方法以定义它们之间的依赖关系,但是在非@Configuration
类中不可以.
1.12.2 使用AnnotationConfigApplicationContext实例化Spring容器
AnnotationConfigApplicationContext
不仅可以接收@Configuration
类作为输入,也可以接收@Component
类及JSR-330定义的类级别元注解.
当使用@Configuration
注解时,这个类本身会作为bean定义被注册,类中的所有@Bean
方法也会作为bean定义被注册.
当使用@Component
或JSR-330注解时,它们也会被作为bean定义进行注册.但在类中的依赖要用@Autowired
或@Inject
.
简单示例之构造函数实例化容器
public static void main(String[] args) {
//可接收多个@Configuration类或@Component类或JSR-330注解类
ApplicationContext ctx = new
AnnotationConfigApplicationContext(AppConfig.class);
MyService myService = ctx.getBean(MyService.class);
myService.doStuff();
}
简单示例之register
方法实例化
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();
}
启用组件扫描
@Configuration
@ComponentScan(basePackages = "com.acme")
public class AppConfig {
...
}
// 等价xml:
<context:component-scan base-package="com.acme"/>
编程式启用组件扫描
public static void main(String[] args) {
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext();
ctx.scan("com.acme"); //启用组件扫描
ctx.refresh();
MyService myService = ctx.getBean(MyService.class);
}
注: @Configuration注解基于@Component定义的
使用AnnotationConfigWebApplicationContext支持Web应用
web.xml配置:
<web-app>
<!-- 使用 AnnotationConfigWebApplicationContext配置ContextLoaderListener
代替默认的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>
<!-- 声明DispatcherServlet -->
<servlet>
<servlet-name>dispatcher</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<!-- 使用 AnnotationConfigWebApplicationContext配置DispatcherServlet代替默认的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
注解是一个方法级别的注解,与<bean/>
元素功能相同,所以它也支持<bean/>
元素的一些属性.
示例:
@Configuration
public class AppConfig {
@Bean
public TransferServiceImpl transferService() {
return new TransferServiceImpl();
}
// @Bean方法的返回类型也可以是接口或基类
@Bean
public TransferService transferService() {
return new TransferServiceImpl();
}
}
Bean依赖
@Bean方法可以有通过参数来定义其依赖, 这与构造函数注入类似.
@Configuration
public class AppConfig {
@Bean
public TransferService transferService(AccountRepository accountRepository) {
return new TransferServiceImpl(accountRepository);
}
}
接收生命周期回调
- 在@Bean注解生成的bean类中通过
@PostConstruct
或@PreDestory
注解定义. - bean实现了
InitializingBean
或DisposableBean
或Lifecycle
接口. - 标准的Aware接口.
- init-method, destory-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();
}
}
// 对于初始化回调,也可以在@Bean方法中直接调用定义的初始化方法
@Bean
public Foo foo() {
Foo foo = new Foo();
foo.init();
return foo;
}
在使用JAVA配置时,你可以在对象上做任何事情而不必总是依赖容器的生命周期.
定义Bean的作用域
使用@Scope
注解
@Bean
@Scope("prototype")
public Encryptor encryptor() {
// ...
}
自定义bean名称
指定name
属性
@Configuration
public class AppConfig {
@Bean(name = "myFoo")
public Foo foo() {
return new Foo();
}
}
定义bean的别名
通过name
属性定义
@Configuration
public class AppConfig {
@Bean(name = { "dataSource", "subsystemA-dataSource", "subsystemB-dataSource" })
public DataSource dataSource() {
// instantiate, configure and return DataSource bean...
}
}
定义bean描述
有些情况下可能需要对bean添加一些文字描述.使用@Description
注解.
@Configuration
public class AppConfig {
@Bean
@Description("Provides a basic example of a bean")
public Foo foo() {
return new Foo();
}
}
1.12.4 使用@Configuration
@Configuration
是一个类级注解.
跨bean注入
@Configuration
public class AppConfig {
@Bean
public Foo foo() {
return new Foo(bar()); //相当于调用一个方法, 这只在@Configuration类中有效
}
@Bean
public Bar bar() {
return new Bar();
}
}
查找方法注入
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配置:
@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();
}
}
}
1.12.5 组件JAVA配置
使用@Import注解
与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();
}
}
此时在注册配置类时只需要注册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);
}
这种方法开发人员只需要注册一个配置类而无需关注大量的配置类.从spring 4.2开始,它也可以接收常规的@Component
类.
在Import的bean定义上注入依赖项
上面的例子过于简单,在多数场景下,bean之间都有依赖关系,这通过ref
来定义,因为这不涉及编译问题, 但是在@Configuration类时,对其他的bean必须是有效的java语法,这是由java编译机制决定的.这是很简单的:
@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
}
}
还可以用@Autowired
或@Value
注入来达到同样的效果, 但是这可能会导致某些bean被过早的初始化.因为@Configuration
类在容器的早期就被初始化了.
@Configuration
public class ServiceConfig {
@Autowired
private AccountRepository accountRepository;
@Bean
public TransferService transferService() {
return new TransferServiceImpl(accountRepository);
}
}
@Configuration
public class RepositoryConfig {
private final DataSource dataSource;
//从spring4.3开始才支持@Configuration类中使用构造函数注入,如果只有一个构造函数,可以省略@Autowired
@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
}
}
引用配置类本身
在我们使用@Autowired
注入某个bean时,我们不能显式的看到它在哪里被定义(这个实际上IDE已经解决了这个问题).这时我们可以注入配置类本身.但是这将与另一个配置类完全耦合.
@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());
}
}
有条件的包含@Configuration类或@Bean方法
有时需要根据系统的某些状态来启用或禁用某个@Configuration
类或某个@Bean
方法. 一个常见的例子是用@Profile
注解来根据不同的环境激活相应的配置文件.
@Profile
注解事实上是在@Conditional
注解基础上实现的.
@Conditonal
注解指示了接口org.springframework.context.annotation.Condition
实现, @Bean
在被注册之前都要访问它.这个接口提供了一个boolean matches(...)
方法.
示例:
@Override
public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
if (context.getEnvironment() != null) {
// 读取 @Profile 注解属性
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
类的支持并不是要100%的代替xml配置.
在必须要用XML配置的情况下有两种解决方式:一是用xml配置实例化容器并支持java配置,二是用java配置实例化容器并配合使用@ImportResource
注解引入xml配置.
- 用xml配置实例化容器并支持java配置.
@Configuration
类本质上也是一个bean定义,因此只需将其配置为一个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());
}
}
// xml定义
<beans>
<!-- 开启注解配置>
<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=
注: 因为@Configuration
是以@Component
为元注解,所以可以在xml中声明<context:component-scan base-package=""/>
来开启注解扫描.
** 以java配置为基础组合xml配置**
通过@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=
1.13 环境抽象
Environment
是spring容器中对profile
和properties
应用环境的抽象.
profile
文件是一个命名的bean定义逻辑组,只有在给定概要文件处于活动状态时才向容器注册。与概要文件相关的环境对象的角色是确定哪些概要文件(如果有的话)当前是活动的,以及哪些概要文件(如果有的话)在默认情况下应该是活动的。
properties
几乎在所有的应用中都扮演着重要的角色, 其来源有:properties文件, JVM系统属性, 系统环境变量, JNDI, servlet context参数等等.此时环境对象与属性之间的关系的作用是为用户提供一个方便的服务接口,以便从配置属性源中解析相关的属性。
1.13.1 bean定义profiles
profile是定义容器在不同的环境下拥有不同的bean定义.此时environment
有以下几种含义:
- 在测试或生产环境中加载不同的数据源(数据库).
- 为不同的用户注册不同的bean定义.
@Profile
@Profile
可以指定在不同的环境加载与其对应的配置.
@Configuration
@Profile("development")
public class StandaloneDataConfig {
@Bean
public DataSource dataSource() {
return new EmbeddedDatabaseBuilder()
.setType(EmbeddedDatabaseType.HSQL)
.addScript("classpath:com/bank/config/sql/schema.sql")
.addScript("classpath:com/bank/config/sql/test-data.sql")
.build();
}
}
@Configuration
@Profile("production")
public class JndiDataConfig {
@Bean(destroyMethod="")
public DataSource dataSource() throws Exception {
Context ctx = new InitialContext();
return (DataSource) ctx.lookup("java:comp/env/jdbc/datasource");
}
}
@Profile还可以作为元注解来创建自定义的注解.
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Profile("production")
public @interface Production {
}
// 此时可以用@Producton代替@Profile("production").
注: @Profile注解还可以使用!, 如@Profile({"p1", "!p2"}).则表示在p1激活或p2没有激活时会注册它们
@Profile也可以用于方法级别.
@Configuration
public class AppConfig {
@Bean("dataSource")
@Profile("development")
public DataSource standaloneDataSource() {
return new EmbeddedDatabaseBuilder()
.setType(EmbeddedDatabaseType.HSQL)
.addScript("classpath:com/bank/config/sql/schema.sql")
.addScript("classpath:com/bank/config/sql/test-data.sql")
.build();
}
@Bean("dataSource")
@Profile("production")
public DataSource jndiDataSource() throws Exception {
Context ctx = new InitialContext();
return (DataSource) ctx.lookup("java:comp/env/jdbc/datasource");
}
}
注:当@Profile注解用在@Bean方法上时,如果有多个重载方法,则所有重载的方法上的@Profile定义应当一致,如果不一致, 则只有第一个声明有效. 如果你想用不同的方法名定义相同的bean, 此时通过@Bean的name属性指定为同一个bean即可, 如上所示.
xml定义profile
通过beans元素的profile属性来定义.
<beans profile="development"
xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:jdbc="http://www.springframework.org/schema/jdbc"
xsi:schemaLocation="...">
<jdbc:embedded-database id="dataSource">
<jdbc:script location="classpath:com/bank/config/sql/schema.sql"/>
<jdbc:script location="classpath:com/bank/config/sql/test-data.sql"/>
</jdbc:embedded-database>
</beans>
<beans profile="production"
xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:jee="http://www.springframework.org/schema/jee"
xsi:schemaLocation="...">
<jee:jndi-lookup id="dataSource" jndi-name="java:comp/env/jdbc/datasource"/>
</beans>
还可以将上述两个xml定义在一个xml中:
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:jdbc="http://www.springframework.org/schema/jdbc"
xmlns:jee="http://www.springframework.org/schema/jee"
xsi:schemaLocation="...">
<!-- other bean definitions -->
<beans profile="development">
<jdbc:embedded-database id="dataSource">
<jdbc:script location="classpath:com/bank/config/sql/schema.sql"/>
<jdbc:script location="classpath:com/bank/config/sql/test-data.sql"/>
</jdbc:embedded-database>
</beans>
<beans profile="production">
<jee:jndi-lookup id="dataSource" jndi-name="java:comp/env/jdbc/datasource"/>
</beans>
</beans>
注意: 这类元素只能放在xml文件的最后.
激活profile
- 通过
Environment
API.
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext();
ctx.getEnvironment().setActiveProfiles("development");
ctx.register(SomeConfig.class, StandaloneDataConfig.class, JndiDataConfig.class);
ctx.refresh();
- 通过
spring.profiles.active
属性
它可以通过系统环境变量, JVM系统属性, web.xml中的servlet context参数, JNDI实体来指定.
在spring集成测试模块, 还可以通过@ActiveProfiles注解来指定.
可以同时激活多个.
ctx.getEnvironment().setActiveProfiles("profile1", "profile2");
-Dspring.profiles.active="profile1,profile2"
默认profile
默认profile是指在没有指定的active的profile时,则使用它, 如果有active的profile, 则不使用它.
定义默认profile:
@Configuration
@Profile("default")
public class DefaultDataConfig {
@Bean
public DataSource dataSource() {
return new EmbeddedDatabaseBuilder()
.setType(EmbeddedDatabaseType.HSQL)
.addScript("classpath:com/bank/config/sql/schema.sql")
.build();
}
}
注: @Profile注解的default属性值 可以通过Environment
的setDefaultProfiles()
方法或spring.profiles.default
属性进行自定义.
1.13.2 PropertySource抽象
spring的Environment
抽象提供了对配置的属性源进行搜索的操作.如:
ApplicationContext ctx = new GenericApplicationContext();
Environment env = ctx.getEnvironment();
// PropertySource是对属性键值对的简单抽象, 下面的方法会在一个PropertySource集合中进行搜索
boolean containsFoo = env.containsProperty("foo");
System.out.println("Does my environment contain the 'foo' property? " + containsFoo);
StandardEnvironment
配置了两个PorpertySource对象,一个是JVM系统属性(类似于System.getProperties()), 一个是系统环境变量(类似于System.getenv()).
StandardEnvironment
用于独立应用程序,StandardServletEnviroment
包含了servlet配置和servlet context参数.它还可以选择性的开启JndiPropertySource.- 由于默认情况下, 系统属性优先于环境变量,因此当foo在两个地方都会设置时,
env.getProperty("foo")
会返回系统属性, 注意这个属性并没有合并,而是后面的被前面的覆盖了.- 对于
StandardServletEnvironment
, 其优先级从高到低如下:
- ServletConfig 参数,如DispatcherServlet上下文
- ServletContext参数.如web.xml中的context-param
- JNDI环境变量,如 java:comp/env/
- JVM系统属性, 如 -D命令行参数
- JVM系统环境变量,如操作系统环境变量
最重要的是这是可以配置的,如果你想将你自定义的属性源集成到env中,你只要实现你自己的PropertySource并实例化它,然后将其添加到PropertySources集合中即可.
ConfigurableApplicationContext ctx = new GenericApplicationContext();
MutablePropertySources sources = ctx.getEnvironment().getPropertySources();
sources.addFirst(new MyPropertySource());
在上面的示例中,自定义的PropertySource被添加到最顶级,如果存在某个属性,则会覆盖其他所有的配置.MutablePropertySources
API提供了大量的方法来精准定义.
1.13.3 @PropertySource
@PropertySource
注解可以很方便的将指定的propertySource添加到spring的Environment
中.如:
@Configuration
@PropertySource("classpath:/com/myco/app.properties")
public class AppConfig {
@Autowired
Environment env;
@Bean
public TestBean testBean() {
TestBean testBean = new TestBean();
testBean.setName(env.getProperty("testbean.name"));
return testBean;
}
}
在@PropertySource
注解中可以使用${}
占位符.如:
@PropertySource("classpath:/com/${my.placeholder:default/path}/app.properties"
以上假设my.placeholder
已被注册, 它会被解析为相应的值, 如果没有,则会使用default/path
作为默认值.如果二者都没有定义, 则会抛出IllegalArgumentException
异常.
在java8中,
@PropertySource
是可重复的,但是都必须在同一个级别声明.
1.13.4 语句中的占位符解析
只要占位符中的值在环境中可用, 无论它定义在哪里,都能够被解析.如
<beans>
<import resource="com/bank/service/${customer}-config.xml"/>
</beans>
// customer只要在环境中可用,无论它被定义在什么地方都能够被解析.
1.14 注册LoadTimeWeaver
Spring使用LoadTimeWeaver
在类被加载到JVM时动态转换它们.
@EnableLoadTimeWeaving
: 开启加载时织入功能.
@Configuration
@EnableLoadTimeWeaving
public class AppConfig {
}
对应的xml配置:
<beans>
<context:load-time-weaver/>
</beans>
一旦给spirng容器作了上述配置, 容器中的任何bean都可以实现 LoadTimeWeaverAware
, 因此可以获取到load-time weaver实例,这在使用JPA时是非常有用的.更多可查阅LocalContainerEntityManagerFactoryBean
的文档.
1.15 ApplicationContext附加功能
ApplicationContext
位于org.springframework.context
包,它扩展了BeanFactory
接口,提供了更多附加功能:
- 通过
MessageSource
接口,可以处理i18n
样式的消息. - 通过
ResourceLoader
接口, 可以访问网络及文件资源. - 通过
ApplicationListener
和ApplicationEventPublisher
接口, 实现事件发布. - 通过
HierarchicalBeanFactory
接口, 可以加载多个或多层次容器.
1.15.1 使用MessageSource国际化.
ApplicationContext
接口扩展了MessageSource
接口, 因此具备了国际化功能.Spring也提供了HierarchicalMessageSource
接口, 用于分层解析消息, 以上接口结合在一起便实现了国际化功能.
这此接口定义了以下方法:
-
String getMessage(String code, Object[] args, String default, Locale loc)
: 从MessageSource
中获取消息,如果指定的locale没有找到, 则使用默认值.传入的任何参数都将被标准库提供的MessageFormat
的相应的值替换. -
String getMessage(String code, Object[] args, Locale loc)
: 与前者一样,不同的是它没有提供默认址,如果没有找到相应的值, 会抛出NoSuchMessageException
. -
String getMessage(MessageSourceResolvable resolvable, Locale loc)
: 前面方法中的所有属性都将被封装在MessageSourceResolvalbe
类中.
当一个ApplicationContext
启动时,容器会自动搜索MessageSource
bean定义,bean的名称必须是messageSource
, 如果没有找到, 则会向上查找,如果也没有找到, 则会实例化一个空的DelegatingMessageSource
, 以便于能被前面定义的方法调用 .
spring提供了两个MessageSource
实现, 即ResourceBundleMessageSource
和StaticMessageSource
, 二者都实现了HierarchicalMessageSource
, 以便于能处理嵌套消息.StaticMessageSource
很少被使用, 但是它可以编程式的添加消息.
ResourceBundleMessageSource
使用如下:
<beans>
<bean id="messageSource"
class="org.springframework.context.support.ResourceBundleMessageSource">
<property name="basenames">
<list>
<value>format</value>
<value>exceptions</value>
<value>windows</value>
</list>
</property>
</bean>
</beans>
在上面的例子中,假设你定义了format, exceptions, windows
三个资源包. 则所有解析消息 的request都将通过资源包以JDK的标准方式解析消息.
对于上面的例子, 假设定义的两个资源包谁的如下:
# in format.properties
message=Alligators rock!
# in exceptions.properties
argument.required=The {0} argument is required.
则有:
public static void main(String[] args) {
// 所有的ApplicationContext都扩展了MessageSource接口.
MessageSource resources = new ClassPathXmlApplicationContext("beans.xml");
String message = resources.getMessage("message", null, "Default", null);
System.out.println(message); // Alligators rock!
}
接下来的示例将说明传递给消息的查找参数; 这些参数将转换为字符串并替换消息资源包中的占位符。
<beans>
<!-- this MessageSource is being used in a web application -->
<bean id="messageSource" class="org.springframework.context.support.ResourceBundleMessageSource">
<property name="basename" value="exceptions"/>
</bean>
<!-- 将上面的MessageSource注入到这个bean中-->
<bean id="example" class="com.foo.Example">
<property name="messages" ref="messageSource"/>
</bean>
</beans>
public class Example {
private MessageSource messages;
public void setMessages(MessageSource messages) {
this.messages = messages;
}
public void execute() {
String message = this.messages.getMessage("argument.required",
new Object [] {"userDao"}, "Required", null);
System.out.println(message); // The userDao argument is required.
}
}
关于国际化, springg的各种MessageSource实现遵循与标准JDK ResourceBundle相同的语言环境解析和回退规则, 即就前面的例子而言,如果你想解析英国地区(en-GB)语言环境的消息, 则你应该分别创建format_en_GB.properties, exception_en_GB.properties, windows_en_GB.properties
文件.
通常来说, 区域(Locale)的设置是由应用程序所处的环境管理的.
可以通过实现MesageSourceAware
接口来获取已被定义的MessageSource
的引用.
Spring还提供了一个ReloadableResourceBundleMessageSource
类, 相对于基于标准JDK实现的ResourceBundleMessageSource
类, 它更加灵活, 它可以从spring的任何资源路径读取资源包, 还支持资源包的热重载(资源包缓存).
1.15.2 标准事件和自定义事件
在ApplicationContext
中是通过ApplicationEvent
类和ApplicationListener
接口来处理事件的.如果某个bean实现了ApplicationListener
接口并被部署到容器中, 那么每次ApplicationEvent
被发布到容器中时,都会通过该bean.这是典型的观察者模式.
从spring4.2开始, 可以通过注解来实现发布任意事件, 即无需再继承ApplicationEvent
.
spring提供的标准事件:
Event | 说明 |
---|---|
ContextRefreshedEvent |
当容器被实例化或refreshed时发布.如调用configurableApplicationContext接口的refresh()方法, 此处的实例化是指所有的bean都已被加载,后置处理器都被激活,所有单例都已被实例化, 所有的容器对象都已准备好可使用. 如果容器支持热重载,则refresh可以被触发多次,如xmlWebApplicatonContext支持热刷新,而GenericApplicationContext则不支持 |
ContextStartedEvent |
当容器被启动时发布,即调用了ConfigurableApplicationContext的start()方法, 已启用意味着所有的Lifecycle bean都已显式接收到了start信号 |
ContextStoppedEvent |
当容器停止时发布,即调用了ConfigurableApplicationContext的stop()方法, 即所有的Lifecycle bean都已显式接收到了stop信号 , 关闭的容器可以通过start()方法重启 |
ContextClosedEvent |
当容器被关闭时发布,即调用 了ConfigrableApplicationContext的close方法, 关闭意味着所有的单例bean都已被销毁.关闭的容器不能被重启或refresh |
RequestHandledEvent |
这只在使用spring的DispatcherServlet时有效,当一个请求被处理完成时发布 |
自定义事件
扩展ApplicationEvent接口.
public class BlackListEvent extends ApplicationEvent {
private final String address;
private final String content;
public BlackListEvent(Object source, String address, String content) {
super(source);
this.address = address;
this.content = content;
}
}
通过调用ApplicationEventPublisher
的publishEvent()
方法发布自定义事件.通过实现ApplicationEventPublisherAware
接口获取ApplicationEventPublisher
实例.
public class EmailService implements ApplicationEventPublisherAware {
private List<String> blackList;
private ApplicationEventPublisher publisher;
public void setBlackList(List<String> blackList) {
this.blackList = blackList;
}
public void setApplicationEventPublisher(ApplicationEventPublisher publisher) {
this.publisher = publisher;
}
public void sendEmail(String address, String content) {
if (blackList.contains(address)) {
publisher.publishEvent(new BlackListEvent(this, address, content));
return;
}
// send email...
}
}
接收自定义事件, 要实现ApplicationListener接口并将其注册为bean.
public class BlackListNotifier implements ApplicationListener<BlackListEvent> {
private String notificationAddress;
public void setNotificationAddress(String notificationAddress) {
this.notificationAddress = notificationAddress;
}
public void onApplicationEvent(BlackListEvent event) {
// notify appropriate parties via notificationAddress...
}
}
注意: 通常使用自定事件类型作为ApplicationListener
接口的泛型参数, 这意味onApplicationEvent()
方法是类型安全的.默认事件的接收是同步的.即所有的publishEvent()
方法会在所有事件被处理完之前处于阻塞状态.这种同步单线程的优势是,当前一个listener侦听到一个事件时,如果存在事务上下文,事件将会在事务上下文中处理. 另一种事件发布机制参考ApplicationEventMulticaster
文档.
基于注解的事件监听
从spring 4.2开始, 可以通过@EventListener
注解来注册bean中的public方法.
public class BlackListNotifier {
private String notificationAddress;
public void setNotificationAddress(String notificationAddress) {
this.notificationAddress = notificationAddress;
}
@EventListener
public void processBlackListEvent(BlackListEvent event) {
// notify appropriate parties via notificationAddress...
}
}
在上面的示例中, 方法的参数指明了被监听的事件类型, 而且没有实现特定的listener
接口.
如果你不想定义参数或想监听多个事件,则可以通过@EventListener
注解本身指定事件类型.
@EventListener({ContextStartedEvent.class, ContextRefreshedEvent.class})
public void handleContextStart() {
...
}
也可以通过@EventListener
注解的condition
属性结合SpEL指定过滤条件.
@EventListener(condition = "#blEvent.content == 'foo'")
public void processBlackListEvent(BlackListEvent blEvent) {
// notify appropriate parties via notificationAddress...
}
事件Spring el表达式可用元数据
Name | Location | 说明 | 示例 |
---|---|---|---|
Event |
root |
实际的ApplicationEvent |
#root.event |
args[] |
root |
调用目标事件的参数 |
#root.args[0] |
参数名 |
容器计算 |
方法的参数名,如果参数不可知,则可用索引 |
#blEvent或#a0 |
注意: #root.event
允许你访问底层事件.
如果你想继续发布另一个事件,只需要将方法的返回值定义为你要发布的事件即可.若发布多个只需要返回一个事件集合即可.
@EventListener
public ListUpdateEvent handleBlackListEvent(BlackListEvent event) {
// notify appropriate parties via notificationAddress and
// then publish a ListUpdateEvent...
}
注:异步事件不支持此功能.
异步监听
使用@Async
注解
@EventListener
@Async
public void processBlackListEvent(BlackListEvent event) {
// BlackListEvent is processed in a separate thread
}
使用异步事件时有以下几点限制:
- 如果事件监听器抛出一个异常, 它将不会被传播给调用者,此时检查
AsyncUncaughtExceptionHandler
获取详细信息. - 无法通过返回一个事件来自动发布事件,此时只能注入
ApplicationEventPublisher
来手动发布事件.
定义listeners的顺序
使用@Order
接口
@EventListener
@Order(42)
public void processBlackListEvent(BlackListEvent event) {
// notify appropriate parties via notificationAddress...
}
泛型事件
可以使用泛型来进一步定义事件, 例如EntityCreatedEvent<T>
,
@EventListener
public void onPersonCreated(EntityCreatedEvent<Person> event) {
...
}
由于类型擦除, 只有在事件的泛型参数被解析后监听器才会工作.
1.15.3 访问底层资源
见Resource.
1.15.4 web应用实例化ApplicationContext
可以使用ContextLoader
实例化ApplicationContext
容器.当然,你也可以使用ApplicationContext
接口的实现类实例化容器.
使用ContextLoaderListener
注册容器
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>/WEB-INF/daoContext.xml /WEB-INF/applicationContext.xml</param-value>
</context-param>
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
这个listener会检查contextConfigLocation
参数, 如果没有定义, listener会默认使用`/WEB-INF/applicationContext.xml. 如果存在,listener会用逗号,分号,空格来拆分参数,使用拆分后的值作为资源路径.这个参数还支持Ant样式, 如/WEB-INF/Context.xml, 则会加载WEB-INF目录下的所有以Context结尾的xml. 又如/WEB-INF//Context.xml, 则会加载WEB-INF目录及其所有子目录下的以Context结尾的xml文件.
1.15.5 将spring ApplicationContext部署为java ee的RAR文件.
可以将spring ApplicationContext部署为rar文件, 相当于在JAVAEE环境中引导一个独立的ApplicationContext, 实际上, 这与没有任何HTTP入口的war文件中部署ApplicationContext类似.
rar文件部署非常适合于没有HTTP入口的应用程序(如任务调试程序).
可以查看SpringContextResourceAdapter类的javadoc,了解RAR部署中涉及的配置细节.
将Spring ApplicationContext部署为rar文件的简单方法: 将所有的应用class文件打包到一个rar文件中, 这是一个具有不同扩展名的标准jar文件. 再将其所有依赖的jar包放到rar存档的根目录.添加一个WEB-INF/ra.xml
部署描述(具体参见SpringContextResourceAdapter
文档)和相应的spring xml bean定义文件(如`WEB-INF/applicationContext.xml). 将生成的jar文件放到应用服务器的部署目录下.
1.16 BeanFactory
BeanFactory
的API提供了spring容器的最基本功能,它定义的方法主要用于与spring的其他模块或第三方框架集成, 它的DefaultListableBeanFactory
实现在高级别容器中是一个关键部分.
BeanFactory
及其相关接口(如BeanFactoryAware, InitializingBean, DisposableBean
)是与其他框架组件集成的重要集成点, 它们之间无需任何注解或反射,就能实现很好的交互.
注意: BeanFactory
的API及DefaultListableBeanFactory
实现都不会对配置格式,组件注解有任何定义.而这些都是通过如`XmlBeanDefinitionReader, AutowiredAnnotationBeanPostProcessor等扩展实现的.这就是spring容器如此灵活和可扩展的根本原因.
1.16.1 BeanFactory与ApplicationContext
ApplicationContext
包含了BeanFactory
的所有功能.
二者的区别:
特性 | BeanFactory | ApplicationContext |
---|---|---|
bean实例化 | Y | Y |
生命周期管理 | N | Y |
自动注册BeanPostProcessor
|
N | Y |
自动注册BeanFactoryPostProcessor
|
N | Y |
消息处理 | N | Y |
事件发布 | N | Y |
给DefaultListableBeanFactory
显式注册bean post-processor.
DefaultListableBeanFactory factory = new DefaultListableBeanFactory();
// 给factory填充bean定义
// 注册 BeanPostProcessor 实例
factory.addBeanPostProcessor(new AutowiredAnnotationBeanPostProcessor());
factory.addBeanPostProcessor(new MyBeanPostProcessor());
// 现在可以使用这个factory了
将BeanFactoryPostProcessor
应用到DefaultListableeBeanFactory
.
DefaultListableBeanFactory factory = new DefaultListableBeanFactory();
XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(factory);
reader.loadBeanDefinitions(new FileSystemResource("beans.xml"));
// 从一个属性文件中引入属性值
PropertyPlaceholderConfigurer cfg = new PropertyPlaceholderConfigurer();
cfg.setLocation(new FileSystemResource("jdbc.properties"));
// now actually do the replacement
cfg.postProcessBeanFactory(factory);
在上面的两个例子中,显式的注解步骤都是不方便的,这就是各种ApplicationContext变体实现优于DefaultListableBeanFactory的原因.
AnnotationConfigApplicationContext拥有所有开箱即用的公共注解后置处理器,它还可以通过配置注解(如@EnableTransactionManagement)引入额外的处理器.