@Configuration

在介绍Spring核心容器的系列文章中已经多次出现这个注解,从使用的角度来说可以把他理解为XML配置中的<beans>标签,但是两者肯定是不等价的。

在<beans>标签中除了使用<bean>声名Bean以外,还有各种<context>标签来扩展功能,比如<context:component-scan/>、<context:annotation-config />以及<import>等,这些扩展的功能并不是@Configuration注解的参数,而是通过另外一个注解来实现——@ComponentScan、@Import。

@Configuration的基本使用方法已经在纯Java运行与@Bean的“@Bean注解”部分介绍了使用方法,本篇在此基础上进一步进行说明。

@Configuration添加依赖

除了在纯Java运行与@Bean文中介绍的使用方法,我们还可以直接通过使用Java代码来添加依赖关系:

@Configuration
public class MyConfig {@Beanpublic Alice alice() {//直接使用方法注入数据。//从表面上看这里调用bob()并没有经过容器处理。而是直接使用了。return new Alice(bob());}@Beanpublic Bob bob() {return new Bob();}
}

看到这里,思维敏捷的码友通过以下逻辑肯定就发现问题了:

  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 {@Beanpublic Cytus cytus() {return new Cytus();}@Beanpublic Dva dva() {return new Dva();}@Beanpublic Game game(Dva dva) {return new Game(cytus(), dva);}
}

BeanManager中的三个方法都会向容器添加Bean。注意第三个方法:public Game game(Dva dva)。这里即采用了通过方法参数注入依赖,也像前面的例子一样直接调用了方法。但是这里与前面介绍的使用@Configuration注解不同,Game中的Cytus实例不是IoC容器中的Cytus。

通过下面的例子来说明@Configuration和@Component中注入Bean的差异。

//package chkui.springcore.example.javabase.configuration;
//使用@Configuration注解
@Configuration
class Config {@Beanpublic Alice alice() {return new Alice(bob());}@Beanpublic Bob bob() {return new Bob();}
}
//package chkui.springcore.example.javabase.configuration.bean;
//使用@Component注解
@Component
public class BeanManager {@Beanpublic Cytus cytus() {return new Cytus();}@Beanpublic Dva dva() {return new Dva();}@Beanpublic 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配置的例子

首先我们使用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 {@Beanpublic Mil mil() {return new Mil();}@Beanpublic 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 {@Overridepublic 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。

欢迎工作一到五年的Java工程师朋友们加入Java技术交流群:659270626
群内提供免费的Java架构学习资料(里面有高可用、高并发、高性能及分布式、Jvm性能调优、Spring源码,MyBatis,Netty,Redis,Kafka,Mysql,Zookeeper,Tomcat,Docker,Dubbo,Nginx等多个知识点的架构资料)合理利用自己每一分每一秒的时间来学习提升自己,不要再用"没有时间“来掩饰自己思想上的懒惰!趁年轻,使劲拼,给未来的自己一个交代!

Spring核心——@Configuration与混合使用相关推荐

  1. 自己实现spring核心功能 一

    聊聊spring spring对于java开发者来说,是最熟悉不过的框架了,我们日常开发中每天都在使用它.它有着各种各样的好处,简单易用,得心应手... ... 我们一说到spring就会讲到ioc ...

  2. Spring核心注释

    介绍: org.springframework.beans.factory.annotation和org.springframework.context.annotation包中存在的Spring注释 ...

  3. java 切面_Java笔试面试精心整理得到89道Spring 核心知识【收藏向】

    点击上方"蓝字",关注了解更多 Spring Framework 简称 Spring,是 Java 开发中最常用的框架,地位仅次于 Java API,就连近几年比较流行的微服务框架 ...

  4. Spring核心系列之ApplicationContext

    Spring核心系列之ApplicationContext Hello,大家好,今天开始,小弟准备推出Spring系列的博客,希望大家喜欢.关于Spring其实我就不用再多介绍了,做过Web开发的,基 ...

  5. Spring核心机制IoC与AoP梳理

    Spring核心机制IoC与AoP梳理 文章目录 Spring核心机制IoC与AoP梳理 IoC介绍 IoC案例介绍 pom文件中IoC环境引入 自己new对象方法举例(正转) IoC创建对象 基于X ...

  6. Spring核心编程思想

    第01章:Spring Framework总览 (12讲)       01.课程介绍.mp4       02.内容综述.mp4       03.课前准备:学习三件套(工具.代码与大脑).mp4 ...

  7. Spring核心容器

    目录 一,什么是Spring 概念 常见的框架 作用 二,什么是IoC&DI Core Container(核心容器) 1.IoC(控制反转) 2.DI(依赖注入) 3.IoC和DI之间的关系 ...

  8. SSM Chapter 05 Spring 核心概念

    SSM Chapter 05 Spring 核心概念 笔记 本章目标: 理解Spring IoC的原理 掌握Spring IoC的配置 理解Spring AOP的原理 掌握Spring AOP的配置 ...

  9. Spring 核心方法 refresh 刷新流程简要概述及相关源码扩展实现(一)

    前言 Spring 启动流程解析 Refresh 内部方法全解析 prepareRefresh obtainFreshBeanFactory AbstractApplicationContext 类以 ...

最新文章

  1. Intel HAXM is required to run this AVD VT-x is disabled in BIOS的处理方法
  2. nginx学习七 高级数据结构之动态数组ngx_array_t
  3. Java解析XMl文件之SAX和DOm方法
  4. linux快速删除海量文件
  5. 【网络安全工程师面试合集】——什么是IP安全 IPsec
  6. C++ STL算法系列4---unique , unique_copy函数
  7. 递归法:从n个小球中取m个小球(不放回),共有多少种取法?
  8. 16.卷2(进程间通信)---Sun RPC
  9. 深度学习笔记(一):logistic分类
  10. 开源实时视频码流分析软件 VideoEye
  11. 组装计算机什么配置比较好,组装电脑什么配置好 组装电脑配置推荐
  12. UGI九宫格sliced显示问题
  13. 服装行业如何做软文营销推广产品?
  14. 联想小新V1000外接R9-380显卡
  15. 最短路算法——Floyd-Warshall(题目练习解析)
  16. Transformers预训练模型使用:文本摘要 Summarization
  17. vue数据传递--父传子-方法传递
  18. MISRA 2004 VS MISRA 2012
  19. Spring源码环境搭建
  20. 可道云上传文件后报错

热门文章

  1. 基于复杂网络理论的计算机网络拓补分析,基于复杂网络理论的计算机网络拓扑分析论文.doc...
  2. TFS(Team Foundation Server)服务器搭建图文教程(tfs2015)
  3. 一天半辛苦的搞机过程
  4. matlab中dtft语法,DTFT的Matlab矩阵计算的理解
  5. mall商城微服务遇见的坑
  6. 全面详细的github中文教程
  7. 一些纳税常识[公司]
  8. linux ab压力测试
  9. 2023最新IDEA下载安装教程
  10. RGB、多光谱、高光谱、全色图介绍