Spring核心——@Configuration與混合使用

@Configuration

在介紹Spring核心容器的系列文章中已經屢次出現這個註解,從使用的角度來講能夠把他理解爲XML配置中的<beans>標籤,可是二者確定是不等價的。html

在<beans>標籤中除了使用<bean>聲名Bean之外,還有各類<context>標籤來擴展功能,好比<context:component-scan/>、<context:annotation-config />以及<import>等,這些擴展的功能並非@Configuration註解的參數,而是經過另一個註解來實現——@ComponentScan、@Import。java

@Configuration的基本使用方法已經在純Java運行與@Bean的「@Bean註解」部分介紹了使用方法,本篇在此基礎上進一步進行說明。git

@Configuration添加依賴

除了在純Java運行與@Bean文中介紹的使用方法,咱們還能夠直接經過使用Java代碼來添加依賴關係:spring

(文中的代碼僅用於說明問題,源碼在gitee上,若有須要請自行clone,本文的案例代碼在chkui.springcore.example.javabase.configuration包中。)api

@Configuration
public class MyConfig {

    @Bean
    public Alice alice() {
        //直接使用方法注入數據。
        //從表面上看這裏調用bob()並無通過容器處理。而是直接使用了。
        return new Alice(bob());
    }

    @Bean
    public Bob bob() {
        return new Bob();
    }
}

看到這裏,思惟敏捷的碼友經過如下邏輯確定就發現問題了:ide

  1. 經過@Bean註解是向容器添加一個BeanDefinition
  2. 在全部的BeanDefinition建立以後容器開始建立Bean以前會執行預設的後置處理器BeanFactoryPostProcessor
  3. 最後容器根據BeanDefinition的內容建立Bean。
  4.  return new Alice(bob()); 這段代碼中MyConfig::bob方法的調用看起來徹底和容器無關,這樣就違反了依賴注入的原則!
  5. 因此是否是Alice類中被注入的Bob實例根本就不是IoC容器中的Bob?

首先能夠很負責的告訴碼友們Spring並無限制這個方式去添加Bean,因此例子中Alice類中的Bob實例就是IoC容器中的實例。即便是這樣去注入Bean一樣實現了依賴注入的功能。至於怎麼解決的看完本文天然就能獲得答案了。工具

@Component添加依賴

以前在Stereotype組件與Bean掃描這篇文章已經提到過,除了在@Configuration中的方法使用@Bean,還能夠在@Component及其派生類中的方法使用@Bean。例以下面的例子:性能

package chkui.springcore.example.javabase.configuration.bean;

@Component
public class BeanManager {
	
	@Bean
	public Cytus cytus() {
		return new Cytus();
	}
	
	@Bean
	public Dva dva() {
		return new Dva();
	}
	
	@Bean
	public Game game(Dva dva) {
		return new Game(cytus(), dva);
	}
}

BeanManager中的三個方法都會向容器添加Bean。注意第三個方法:public Game game(Dva dva)。這裏即採用了經過方法參數注入依賴,也像前面的例子同樣直接調用了方法。可是這裏與前面介紹的使用@Configuration註解不一樣,Game中的Cytus實例不是IoC容器中的Cytus。ui

經過下面的例子來講明@Configuration和@Component中注入Bean的差別。(代碼僅用於展現,有興趣運行的能夠下載gitee上的源碼,代碼在chkui.springcore.example.javabase.configuration 包中)。spa

//package chkui.springcore.example.javabase.configuration;
//使用@Configuration註解
@Configuration
class Config {
	
	@Bean
	public Alice alice() {
		return new Alice(bob());
	}
	
	@Bean
	public Bob bob() {
		return new Bob();
	}
}
//package chkui.springcore.example.javabase.configuration.bean;
//使用@Component註解
@Component
public class BeanManager {
	
	@Bean
	public Cytus cytus() {
		return new Cytus();
	}
	
	@Bean
	public Dva dva() {
		return new Dva();
	}
	
