Spring基础篇——bean的自动化装配
上篇博文讲Spring的IOC容器时说道,虽然容器功能强大,但容器本身只是个空壳,需要我们主动放入装配对象,并告诉它对象之间的协作关系,然后容器才能按照我们的指示发挥它的魔力,完成装配bean的使命。这里,我们把Spring创建应用对象之间的协作关系的行为成为装配。Spring提供了很多装配bean的方式供我们在开发中选择,我们常用到的有三种装配机制:自动装配、Java注解和XML配置。通常我们将第一种称为隐式的装配机制,后面两种为显示的装配机制。实际应用中,基于便利性考虑,首选的肯定是隐式的自动化装配机制,只有当需要注入的bean的源码不是由自己的程序来维护,而是引入第三方的应用组件的时候,才考虑显示的方式装配bean。当然,各种装配方式在实际应用中是可以自由选择搭配的,编码过程中也不必拘泥哪一种,适用就好。本篇博文先来讲述隐式的装配机制——bean的自动化装配。
你一定很好奇Spring是怎么来实现其自动化装配机制的,其实Spring主要通过下面两个方面来实现:
- 组件扫描——通过开启组件扫描功能让Spring可以自动发现应用上下文中的bean;
- 自动装配——自动满足组件之间的依赖关系。
下面,我们分别来看看Spring如何通过组件扫描和自动装配来为我们的应用程序自动化的装配bean。我们先定义一个汽车接口:
- package spring.facade;
- public interface Car {
- void drive();
- }
组件扫描
组件扫描的要义在于通过扫描控制,让Spring自动的去发现应用程序中的bean。不过程序中的对象那么多,Spring怎么知道哪些对象是需要它去管理创建的呢?这就涉及到Spring的一个组件注解——@Component,被该注解标注的类即为Spring的组件类,Spring容器加载过程中会自动的为该类创建bean(PS:实际上Spring的组件注解按照语义化的分类还有@Controller @Repository @Service等等,分别作用于控制层、持久层和业务层,此处仅是举例演示,不做区分讲解)。所以,我们可以将接口的一个实现标注上该注解,表明实现类是要被Spring创建实例的——
- package spring.impl;
- import org.springframework.stereotype.Component;
- import spring.facade.Car;
- @Component
- public class QQCar implements Car {
- @Override
- public void drive() {
- System.out.println("开QQ车");
- }
- }
不过,Spring的注解扫描默认是不开启的,所以我们还需要显示的配置注解启动。这里同样有两种方式,Java注解和XML的方式,我们分别展示出来——
Java配置类CarConfig :
- package spring.config;
- import org.springframework.context.annotation.ComponentScan;
- import org.springframework.context.annotation.Configuration;
- @ComponentScan
- public class CarConfig {
- }
XML配置文件applicationContext.xml:
- <?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"
- xsi:schemaLocation="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.xsd">
- <!--启动注解扫描-->
- <context:component-scan base-package="spring"/>
- </beans>
接下来我们编写测试类,看看Spring是不是自动的去发现了我们注解为组件的bean并为我们创建了对象——
- package spring.test;
- import org.junit.Test;
- import org.junit.runner.RunWith;
- import org.springframework.beans.factory.annotation.Autowired;
- import org.springframework.test.context.ContextConfiguration;
- import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
- import spring.config.CarConfig;
- import spring.impl.QQCar;
- import static org.junit.Assert.assertNotNull;
- /**
- * 注解释义:
- * @RunWith(SpringJUnit4ClassRunner.class) 测试在Spring环境中运行
- * @ContextConfiguration 上下文配置注解,指定配置文件(Java类或XML文件)的位置
- */
- @RunWith(SpringJUnit4ClassRunner.class)
- //@ContextConfiguration(classes = CarConfig.class) //加载Java配置类的方式
- @ContextConfiguration(locations = "classpath:resource/applicationContext.xml") //加载XML配置的方式
- public class CarTest {
- @Autowired
- private QQCar car ;
- @Test
- public void carTest(){
- assertNotNull(car);
- }
- }
虽然现在的编程趋势是越来越多的使用Java注解的方式,但是上面的测试你会发现,通过XML注解的方式能够测试成功,而Java注解的方式却是失败的,测试会抛出NoSuchBeanDefinitionException的异常,表示没有QQCar的组件定义,也就是Spring没有发现它,Why? 原因也很简单,那就是基于Java注解的方式启动的注解扫描默认情况下只能扫描配置类所在的包以及其的子包,如果要明确扫描其它包中的组件,需要在启动扫描的注解 @ComponetScan 中显示的注明,如改成 @ComponentScan("spring.impl"),上诉的测试就能通过了。如果有多个包要扫描,可以这样配置:@ComponentScan(basePackages = {"spring.impl","spring.test"}) 不过这样字符串的表示方式是类型不安全的,而且写死包名的方式不利于代码重构,我们可以指定包中所含的类或接口来指定要扫描的包,于是可以这样标注: @ComponentScan(basePackageClasses = QQCar.class) ,多个包同样可以用{}来以数组形式的表示。不过这样对重构依然不友好,最好的方式就是在要扫描的包中定义一个空标接口,该接口仅仅用来指定包扫描的范围,如此将重构的影响降到最低。
自动装配
前文的讲述只是阐明如何将一个类定义成Spring的组件并启动Spring的组件扫描,而且我们已经通过测试证实,Spring确实扫描到了我们指定的组件类并为我们创建了对象。不过,创建的对象只是独立的存在,并没有和其他对象产生依赖协作;实际应用中,对象之间的依赖协作是再常见不过了,而要将Spring通过组件扫描为我们创建的对象根据实际业务建立起相互的依赖协作,就需要利用Spring的自动装配。便于演示,我们再定义一个Man类,Man的工作就是开车,我们先通过构造器注入的方式来满足依赖,看Spring是否会给我们自动注入我们需要的Car的实例对象——
- package spring.impl;
- import org.springframework.beans.factory.annotation.Autowired;
- import org.springframework.stereotype.Component;
- import spring.facade.Car;
- @Component
- public class Man {
- private Car car;
- public Man() {
- }
- @Autowired
- public Man(QQCar car) {
- this.car = car;
- }
- public void work() {
- car.drive();
- }
- }
测试方法——
- package spring.test;
- import org.junit.Test;
- import org.junit.runner.RunWith;
- import org.springframework.beans.factory.annotation.Autowired;
- import org.springframework.test.context.ContextConfiguration;
- import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
- import spring.config.CarConfig;
- import spring.impl.Man;
- @RunWith(SpringJUnit4ClassRunner.class)
- @ContextConfiguration(classes = CarConfig.class)
- public class CarTest {
- @Autowired
- Man man;
- @Test
- public void carTest() {
- man.work();
- }
- }
如以上代码,测试当然是成功的,在测试类中,Man作为组件类被Spring扫描并创建了一个对象实例,该实例调用work方法的时候,需要Car的实例对象,而我们在有参构造函数上通过 @Autowired 注解表明了对象的依赖关系,程序运行过程中,Spring会自动为我们注入Car的实例对象来满足对象依赖,这就是自动装配的精要所在。实际上,不只是构造器上可以用 @Autowired 注解,在属性的Setter方法上,甚至普通的方法上,都可以用@Autowired 注解来满足对象之间的依赖,实现自动注入的功能——
- package spring.impl;
- import org.springframework.beans.factory.annotation.Autowired;
- import org.springframework.stereotype.Component;
- import spring.facade.Car;
- @Component
- public class Man {
- private Car car;
- public Man() {
- }
- //构造器实现自动装配
- // @Autowired
- public Man(QQCar car) {
- this.car = car;
- }
- //Setter方法实现自动装配
- // @Autowired
- public void setCar(QQCar car) {
- this.car = car;
- }
- //普通方法实现自动装配
- @Autowired
- public void insertCar(QQCar car) {
- this.car = car;
- }
- public void work() {
- car.drive();
- }
- }
我们将Man类中添加不同的方法测试,依然是可以成功的。不过有一点要注意,在非构造器实现自动装配的时候,虽然我们没有自己new对象,但Spring创建实例会通过Man的默认的构造器,此时的Man类中如果定义了有参构造器,就一定要把默认构造器构造出来,不然会抛无默认构造器的异常,记住:一定养成类中写默认构造器的习惯,便于扩展。
自动装配的歧义性
如果你足够细心,你会发现博主上面满足自动装配的测试代码中,注入的Car并没有采用多态的写法,代码显得很低级。其实我是为了测试通过,故意注入了具体的实现,实际业务中当然不会这么局限的去写代码。因为博主Car的接口还有一个奔驰车的实现类BenzCar,如果用多态的写法,自动装配会有产生歧义性问题,会抛 NoUniqueBeanDefinitionException 异常。那么,面对这种歧义性,如何去解决呢?你一定知道Spring容器管理的每个bean都会有一个ID作为唯一标识,在上面的示例中,我们描述QQCar类为Spring的组件的时候并没有明确的设置ID,但是Spring默认会将组件类的类名首字母小写来作为bean的ID,而我们也可根据我们自己的业务需要自定义ID标识——
- package spring.impl;
- import org.springframework.stereotype.Component;
- import spring.facade.Car;
- //这里指定 chenbenbuyi 为组件的ID
- @Component("chenbenbuyi")
- public class QQCar implements Car {
- @Override
- public void drive() {
- System.out.println("开QQ车");
- }
- }
可是测试发现,这并没有解决接口参数在自动装配时的歧义性问题,因为在组件上自定义ID是一种后发行为,当你让Spring在装配阶段从多个接口实现中选择要自动注入的对象实例时,Spring无法选择——就好比你只跟我说你要开一辆车,每辆车也都有唯一的车牌号,但我还是不知道你要开什么车。怎么办呢?这里有多种解决方案,我们可以通过 @Primary注解将我们明确需要自动注入的实现类标注为首选的bean,就想这样——
- package spring.impl;
- import org.springframework.context.annotation.Primary;
- import org.springframework.stereotype.Component;
- import spring.facade.Car;
- @Component
- @Primary
- public class BenzCar implements Car {
- @Override
- public void drive() {
- System.out.println("开奔驰车");
- }
- }
当自动装配的时候,Spring面对歧义性时,会优先选择被标注为首选的bean进行自动注入。当然,我们还可以采用限定符注解,在使用@Autowired 完成自动装配的时候限定只让某个bean作为自动注入的bean——
- package spring.impl;
- import org.springframework.beans.factory.annotation.Autowired;
- import org.springframework.beans.factory.annotation.Qualifier;
- import org.springframework.stereotype.Component;
- import spring.facade.Car;
- @Component
- public class Man {
- private Car car;
- public Man() {
- }
- //普通方法实现自动装配
- @Autowired
- @Qualifier("chenbenbuyi") //限定ID为 chenbenbuyi 的bean被装配进来
- public void insertCar(Car car) {
- this.car = car;
- }
- public void work() {
- car.drive();
- }
- }
自此,关于Spring的自动装配就阐述得差不多了,下一节系列文章会接着讲解Spring的另外两种常用的装配机制——Java注解和XML配置。博文所述皆为原创,如要转载,请注明出处;如果阐述得不恰当的地方,欢迎指教,不胜感激。
Spring基础篇——bean的自动化装配的更多相关文章
- Spring基础09——Bean的自动装配
1.XML配置的Bean自动装配 SpringIOC容器可以自动装配Bean,需要做的仅仅是在<bean>的autowire属性里指定自动装配的模式,而不需要手工去指定要装配的Bean,a ...
- Spring基础篇——通过Java注解和XML配置装配bean
自动化装配的确有很大的便利性,但是却并不能适用在所有的应用场景,比如需要装配的组件类不是由自己的应用程序维护,而是引用了第三方的类库,这个时候自动装配便无法实现,Spring对此也提供了相应的解决方案 ...
- Spring基础篇——通过Java注解和XML配置装配bean(转载)
作者:陈本布衣 出处:http://www.cnblogs.com/chenbenbuyi 本文版权归作者和博客园共有,欢迎转载分享,但必须保留此段声明,且在文章页面明显位置给出原文链接,否则保留 ...
- Spring基础14——Bean的生命周期
1.IOC容器中的Bean的生命周期方法 SpringIOC容器可以管理Bean的生命周期,Spring允许在Bean生命周期的特定点执行定制的任务.SpringIOC容器对Bean的生命周期进行管理 ...
- Spring基础11——Bean的作用域
1.Bean的作用域种类 Spring中的bean的作用域分为四种:singleton.prototype.session.request,后两种很少使用,下面我们主要来学习前两种 2.singlet ...
- Spring基础10——Bean之间关系
1.前言 不同的Bean之间存在两种关系:继承和依赖,这里的继承与java中的继承不同,它指的是配置上的继承. 2.继承bean配置 Spring允许继承bean的配置,被继承的bean成为父bean ...
- Spring基础篇——Spring容器和应用上下文理解
上文说到,有了Spring之后,通过依赖注入的方式,我们的业务代码不用自己管理关联对象的生命周期.业务代码只需要按照业务本身的流程,走啊走啊,走到哪里,需要另外的对象来协助了,就给Spring说,我想 ...
- Spring基础篇——DI和AOP初识
前言 作为从事java开发的码农,Spring的重要性不言而喻,你可能每天都在和Spring框架打交道.Spring恰如其名的,给java应用程序的开发带了春天般的舒爽感觉.Spring,可以说是任何 ...
- Spring基础篇——Spring的AOP切面编程
一 基本理解 AOP,面向切面编程,作为Spring的核心思想之一,度娘上有太多的教程啊.解释啊,但博主还是要自己按照自己的思路和理解再来阐释一下.原因很简单,别人的思想终究是别人的,自己的理解才是 ...
随机推荐
- STL中的nth_element()方法的使用
STL中的nth_element()方法的使用 通过调用nth_element(start, start+n, end) 方法可以使第n大元素处于第n位置(从0开始,其位置是下标为 n的元素),并且比 ...
- bfs学习
今天做到了bfs的练习,顺便写下心得... bfs能解决搜索和最短路径的问题. 下面是学习心得: typedef struct point //定义点 { int x; int y; }P; bfs( ...
- How Many Sets I(容斥定理)
题目链接:http://acm.zju.edu.cn/onlinejudge/showProblem.do?problemCode=3556 How Many Sets I Time Limit: 2 ...
- 算法-java代码实现堆排序
堆排序 第7节 堆排序练习题 对于一个int数组,请编写一个堆排序算法,对数组元素排序. 给定一个int数组A及数组的大小n,请返回排序后的数组. 测试样例: [1,2,3,5,2,3],6 [1,2 ...
- JAVA:创建类和对象
package duixiang; public class duixiang { /* * 类的实例化:创建对象 */ public static void main(String[] args) ...
- [拾 得] zip gzip bzip2 & tar 压缩/打包 四大金刚
坚持知识分享,该文章由Alopex编著, 转载请注明源地址: http://www.cnblogs.com/alopex/ 索引: 介绍压缩和打包 gzip bzip2 zip 的基本使用 gz ...
- IT术语的正确读法
Linux /ˈlɪnəks/ /ˈlɪnʊks/(EU) Linux 是一类 Unix 计算机操作系统的统称.该操作系统的核心的名字也是“ Linux” .参考: < !-- m --> ...
- MYSQL ORDER BY Optimization
ORDER BY Optimization 某些情况下,MYSQL可以使用index排序而避免额外的sorting. 即使order by语句列不能准确的匹配index,只要没有index中(不在or ...
- Jquery如何删除table里面checkbox选中的多个行
思路:遍历被选中的checkbox对象→根据选中项筛选出需要删除的行→删除行.实例说明如下: 1.HTML结构 <table id = "test_table"> &l ...
- java实现定时任务
Java中实现定时任务执行某一业务.具体操作如下: 1.定义初始化任务 2.任务业务操作 3.定义初始化方法 4.在web.xml中注册启动 5.定义具体执行时间