在介绍Spring核心容器的系列文章中已经屡次出现这个注解,从使用的角度来讲能够把他理解为XML配置中的<beans>标签,可是二者确定是不等价的。html
在<beans>标签中除了使用<bean>声名Bean之外,还有各类<context>标签来扩展功能,好比<context:component-scan/>、<context:annotation-config />以及<import>等,这些扩展的功能并非@Configuration注解的参数,而是经过另一个注解来实现——@ComponentScan、@Import。java
@Configuration的基本使用方法已经在纯Java运行与@Bean的“@Bean注解”部分介绍了使用方法,本篇在此基础上进一步进行说明。git
除了在纯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
首先能够很负责的告诉码友们Spring并无限制这个方式去添加Bean,因此例子中Alice类中的Bob实例就是IoC容器中的实例。即便是这样去注入Bean一样实现了依赖注入的功能。至于怎么解决的看完本文天然就能获得答案了。工具
以前在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 instance和BeanManager 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]"; } }
最后在使用@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。