	@Bean
	public Game game(Dva dva) {
		return new Game(cytus(), dva);
	}
}
//運行
public class ConfigurationApp {
	public static void main(String[] args) {
		ApplicationContext ctx = new AnnotationConfigApplicationContext(Config.class, BeanManager.class);
		Bob bob = ctx.getBean(Bob.class);
		Alice alice = ctx.getBean(Alice.class);
		System.out.println("Bob instance of IoC hash: " + bob.hashCode());
		System.out.println("Bob instance of Alice hash: " + alice.getBob().hashCode());
		System.out.println("Compare:" + (bob == alice.getBob()));
		System.out.println("Config instance:" + ctx.getBean(Config.class));

		Game game = ctx.getBean(Game.class);
		Cytus cytus = ctx.getBean(Cytus.class);
		Dva dva = ctx.getBean(Dva.class);
		System.out.println("IoC Cytus: " + cytus.hashCode());
		System.out.println("Game Cytus: " + game.getCytus().hashCode());
		System.out.println("IoC Dva: " + dva.hashCode());
		System.out.println("Game Dva: " + game.getDva().hashCode());
		System.out.println("Cytus:" + (cytus == game.getCytus()));
		System.out.println("Dva:" + (dva == game.getDva()));
		System.out.println("BeanManager Instance:" + ctx.getBean(BeanManager.class));
	}
}

在最後的main方法中咱們對容器中以及Alice、Game中包含的實例進行了hash以及實例對比,在個人電腦上輸出結果以下:

1.Bob instance of IoC hash: 1242027525
2.Bob instance of Alice hash: 1242027525
3.Compare:true
4.Config instance:5.chkui.springcore.example.javabase.configuration.Config$$EnhancerBySpringCGLIB$$acdbeb32@74287ea3
6.IoC Cytus: 2104973502
7.Game Cytus: 735937428
8.IoC Dva: 1604247316
9.Game Dva: 1604247316
10.Cytus:false
11.Dva:true
12.BeanManager Instance:chkui.springcore.example.javabase.configuration.bean.BeanManager@68746f22

例子中分別在@Configuration和@Component標記的類中使用@Bean來向容器添加Bean。最後經過輸出實例的hash以及地址匹配(使用「==」比對)來肯定是否都是同一個單例。

很明顯IoC容器中的Cytus以Game中的Cytus並非一個實例,其餘都是同一個單例。仔細看看第4行和第12行的Config instanceBeanManager instance的輸出內容就會獲得答案。

BeanManager是一個常規的類,而在JVM中運行的Config是一個經過CGLIB實現的字節碼級別的代理類(若是不知道CGLIB是什麼就本身網上找找吧,這玩意在Java界已經紅得發紫了)。Spring其實是使用CGLIB爲Config類添加了一個「代理殼」,當咱們在任何地方直接調用@Configuration標註的類中的的方法時,代理殼都會將其整理爲一個BeanDefinition的轉換過程。

知道二者的差別後咱們選擇何種方式來添加Bean就很清晰了:

使用@Configuration能保證不會出現例子中Cytus這樣的例外。也能清晰的明確@Configuration等價於一個<beans>統一管理。

而在@Component或其餘組建中使用@Bean好處是不會啓動CGLIB這種重量級工具(不過在Spring中即便這裏不使用,其餘不少地方也在使用)。而且@Component及其相關的Stereotype組件自身就有摸框級別的功能,在這裏使用@Bean註解能很好的代表一個Bean的從屬和結構關係,可是須要注意直接調用方法的「反作用」。

我的建議若是沒什麼特別的要求就使用@Configuration,引入CGLIB並不會影響多少性能,然而坑會少不少。在spring官網將用@Configuration建立的@Bean稱呼爲"Full"模式、將@Component建立的@Bean稱呼爲"'lite"模式,從字面上也能略知他們的差別。

多種方式混合使用

從XML配置到純Java配置,Spring變得愈來愈簡便好用,對應的功能也愈來愈多樣化。若是對他的脈絡沒有清晰的認識,每每會陷入迷惑中。不管功能再複雜咱們都要記住本系列文章開篇提到的IoC容器的初衷:

處理容器與Bean、Bean與Bean的關係。Bean是最小的工做單元,一切功能都是在Bean基礎上擴展而來的。

因此不管是XML配置仍是純Java配置基本目標就是解決三個問題:向容器添加Bean,肯定Bean的功能,肯定Bean與Bean之間的依賴關係。

