Spring IOC之基于JAVA的配置
基础内容:@Bean 和 @Configuration
在Spring中新的支持java配置的核心组件是 @Configuration注解的类和@Bean注解的方法。
@Bean注解被用于表明一个方法实例配置和实例化一个能够被Spring Ioc容器管理的对象。和Spring 的基于XML配置的文件中的<beans/>中熟悉的一样,@Bean注解承担了和<bean/>元素相同的角色功能。你可以使用在任何Spring 的@Component中使用@Bean注解的方法,但是,他们大部分应用在@Configuration 的bean中。
使用@Configuration注解的类标明它是一个bean的原始定义。还有,@Configuration类允许通过调用在相同类中的其他的@Bean方法来定义内部Bean的依赖。最简单的@Configuration类如下所示:
@Configuration public classAppConfig { @Bean publicMyService myService() { return newMyServiceImpl(); } }
上面的AppConfig类和下面的Spring的XML中的配置一样:
<beans> <bean id="myService" class="com.acme.services.MyServiceImpl"/> </beans>
@Bean 和@Configuration注解将在下面的章节更加深层次被讨论。首先,我们将使用基于Java的注解来覆盖几种不同的创建Spring容器的方式。
完全的@Configuration VS 简化的 @Bean模式?
当@Bean方法被声明在那些没有使用@Configuration注解注解的类上面,他们就是表面使用了一个简化的模式。例如:在一个@Component中或者甚至在一个普通的POJO类中被声明bean方法将会被当成是简化的模式。和完全的@Configuration不同,简化的@Bean方法不能被简单声明内部bean的依赖。一般来讲一个@Bean方法不应该调用其他的@Bean 方法在以一个简化的模式中。只有在@Configuration 类中使用@Bean方法才可以被建议使用完全的模式。这样可以方法可以防止相同的@Bean方法被多次的调用,从而防止减少了很难追寻的潜在BUG。
1.使用AnnotationConfigApplicationContext来实例化Spring容器
通用的ApplicationContext实现不但能够接受@Congratulation类作为输入而且可以接受一般的@Component和在JSR-330元数据中注解标识的类。
当@Congratulation类被作为输入提供了,@Configuration类自己也被作为一个Bean定义被注册,而且所有的在类内部被声明的@Bean方法也是作为一个Bean定义被注册。
当@Component和JSR-330类被提供的时候,他们被作为Bean定义被注册,而且它假设例如@Autowired和@Inject的DI元数据在这些类内部被使用了。
1.1简单的说明
在实例化一个ClassPathApplicationContext的使用很多情况下都是使用Spring的XML文件作为输入的,在实例化一个AnnotationConfigApplicationContext的时候@Configuration类也是作为输入的。这样的设计考虑到了在Spring容器中的完全自由使用。
public static voidmain(String[] args) { ApplicationContext ctx = newAnnotationConfigApplicationContext(AppConfig.class); MyService myService = ctx.getBean(MyService.class); myService.doStuff(); }
正如上面提到的那样,AnnotationCOnfigApplicaitonContext并没有只是限制在@Congratulation类中使用。任何@Component或者JSR-330注解类都可以作为整个构造函数的输入。例如:
public static voidmain(String[] args) { ApplicationContext ctx = newAnnotationConfigApplicationContext(MyServiceImpl.class, Dependency1.class, Dependency2.class); MyService myService = ctx.getBean(MyService.class); myService.doStuff(); }
上面假设MyServiceImpl, Dependency1和 Dependency2使用了例如@Autowired的Spring依赖注入注解。\
1.2使用注册器(class<?>)来自动构建容器
一个AnnotationConfigApplicationContext可以使用一个无参的构造函数来实例化然后使用register()方法来配置。这种途径在由程序自动构建一个AnnotationConfigApplicationContext的时候非常有用。
public static voidmain(String[] args) { AnnotationConfigApplicationContext ctx = newAnnotationConfigApplicationContext(); ctx.register(AppConfig.class, OtherConfig.class); ctx.register(AdditionalConfig.class); ctx.refresh(); MyService myService = ctx.getBean(MyService.class); myService.doStuff(); }
1.3使用scan(Stirng..)来使组件进行扫描
有经验的Spring用户对下面的Spring的context:namespace 的XML配置会比较熟悉:
<beans> <context:component-scan base-package="com.acme"/> </beans>
在上面的例子中,com.acme包将会被扫描来查找任何@Component-注解的类,而且这些类在容器内部将会作为Spring的Bean定义被注册到容器中。AnnotationConfigApplicationContext提供的scan(Stirng..)方法也考虑懂啊了相同的组件扫描功能:
public static voidmain(String[] args) { AnnotationConfigApplicationContext ctx = newAnnotationConfigApplicationContext(); ctx.scan("com.acme"); ctx.refresh(); MyService myService = ctx.getBean(MyService.class); }
注意:记住@Configuration类使用 @Component的元数据注解,所以他们是候选要被组件扫描的。在上面的例子中,假设AppConfig在com.acme包中被声明了。它在调用scan()和refresh()方法的时候被获取到,然后注册到容器中。
1.4AnnotationConfigWebApplicationContext支持web 引用
AnnotationConfigApplicationContext的变体WebApplicationContext可以使用AnnotationConfigWebApplicationContext的相关功能。这种实现能够被使用在配置Spring的ContextLoaderListener 的Servelet 监听器、Spring MVC的DispatcherServlet等等。下面就是一个web.xml的片段,它配置了一个标准的Spring MVC web引用。注意contextClass 上下文参数和初始化参数的使用:
<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</listenerclass> </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>
2使用@Bean注解
@Bean注解是一个方法级别的注解,而且相对于的是XML中的<bean/>元素,这个注解支持了<bean/>元素的一些属性: init-method, destroy-method, autowiring和name。 你可以在一个@Configuration注解的或者的Component-注解的类使用@Bean注解。
2.1声明一个Bean
为了声明一个bean可以使用@Bean注解来简单的注解一个方法,你可以使用这个方法在一个ApplicationContext内部返回值上指明来注册一个bean的定义。默认情况下,bean的名字和方法名字相同,下面就是是一个简单@Bean 方法的声明:
@Configuration public classAppConfig { @Bean publicTransferService transferService() { return newTransferServiceImpl(); } }
前面的配置和下面的Spring的XML的配置完全等价:
<beans> <bean id="transferService" class="com.acme.TransferServiceImpl"/> </beans>
上面的两种声明都可以使名字为transferService绑定了TransferServiceImpl类型实例的的bean在ApplicationContext中可用:
transferService -> com.acme.TransferServiceImpl
2.2接受声明周期回调
任何被@Bean注解定义的类支持一般的声明周期回调而热情能够使用JSR-250中的@PostConstruct 和@PreDestory注解。
常规的Spring生命周期回调也被很好的支持。如果一个bean实现了InitializingBean, DisposableBean, 或者 Lifecycle,他们相应的方法就会被容器所调用。
标准的*Aware接口集比如BeanFactoryAware, BeanNameAware,
MessageSourceAware, ApplicationContextAware也被很好的支持。
@Bean注解支持指明任何的初始化和销毁回调方法,很像Spring XML的int-method和destory-method属性:
public classFoo { public voidinit() { // initialization logic } } public classBar { public voidcleanup() { // destruction logic } } @Configuration public classAppConfig { @Bean(initMethod = "init") publicFoo foo() { return newFoo(); } @Bean(destroyMethod = "cleanup") publicBar bar() { return newBar(); } }
当然了,在上面的Foo例子中,它也等价于在构造方法中直接调用 init()方法:
@Configuration public classAppConfig { @Bean publicFoo foo() { Foo foo = newFoo(); foo.init(); returnfoo; } // ... }
建议:当你直接在Java中使用时候,你可以随你所好的处理你的对象而不用依赖容器的声明周期。
2.3明确Bean作用域
使用@Scoped注解
你应该明确用@Bean注解定义的bean应该有一个明确的作用域。你可以使用任何标准的作用域。
默认的作用域是singleton,但是你可以覆盖这个用@Scope注解:
@Configuration public classMyConfiguration { @Bean @Scope("prototype") publicEncryptor encryptor() { // ... } }
@Scope和作用域代理
Spring通过作用域代理提供了一种可用的方法来处理作用域依赖。在使用XML配置的时候最简单的创建一个代理的方法就是 <aop:scoped-proxy/>元素。在Java中使用@Scope注解来配置你的bean的时候使用proxyMode属性提供了等价的支持。默认情况下是没有代理的(ScopedProxyMode.NO),但是你可以指明ScopedProxyMode.TARGET_CLASS或者ScopedProxyMode.INTERFACES。
如果你把XML引用文档中的代理的例子应用到我们的@Bean中,它会是下面的这个样子:
// an HTTP Session-scoped bean exposed as a proxy @Bean @Scope(value = "session", proxyMode = ScopedProxyMode.TARGET_CLASS) publicUserPreferences userPreferences() { return newUserPreferences(); } @Bean publicService userService() { UserService service = newSimpleUserService(); // a reference to the proxied userPreferences bean service.setUserPreferences(userPreferences()); returnservice; }
2.4自定义bean命名
默认情况下,使用@Bean方法的名字作为作用的Bean的名字。但是这种功能能够通过name属性来被覆盖。
@Configuration public classAppConfig { @Bean(name = "myFoo") publicFoo foo() { return newFoo(); } }
2.5Bean 别名
有时候有需求需要给一个bean多个名字,也就是我们所说的别名。因为这个目的,@Bean注解的 name属性接受了String的字符串数组:
@Configuration public classAppConfig { @Bean(name = { "dataSource", "subsystemA-dataSource", "subsystemB-dataSource" }) publicDataSource dataSource() { // instantiate, configure and return DataSource bean... } }
2.6Bean描述
有时候提供一个更加详细的Bean的描述是非常有用的。特别是当bean是用于监控的目的时是非常有用的。为了给@Bean添加描述信息,@Description注解是需要的:
@Configuration public classAppConfig { @Bean @Desciption("Provides a basic example of a bean") publicFoo foo() { return newFoo(); } }
3.使用@Configuratioin注解
@Configuration是一备案个类级别的注解,它标识一个对象是一个Bean定义的来源。@Configuration类通过public 的@Bean注解的方法来声明bean.调用@Congratulation类中的@Bean方法也能够定义内部bean的依赖。
3.1注入内部Bean依赖
当@BeanS有在其他上面的依赖时,标识这个依赖只需要简单通过Bean的方法来调用其他的即可:
@Configuration public classAppConfig { @Bean publicFoo foo() { return newFoo(bar()); } @Bean publicBar bar() { return newBar(); } }
在上面的例子中,foo通过构造函数注入来持有对bar的引用。
注意:只有当@Bean方法声明在@Configuration类中才能够让方法中声明的内部bean依赖才会起作用。你不能通过普通的@Component类来声明内部bean的依赖。
3.2查看方法注入
正如早前提到的一样,查看方法注入是一个你很少使用的高级功能。这个在当一个单例的bean有一个原型作用域的Bean的依赖的时候的场景下是很有用的。在这种类型的配置中使用Java提供了一个实现这种模式的自认的途径。
public abstract classCommandManager { publicObject 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); returncommand.execute(); } // okay... but where is the implementation of this method? protected abstractCommand createCommand(); }
使用Java配置的支持,你可以创建一个CommandManager的子类:
@Bean @Scope("prototype") publicAsyncCommand asyncCommand() { AsyncCommand command = newAsyncCommand(); // inject dependencies here as required returncommand; } @Bean publicCommandManager commandManager() { // return new anonymous implementation of CommandManager with command() overridden // to return a new prototype Command object return newCommandManager() { protectedCommand createCommand() { returnasyncCommand(); } } }
3.3更多的信息关于基于Java配置的内部工作机制
下面的例子标明一个@Bean注解方法被调用两次的情况:
@Configuration public classAppConfig { @Bean publicClientService clientService1() { ClientServiceImpl clientService = newClientServiceImpl(); clientService.setClientDao(clientDao()); returnclientService; } @Bean publicClientService clientService2() { ClientServiceImpl clientService = newClientServiceImpl(); clientService.setClientDao(clientDao()); returnclientService; } @Bean publicClientDao clientDao() { return newClientDaoImpl(); } }
clientDao()在clientService1()中被调用一次在clientService2()中也被调用了一次。因为这个方法创建了一个 ClientDaoImpl实例并且返回了它。你将希望可以有两个实例。这个明显会出现问题的:在Spring中,实例化的bean在默认情况下有一个单例的作用域。这就是神奇的地方所在:所有的@Configuration类在启动的时候通过CGLIB子类化了。在子类中,子方法在它调用父类的方法和创建一个新的实例的时候会首先检查容器查看是否有缓存的bean。
注意:根据你的bean的作用域的不同,他们的表现也可能有差异。我们在这里只是讨论了单例。因为CGLIB的缘故所以在启动的时候动态的添加功能会有些限制:配置的类不能是final;必须要有一个无参的构造函数。
4组合基于Java的配置
4.1使用@Import注解
和大部分在Spring XML文件中为了添加模块化配置的需要而使用的<import/>元素一样,@Import注解会将@Bean定义从其他配置类中获取到:
@Configuration public classConfigA { @Bean publicA a() { return newA(); } } @Configuration @Import(ConfigA.class) public classConfigB { @Bean publicB b() { return newB(); } }
现在在实例化上下文的时候,只需要ConfigB被显示的指出,而不是同时指明ConfigA.class和ConfigB.class。
public static voidmain(String[] args) { ApplicationContext ctx = newAnnotationConfigApplicationContext(ConfigB.class); // now both beans A and B will be available... A a = ctx.getBean(A.class); B b = ctx.getBean(B.class); }
这种途径简化了容器的实例化,因为只需要处理一个类,而不是要求开发者记住大量的@Configuration类。
4.2在引进的@Bean定义中注入依赖
上面的例子是可以起作用的,但是它太简化了。在大部分的场景中,Bean会通过配置的类拥有对其他的依赖。当使用XML的时候,这不是问题,因为不牵涉编译器而且一个bean只需要声明ref="someBean" 而且确信Spring将会在容器的实例化的时候起作用。当然了,当使用@Congratulation类的时候,Java编译器把约束放到了配置模型中,对其他bean的引用必须合乎Java语法。
幸运的是,解决这个问题比较容易。需要记住 @Configuration类没有限制在容器中的其他bean-这个方式可以利用@Autowired注解。
让我考虑下一个含有几个@Configuration类的更加真实的场景,每个都是依赖声明在其他bean中
@Configuration public classServiceConfig { @Autowired privateAccountRepository accountRepository; @Bean publicTransferService transferService() { return newTransferServiceImpl(accountRepository); } } @Configuration public classRepositoryConfig { @Autowired privateDataSource dataSource; @Bean publicAccountRepository accountRepository() { return newJdbcAccountRepository(dataSource); } } @Configuration @Import({ServiceConfig.class, RepositoryConfig.class}) public classSystemTestConfig { @Bean publicDataSource dataSource() { // return new DataSource } } public static voidmain(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"); }
在上面的场景中,使用@Autowired注解可以正常工作而且提供了需要的模块化,但是决定自动装配的bean的定义的地方还是多少有些含糊不清。例如,当一个开发者寻找ServiceConfig的时候,你怎么明确的知道 @Autowired AccountRepository 的bean在哪里声明的。这个在代码里面是不明确的,而且可能刚刚好。因为SpringSource Tool Suit提供了可以查看被分装的图形化工具--这个刚好是你所需要的。所以,你的IDE能够很容易的找到所有的声明,并且使用AccountRepository类型,而且可以快速的向你展示@Bean方法的位置和反馈会那个类型。
在哪些模糊不清不能接受的地方,如果你希望有一个直观视图,考虑自动装配哪些配置的 类:
@Configuration public classServiceConfig { @Autowired privateRepositoryConfig repositoryConfig; @Bean publicTransferService transferService() { // navigate throughthe config classto the @Beanmethod! return newTransferServiceImpl(repositoryConfig.accountRepository()); } }
在上面的情况下,它完全明确的指明了AccountRepository定义的地方。但是ServiceConfig和RepositoryConfig耦合在一起,这就是折衷的做法。紧耦合可以通过使用基于接口或者基于抽象类的方式来进行缓解。如下所示:
@Configuration public classServiceConfig { @Autowired privateRepositoryConfig repositoryConfig; @Bean publicTransferService transferService() { return newTransferServiceImpl(repositoryConfig.accountRepository()); } } @Configuration public interfaceRepositoryConfig { @Bean AccountRepository accountRepository(); } @Configuration public classDefaultRepositoryConfig implementsRepositoryConfig { @Bean publicAccountRepository accountRepository() { return newJdbcAccountRepository(...); } } @Configuration @Import({ServiceConfig.class, DefaultRepositoryConfig.class}) // import the concrete config! public classSystemTestConfig { @Bean publicDataSource dataSource() { // return DataSource } } public static voidmain(String[] args) { ApplicationContext ctx = new AnnotationConfigApplicationContext(SystemTestConfig.class); TransferService transferService = ctx.getBean(TransferService.class); transferService.transfer(100.00, "A123", "C456"); }
现在ServiceConfigis相对于具体的DefaultRepositoryConfig是松耦合了,而且内置的IDE仍然是有用的:对于开发者仍然可以获取RepositoryConfig实现继承的结构。通过这种方式,视图化 @Configuration类和他们的依赖和一般的基于接口的代码就没有差异了。
4.3有条件的使用@Configuration类或者 @Beans
基于一些随意的系统状态有条件的不适用完整的@Congratulation类或者个别的@Bean方法是非常有益的。常见的例子就是只有一个明确的profile在一个Spring的Environment中生效的时候才会使用@Profile注解去激活beans。@Profile注解通常采用一个更加灵活的叫做@Confitional的注解来实现的。@Conditional注解标识明确的org.springframework.context.annotation.Condition实现应该在一个@Bean注入前起作用的。
Condition接口的实现一般会提供一个返回true或者false的matches()方法。例如,下面就是一个使用@Profile的事假的Condition的实现:
@Override public booleanmatches(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))) { returntrue; } } returnfalse; } } returntrue; }
4.4将JAVA和XML配置联合起来
Spring的@Configuration类的支持不是为了100%的完全取代Spring的 XML配置。一些内容像Spring的XML命名空间任务被保留下来来配置容器。在XML方便和必要的时候你可以选择:既可以使用一XML为中心的实例化容器也可以以JAVA为中心的使用 AnnotationConfigApplicationContex和 @ImportResource注解来引入XML文件。
4.4.1以XML为中心的使用@Configuration 类
从XML文件和包含@Configuration类中来启动Spring容器可能更受欢迎。例如,在一个大型的存在使用Spring XML的基代码中,创建@Configuration类并且把他们从存在的XML文件中包含变得更加容易。接下来你会可以选择在这种以XML文件为重的情形中使用@Configuration类。
记住@Configuration类在容器中不仅仅是bean的定义。在这个例子 中,我们创建了伊特名字为AppConfig的@Configuration类并且把它作为<bean/>定义包含在system-test-config.xml文件中。因为<context:annotation-config/>是开启的,容器会识别@Configuration注解并且把@Bean方法引用在AppConfig中。
@Configuration public classAppConfig { @Autowired privateDataSource dataSource; @Bean publicAccountRepository accountRepository() { return newJdbcAccountRepository(dataSource); } @Bean publicTransferService transferService() { return newTransferService(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 voidmain(String[] args) { ApplicationContext ctx = newClassPathXmlApplicationContext("classpath:/com/acme/ system-test-config.xml"); TransferService transferService = ctx.getBean(TransferService.class); // ... }
注意:在上面的sytem-test-config.xml中,AppConfig的<bean/>没有声明一个id元素。尽管它可以接受这样的做法但是考虑到没有其他的bean引用它所有是没有必要的 ,而且它可以通过名字在容器中获取。同样的和DataSoure 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>
4.4.2以Configuration类为中心的通过 @ImportResource使用XML
在应用中@Configuration类作为首选的机制用来配置容器的地方,它仍然需要一些XML文件。在这些场景中,简单的使用@ImportResource 并且定义需要XML的地方。这样做可以让一个以JAVA为中心的方案中类通过XML来配置容器:
@Configuration @ImportResource("classpath:/com/acme/properties-config.xml") public classAppConfig { @Value("${jdbc.url}") privateString url; @Value("${jdbc.username}") privateString username; @Value("${jdbc.password}") privateString password; @Bean publicDataSource dataSource() { return newDriverManagerDataSource(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 voidmain(String[] args) { ApplicationContext ctx = newAnnotationConfigApplicationContext(AppConfig.class); TransferService transferService = ctx.getBean(TransferService.class); // ... }