Spring核心——@Configuration与混合使用
@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();}
}
看到这里,思维敏捷的码友通过以下逻辑肯定就发现问题了:
- 通过@Bean注解是向容器添加一个BeanDefinition,
- 在所有的BeanDefinition创建之后容器开始创建Bean之前会执行预设的后置处理器BeanFactoryPostProcessor。
- 最后容器根据BeanDefinition的内容创建Bean。
- return new Alice(bob()); 这段代码中MyConfig::bob方法的调用看起来完全和容器无关,这样就违反了依赖注入的原则!
- 所以是不是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 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配置的例子
首先我们使用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与混合使用相关推荐
- 自己实现spring核心功能 一
聊聊spring spring对于java开发者来说,是最熟悉不过的框架了,我们日常开发中每天都在使用它.它有着各种各样的好处,简单易用,得心应手... ... 我们一说到spring就会讲到ioc ...
- Spring核心注释
介绍: org.springframework.beans.factory.annotation和org.springframework.context.annotation包中存在的Spring注释 ...
- java 切面_Java笔试面试精心整理得到89道Spring 核心知识【收藏向】
点击上方"蓝字",关注了解更多 Spring Framework 简称 Spring,是 Java 开发中最常用的框架,地位仅次于 Java API,就连近几年比较流行的微服务框架 ...
- Spring核心系列之ApplicationContext
Spring核心系列之ApplicationContext Hello,大家好,今天开始,小弟准备推出Spring系列的博客,希望大家喜欢.关于Spring其实我就不用再多介绍了,做过Web开发的,基 ...
- Spring核心机制IoC与AoP梳理
Spring核心机制IoC与AoP梳理 文章目录 Spring核心机制IoC与AoP梳理 IoC介绍 IoC案例介绍 pom文件中IoC环境引入 自己new对象方法举例(正转) IoC创建对象 基于X ...
- Spring核心编程思想
第01章:Spring Framework总览 (12讲) 01.课程介绍.mp4 02.内容综述.mp4 03.课前准备:学习三件套(工具.代码与大脑).mp4 ...
- Spring核心容器
目录 一,什么是Spring 概念 常见的框架 作用 二,什么是IoC&DI Core Container(核心容器) 1.IoC(控制反转) 2.DI(依赖注入) 3.IoC和DI之间的关系 ...
- SSM Chapter 05 Spring 核心概念
SSM Chapter 05 Spring 核心概念 笔记 本章目标: 理解Spring IoC的原理 掌握Spring IoC的配置 理解Spring AOP的原理 掌握Spring AOP的配置 ...
- Spring 核心方法 refresh 刷新流程简要概述及相关源码扩展实现(一)
前言 Spring 启动流程解析 Refresh 内部方法全解析 prepareRefresh obtainFreshBeanFactory AbstractApplicationContext 类以 ...
最新文章
- Intel HAXM is required to run this AVD VT-x is disabled in BIOS的处理方法
- nginx学习七 高级数据结构之动态数组ngx_array_t
- Java解析XMl文件之SAX和DOm方法
- linux快速删除海量文件
- 【网络安全工程师面试合集】——什么是IP安全 IPsec
- C++ STL算法系列4---unique , unique_copy函数
- 递归法:从n个小球中取m个小球(不放回),共有多少种取法?
- 16.卷2(进程间通信)---Sun RPC
- 深度学习笔记(一):logistic分类
- 开源实时视频码流分析软件 VideoEye
- 组装计算机什么配置比较好,组装电脑什么配置好 组装电脑配置推荐
- UGI九宫格sliced显示问题
- 服装行业如何做软文营销推广产品?
- 联想小新V1000外接R9-380显卡
- 最短路算法——Floyd-Warshall(题目练习解析)
- Transformers预训练模型使用:文本摘要 Summarization
- vue数据传递--父传子-方法传递
- MISRA 2004 VS MISRA 2012
- Spring源码环境搭建
- 可道云上传文件后报错