既然XML和純Java配置都是解決一樣的問題,那麼混合使用固然沒問題。好比在XML中配置了<context:component-scan/>,那麼指定路徑下的@Component以及派生註解(@Service、@Comfiguration等)都會被掃描並添加到容器中成爲一個Bean。而後IoC容器會根據註解的類型來肯定這個Bean是什麼功能。、

下面是一個使用AnnotationConfigApplicationContext啓動容器混合使用Java配置與XML配置的例子(源碼在本人gitee的spring-core-sample倉庫中,本節的代碼在包chkui.springcore.example.javabase.multiconfiguration中)。

首先咱們使用AnnotationConfigApplicationContext啓動IoC容器:

package chkui.springcore.example.javabase.multiconfiguration;

@Configuration
@ComponentScans({ @ComponentScan("chkui.springcore.example.javabase.multiconfiguration.config"),
		@ComponentScan("chkui.springcore.example.javabase.multiconfiguration.service") })
public class MultiConfigurationApp {
	public static void main(String[] args) {
		ApplicationContext ctx = new AnnotationConfigApplicationContext(MultiConfigurationApp.class);
	}
}

在Main方法中直接指定了當前的類,因此MultiConfigurationApp類會成爲一個Bean。因爲是一個Stereotype模式的@Configuration標記類(@Configuration繼承自@Component,提供了配置相關的分層功能,關於Stereotype模式的內容相見Stereotype組件與Bean掃描),因此容器會用CGLIB來代理它實現配置相關的功能。@ComponentScans是一個輔助註解,他的做用就是整合多個@ComponentScan一塊兒使用。

在config包中有2個@Configuration類:

package chkui.springcore.example.javabase.multiconfiguration.config;

@Configuration
@Import({ClubConfiguration.class})
@ImportResource("javabase/multiconfiguration/config.xml")
public class MainConfiguration {}
package chkui.springcore.example.javabase.multiconfiguration.config;

public class ClubConfiguration {
	@Bean
	public Mil mil() {return new Mil();}
	@Bean
	public Mau mau() {return new Mau();}
}

MainConfiguration類被標記了@Configuration註解,因此他會被掃描並添加到容器中。

@Import註解的做用是引入其餘類成爲一個Bean,咱們能夠看到ClubConfiguration類並無任何註解,可是他經過@Import註解在其餘類添加到容器中。

而@ImportResource等價於XML配置中的<import>標籤,做用就是引入一個XML配置文件。對應的XML文件以下:

<beans ...>
    <bean class="chkui.springcore.example.javabase.multiconfiguration.bean.Cfc" />
    <bean class="chkui.springcore.example.javabase.multiconfiguration.bean.Jav" />
</beans>

這樣XML配置中的2個類也會被添加到容器中。案例中對應的實體類以下:

package chkui.springcore.example.javabase.multiconfiguration.bean;
class Mau {
	public String toString() {
		return "Manchester United[MAU]";
	}
}
class Cfc {
	public String toString() {
		return "Chelsea Football Club[CFC]";
	}
}
class Mil {
	public String toString() {
		return "A.C Milan [MIL]";
	}
}
class Jav {
	public String toString() {
		return "Juventus [JAV]";
	}
}

Conditionally

最後在使用@Configuration時可使用Conditionally特性來肯定是否添加Bean。大體用法就是實現Condition接口,而後經過@Conditional註解和@Bean綁定在一塊兒進行條件判斷。

實現Condition:

package chkui.springcore.example.javabase.multiconfiguration.config;
public class SoySauceCondition implements Condition {
	@Override
	public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
		return false; //返回false則不會對應的Bean。
	}
}

而後使用@Conditional註解綁定到一個@Bean上:

package chkui.springcore.example.javabase.multiconfiguration.config;
public class ClubConfiguration {
	@Bean
	@Conditional(SoySauceCondition.class)
	public SoySauce soySauce() {
		return new SoySauce();
	}
}

這樣,若是SoySauceCondition中的matches方法返回ture則添加SoySauce到IoC容器中,不然不會存在這個Bean。

相關文章
相關標籤/搜索