[TOC]
Spring配置方案
(不僅僅是Ioc的配置)
從前文容器的具體實現(xiàn)已經(jīng)知道,配置容器或者說配置應(yīng)用上下文有多種方式,如Java類,來自文件系統(tǒng)或來自classpath的的xml文件,還有Spring基于注解的自動配置(依賴前兩種開啟自動掃描)。
總結(jié)下來,對于Spring最核心和最基礎(chǔ)的自動裝配功能(集成依賴注入),主要有以下三種裝配機(jī)制:
- 在XML中顯式配置
- 在java類中顯式配置
- 隱式bean發(fā)現(xiàn)機(jī)制(自動掃描裝配)
三種方式并非互斥,甚至三者可以共同使用。
以下記錄三種方式配置IOC容器。
自動化裝配
自動化裝配的思路是:
- 指明要被容器管理的bean
- bean之間的依賴關(guān)系
定義Bean的方式和定義bean之間的依賴注入都有兩種方式:基于xml和基于注解。也就是說可以使用xml或注解來定義bean,可以使用xml或注解來定義bean之間的依賴注入關(guān)系。這兩個是分開的,你可以使用xml定義所有的bean,然后使用注解的形式來注入依賴(需要開啟注解配置),也可以使用注解來定義bean、然后使用注解來定義依賴注入。(沒有使用注解定義bean再使用xml定義依賴注入這種方式);
定義bean的方式是在Spring讀取配置元數(shù)據(jù)這個時期需要處理的事情,所以和這個過程相關(guān)的是取得BeanDefinition的過程,是和BeanFactoryPostProcessor相關(guān)的;而注入依賴是BeanPostProcessor做的事情,BeanPostProcessor是在bean實例化之后介入bean的生命周期,為對象裝配屬性;構(gòu)造器注入的bean在實例化的時候調(diào)用的是有參構(gòu)造器,構(gòu)造器參數(shù)肯定在此前實例化完成,所以這步依賴的注入在實例化bean時完成。
使用xml定義bean方式即為在xml中使用<bean>標(biāo)簽來配置,在xml中定義注入關(guān)系或者開啟注解使用注解來配置依賴關(guān)系,開啟注解的方法是在xml中添加<context:annotation-config/>,然后使用如@Autowired,@PostConstruct,@PreDestroy,@Inject,@Named,@Required來配置注入;
使用注解的形式定義bean實際上分為兩種,一種是集中式的定義在Java類中,另一種是分散在各個包中的bean類定義上,前者就是后面要說的基于java類的配置,后者是基于Spring發(fā)現(xiàn)機(jī)制的隱式配置。前者在JavaConfig類中使用@Bean來定義bean,然后使用前述的注解配置依賴關(guān)系;后者則是有一系列的注解來定義bean,且這些注解是在具體要定義成bean的類中加的,如@Component、@Service、@Controller、@Repository,聲明此bean交由容器管理和裝配屬性,另外給需要由容器注入的屬性加上類似@Autowired、@Resource、@Value的注解,指明依賴關(guān)系。
IOC常用注解
@Required
這個注釋表明,bean屬性必須在配置時填充,通過bean定義中的顯式屬性值或通過自動裝配。
@Component
聲明此class為一個Spring組件,在class上使用,可以在注解后面指定beanID,@Component是聲明為Spring組件最基礎(chǔ)的注解,在其基礎(chǔ)上還有@Controller、@Service、@Repository等語義更加細(xì)粒的注解形式。
Spring提供了進(jìn)一步的模板注釋:@Component, @Service和@Controller。@Component是任何spring管理組件的通用原型。@Repository、@Service和@Controller分別是針對更具體的用例(例如,在持久性、服務(wù)和表示層)的@Component的專門化。因此,您可以使用@Component對組件類進(jìn)行注釋,但是通過使用@Repository、@Service或@Controller對它們進(jìn)行注釋,您的類更適合通過工具進(jìn)行處理或與方面關(guān)聯(lián)。例如,這些模板注釋是切入點的理想目標(biāo)。在Spring框架的未來版本中,@Repository、@Service和@Controller也可能包含額外的語義。因此,如果您正在為您的服務(wù)層選擇使用@Component還是使用@Service,那么@Service顯然是更好的選擇。類似地,如上所述,@Repository已經(jīng)被支持作為持久性層中自動異常轉(zhuǎn)換的標(biāo)記。
- Meta-annotations
例如@Component是@Service的元注解,也就是這種更細(xì)粒度的注解也是基于基礎(chǔ)注解的。
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Component // Spring will see this and treat @Service in the same way as @Component
public @interface Service {
// ....
}
Meta-annotations can also be combined to create composed annotations. For example, the @RestController annotation from Spring MVC is composed of @Controller and @ResponseBody.
@Autowired
聲明需要注入一個屬性 | 在屬性,構(gòu)造器,setter上使用
As of Spring Framework 4.3, an @Autowired annotation on a constructor is no longer necessary if the target bean only defines one constructor to begin with. However, if several constructors are available, at least one must be annotated to teach the container which one to use.
The required attribute of @Autowired is recommended over the @Required annotation.
另外Spring支持Java依賴注入規(guī)范(Java Dependency Injection)提供的注解來替代Spring的注解,如使用@Named替換@Component,使用@Inject替代@Autowired.
@Qualifier
限定beanID,和@Autowired組合使用限定注入的BeanID,也可以和@Component組合使用,設(shè)置當(dāng)前BeanID
@Resource
和Autowired相似,@Autowired按byType自動注入,而@Resource默認(rèn)按byName自動注入。@Resource有兩個屬性是比較重要的,分別是name和type,Spring將@Resource注解的name屬性解析為bean的名字,而type屬性則解析為bean的類型。
@Primary
@Primary表示當(dāng)多個bean可以自動裝配到單值依賴項時,應(yīng)該優(yōu)先選擇特定的bean。如果候選者中只存在一個“主”bean,則它將是自動裝配的值。此注解用來標(biāo)明bean是primary,一般和@Bean結(jié)合使用,還可以在xml中bean定義中primary="true"標(biāo)明此bean是primary。使用@Qualifier("main")擁有更細(xì)的粒度;
@PostConstruct和@PreDestroy
這兩個是lifecycle annotations,對應(yīng)initialization callbacks和destruction callbacks。在Spring 2.5中引入,對這些注釋的支持提供了初始化回調(diào)和銷毀回調(diào)中描述的另一種替代方法。 如果CommonAnnotationBeanPostProcessor在Spring ApplicationContext中注冊,則在生命周期的同一點調(diào)用承載這些注釋之一的方法,作為相應(yīng)的Spring生命周期接口方法或顯式聲明的回調(diào)方法。
@Lazy
可以放在bean定義一起來延遲創(chuàng)建bean,也可以和@Autowired或@Inject一起使用來延遲注入依賴
配置注入關(guān)系的注解的使用范圍
@Autowired, @Inject, @Resource, and @Value annotations are handled by Spring BeanPostProcessor implementations which in turn means that you cannot apply these annotations within your own BeanPostProcessor or BeanFactoryPostProcessor types (if any). These types must be 'wired up' explicitly via XML or using a Spring @Bean method.
1.基于XML文件配置自動裝配方式
- 配置思路
使用xml配置聲明bean,在xml中配置DI或使用注解配置DI
XML定義bean和DI
- 示例
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context"
xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-3.1.xsd
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.0.xsd">
<!-- 配置一個bean -->
<!--如果沒有指明ID,則默認(rèn)ID為“com.springinaction.ioc.BraveKnight#0”-->
<bean id="knight" class="com.springinaction.ioc.BraveKnight">
<!--給構(gòu)造函數(shù)傳遞參數(shù),沒有的話則調(diào)用默認(rèn)構(gòu)造方法 -->
<constructor-arg ref="quest" />
</bean>
<bean id="quest" class="com.springinaction.ioc.SlayDragonQuest">
<constructor-arg value="#{T(System).out}" />
</bean>
<bean id="minstrel" class="com.springinaction.aop.Minstrel">
<constructor-arg value="#{T(System).out}" />
</bean>
</beans>
- 啟動容器測試
ApplicationContext context = new ClassPathXmlApplicationContext(
"classpath:spring.xml");
// 也可選擇FileSystemXmlApplicationContext類來定義上下文啟動容器
BraveKnight knight = context.getBean(BraveKnight.class);
knight.fight();
需要注意的是,Spring在遇到<bean>創(chuàng)建bean時,調(diào)用的是默認(rèn)構(gòu)造器(如果沒有定義構(gòu)造器注入的話);
XML定義bean,注解配置DI
- 示例
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="
http://www.springframework.org/schema/context"
xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop-3.1.xsd
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-3.0.xsd">
<!-- 配置一個bean -->
<bean id="testBean" class="com.springinaction.ioc.beaninject.TestBean"></bean>
<!-- 依賴TestBean,但是依賴關(guān)系定義在類的注解中 -->
<bean id="testBean2" class="com.springinaction.ioc.beaninject.TestBean2"></bean>
<!-- 啟用注解支持 -->
<context:annotation-config />
</beans>
- java類中使用注解配置DI
public class TestBean2 {
public TestBean2() {
System.out.println("TestBean2 default constructor");
}
private TestBean t;
// 配置DI
@Autowired
public TestBean2(TestBean t) {
System.out.println("TestBean2 set t in constructor");
this.t = t;
}
// ...
}
- 啟動容器測試
@Test
public void testDefineBeanInXMLButDIbyAnnotation() {
context = new ClassPathXmlApplicationContext(
"classpath:com/springinaction/ioc/beaninject/spring.xml");
TestBean2 t2 = context.getBean(TestBean2.class);
System.out.println(t2.getT());
}
2.基于Java類配置自動裝配方式
Java類中定義bean,類中配置DI。依賴注解@Bean和@Configuration。
@Bean和@Configuration
@Bean和@Configuration是基于Java配置容器的核心組件。@Bean用來注解方法,用來指明一個實例化、配置和初始化一個由Spring容器管理的新對象的方法。@Bean的語義和xml中<bean>的作用類似。@Bean可以和@Component一起使用,但是一般是和@Configuration一起使用。
@Target({ElementType.METHOD, ElementType.ANNOTATION_TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Bean {
/**
* The name of this bean, or if plural, aliases for this bean. If left unspecified
* the name of the bean is the name of the annotated method. If specified, the method
* name is ignored.
*/
String[] name() default {};
/**
* Are dependencies to be injected via convention-based autowiring by name or type?
*/
Autowire autowire() default Autowire.NO;
String initMethod() default "";
String destroyMethod() default AbstractBeanDefinition.INFER_METHOD;
}
@Bean的使用
@Configuration
public class AppConfig {
@Bean(name = "myFoo")
@Bean(initMethod = "init")
public Foo foo() {
return new Foo();
}
@Bean(destroyMethod = "cleanup") //bean默認(rèn)會調(diào)用類的公共close或shutdown方法作為destoryMethod,如果不需要可以使用 @Bean(destroyMethod="")
@Bean(name = { "dataSource", "subsystemA-dataSource", "subsystemB-dataSource" }) // alias
@Description("Provides a basic example of a bean")
public Bar bar() {
return new Bar();
}
// 定義scope
@Bean
@Scope("prototype")
public Encryptor encryptor() {
// ...
}
// scoped-proxy
// an HTTP Session-scoped bean exposed as a proxy
@Bean
@SessionScope
public UserPreferences userPreferences() {
return new UserPreferences();
}
}
@Configuretion的使用
@Configuretion注解用來注解一個class,表明它是一個bean定義的配置類。此外,@Configuration類允許通過簡單地調(diào)用同一類中的其他@Bean方法來定義bean間的依賴關(guān)系。
This method of declaring inter-bean dependencies only works when the @Bean method is declared within a @Configuration class. You cannot declare inter-bean dependencies using plain @Component classes.
All @Configuration classes are subclassed at startup-time with CGLIB.In the subclass, the child method checks the container first for any cached (scoped) beans before it calls the parent method and creates a new instance.
There is another way to achieve the same result. Remember that @Configuration classes are ultimately just another bean in the container: This means that they can take advantage of @Autowired and @Value injection etc just like any other bean!
@Bean的lite模式
@Bean注解的方法所在的類沒有使用@Configuration注解的情況,就是lite模式。這種模式下,不能在類中定義DI關(guān)系(直接調(diào)用其他的@Bean方法不是使用的Spring容器注入的bean,而是新創(chuàng)建的對象),此場景下@Bean方法定義的是bean的工廠方法。(有一種prototype的語義)
常規(guī)Spring組件中的@Bean方法與Spring @Configuration類中的對應(yīng)方法處理方式不同。不同之處在于,并沒有使用CGLIB增強(qiáng)@Component類來攔截方法和字段的調(diào)用。CGLIB代理是在@Configuration類中的@Bean方法中調(diào)用方法或字段的方法,它為協(xié)作對象創(chuàng)建bean元數(shù)據(jù)引用;這些方法不是使用普通的Java語義來調(diào)用的,而是通過容器來提供Spring bean通常的生命周期管理和代理,即使是通過對@Bean方法的編程調(diào)用來引用其他bean時也是如此。相反,在普通的@Component類中調(diào)用@Bean方法中的方法或字段具有標(biāo)準(zhǔn)的Java語義,不應(yīng)用特殊的CGLIB處理或其他約束。
注意,對靜態(tài)@Bean方法的調(diào)用永遠(yuǎn)不會被容器截獲,甚至在@Configuration類中也不會被攔截(參見上面的內(nèi)容)。這是由于技術(shù)限制:CGLIB子類化只能覆蓋非靜態(tài)方法。因此,直接調(diào)用另一個@Bean方法將具有標(biāo)準(zhǔn)的Java語義,從而直接從factory方法本身返回一個獨立的實例。
最后,請注意,單個類可以為同一個bean保存多個@Bean方法,作為根據(jù)運行時可用依賴項使用的多個工廠方法的安排。這與在其他配置場景中選擇“greediest”構(gòu)造函數(shù)或工廠方法的算法是相同的:在構(gòu)建時將選擇具有最大數(shù)量可滿足依賴性的變體,類似于容器在多個@Autowired構(gòu)造
public class SpringBeanInjectConfig4TestBean {
@Bean
public TestBean testBean() {
return new TestBean();
}
@Bean
public TestBean2 testBean2() {
return new TestBean2(testBean());
}
}
@Test
public void testJavaBasedConfig() {
context = new AnnotationConfigApplicationContext(SpringBeanInjectConfig4TestBean.class);
TestBean2 t2 = context.getBean(TestBean2.class);
TestBean t = context.getBean(TestBean.class);
org.junit.Assert.assertEquals(t, t2.getT()); // error
}
配置示例
- 創(chuàng)建配置類
- 使用注解@Bean聲明bean
- 借助JavaConfig注入
- config類
@Configuration
public class SpringBeanInjectConfig4TestBean {
@Bean
public TestBean testBean() {
return new TestBean();
}
@Bean
public TestBean2 testBean2() {
return new TestBean2(testBean());
}
}
- bean
public class TestBean2 {
private TestBean t;
public TestBean2(TestBean t) {
System.out.println("TestBean2 set t in constructor");
this.t = t;
}
// ...
}
- 啟動容器
@Test
public void testJavaBasedConfig() {
context = new AnnotationConfigApplicationContext(
SpringBeanInjectConfig4TestBean.class);
TestBean2 t2 = context.getBean(TestBean2.class);
System.out.println(t2.getT());
}
還可以:
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext();
ctx.register(AppConfig.class, OtherConfig.class);
ctx.register(AdditionalConfig.class);
ctx.refresh();
@Bean注解聲明此方法返回類型對應(yīng)的bean注冊為Spring Bean,且默認(rèn)方法名為beanID。Spring會自動調(diào)用@Bean注解的方法中的方法創(chuàng)建對象和注入依賴。這個方式更像是手動注入依賴和創(chuàng)建對象,因為需要手動去寫new,以及傳入屬性。
@Bean is a method-level annotation and a direct analog of the XML <bean/> element. @Bean帶注釋的方法可以有任意數(shù)量的參數(shù)來描述構(gòu)建該bean所需的依賴關(guān)系。
@Bean注解的方法是作為bean的factory-method來創(chuàng)建bean的,類似在xml中定義factory-method。使用此方式的bean不能在配置類的外部使用@Autowired等配置DI。只能在config類中配置,配置方式有兩種:一是直接在@Bean注解的方法內(nèi)調(diào)用其他@Bean的方法,或者使用方法參數(shù)來注入:
@Bean
public TestBean2 testBean2(TestBean t) {
return new TestBean2(t);
}
3.基于隱式bean發(fā)現(xiàn)機(jī)制的自動裝配方式
使用此方式不需要使用xml或java類明確集中式地定義bean,而是使用注解聲明一個class是bean,然后通過Spring的自動掃描機(jī)制將其裝入容器中,此方式下的DI也通過注解的形式配置。
此方式基于注解和Spring detect機(jī)制;需要借助前兩種方式來開啟包掃描功能才能使用注解來讓Spring發(fā)現(xiàn),此方式相較前兩種的集中式配置方案,它的配置是分散在各個類中的。
“Annotation injection is performed before XML injection, thus the latter configuration will override the former for properties wired through both approaches.”
Excerpt From: Rod Johnson. “Spring Framework Reference Documentation.” iBooks.
bean聲明
- 使用Spring 模板注解(@Component, @Repository, @Service, and @Controller)是,聲明一個bean
- bean附加屬性如name,scope
@Scope("prototype")
@Repository("myrepos")
public class MovieFinderImpl implements MovieFinder {
// ...
}
- @Qualifier可以和@Component一起使用來設(shè)置name
Spring組件模型元素vs JSR-330變體
| Spring | javax.inject.* |
|---|---|
| @Autowired | @Inject |
| @Component | @Named / @ManagedBean |
| @Scope("singleton") | @Singleton |
| @Qualifier | @Qualifier / @Named |
| @Value | - |
| @Required | - |
| @Lazy | - |
| ObjectFactory | Provider |
自動掃描
自動掃描的配置需要借助Java類或者是XML來配置開啟自動掃描以及掃描的包目錄;
基于Java類配置包掃描
示例:
@Configuration
@ComponentScan // 配置自動掃描,默認(rèn)是本類所在的包及子包
public class SpringConfig {
//...
}
啟動容器,以java配置類定義應(yīng)用上下文:
public static void main(String[] args) {
ApplicationContext context = null;
// 使用java類配置方式
context = new AnnotationConfigApplicationContext(com.springinaction.SpringConfig.class);
// ...
// or
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext();
ctx.scan("com.acme");
ctx.refresh();
}
@Configuration
從Spring3.0,@Configuration用于定義配置類,可替換xml配置文件,被注解的類內(nèi)部包含有一個或多個被@Bean注解的方法,這些方法將會被AnnotationConfigApplicationContext或AnnotationConfigWebApplicationContext類進(jìn)行掃描,并用于構(gòu)建bean定義,初始化Spring容器。
如果像示例一樣在main方法中直接指明了使用AnnotationConfigApplicationContext來從此類定義應(yīng)用上下文,可以不加這個注解。
@ComponentScan
- 這個注解默認(rèn)情況下開啟掃描本類所在的包以及子包;
- @ComponentScan("com.test"):指定包名
- @ComponentScan(basePackages="com.test")
- @ComponentScan(basePackages={"com.test","com.test2"})
- @ComponentScan(basePackageClasses={A.class,B.class}):類所在包及子包
掃描過濾
- 示例
@Configuration
@ComponentScan(basePackages = "org.example",
includeFilters = @Filter(type = FilterType.REGEX, pattern = ".*Stub.*Repository"),
excludeFilters = @Filter(Repository.class))
public class AppConfig {
...
}
- 支持的格式
| Filter Type | Example Expression |
|---|---|
| annotation (default) | org.example.SomeAnnotation |
| assignable | org.example.SomeClass |
| aspectj | org.example..*Service+ |
| regex | org\.example\.Default.* |
還可以實現(xiàn)org.springframework.core.type .TypeFilter接口自定義過濾器
基于xml配置包掃描
示例:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans" xmlns:context="
http://www.springframework.org/schema/context" xmlns:xsi="
http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd">
<!-- 自動掃描web包 ,將帶有注解的類 納入spring容器管理 -->
<context:component-scan base-package="com.test.*"></context:component-scan>
</beans>
使用了<context:annotation-config>自動包含了<context:annotation-config>;
掃描過濾
<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>
啟動容器定義應(yīng)用上下文:
public static void main(String[] args) {
ApplicationContext context = null;
// 使用xml配置文件方式
context = new ClassPathXmlApplicationContext("spring.xml");
}
如此,基本的基于注解的自動掃描裝配就可用了。
使用自動掃描機(jī)制實際上隱式地包含了AutowiredAnnotationBeanPostProcessor和CommonAnnotationBeanPostProcessor,它們是自動掃描裝配的支撐。
4.混合配置方式
JavaConfig引用XML和其他JavaConfig
@Configuration
@Import(BConfig.class)
public class AConfig{
@Bean
//...
}
@Configuration
@ImportResource("classpath:spring.xml")
public class BConfig{
//..
}
@Configuration
@Import({AConfig.class,BConfig.class})
public class AllConfig{
}
xml中引用JavaConfig
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="...">
<!--引用xml-->
<import resource="spring.xml"/>
<!--引用JavaConfig-->
<bean class=com.JavaConfig.class/>
</beans>
參考資料
[1] Spring In Action