Spring IOC学习(01)Bean的装配

内容概览


    1. 简化Java开发
    1. Spring Bean
    1. Spring Bean生命周期
    1. 装配Bean的可选方案
    1. 自动化装配Bean
    1. 通过Java配置类装配Bean
    1. 通过XML装配Bean
    1. 导入和混合配置
    1. 总结

1. 简化Java开发


Spring框架是为了解决Java开发的复杂性而创建的,相对于EJB的复杂,Spring只用简单的JavaBean(或者Bean)组件就能实现EJB所完成的事情。而且因为Spring的简单,松耦合等特性,使它能服务但是不限于服务端开发,因此努力学习Spring是一个Java程序员正确的选择。

2. Spring Bean


Spring有跟多概念,其中最基本的一个就是bean,那到底spring bean是什么?

我们知道,Spring最核心的两个概念就是IOC和AOP,而IOC容器管理的对象就是一个个Bean,说直白些就是一个个Java类对象。

概念简单明了,我们提取处关键的信息:

  • bean是对象,一个或者多个不限定
  • bean由Spring中一个叫IoC的东西管理
  • 我们的应用程序由一个个bean构成

那么IoC又是什么东西?

控制反转英文全称:Inversion of Control,简称就是IoC。控制反转通过依赖注入(DI)方式实现对象之间的松耦合关系。在普通Java代码中,我们一个个去new对象是很麻烦的,如果一个类中的属性类型是其它类,那么它们之间就有依赖,这样的依赖对于一个类来说可能是一个或者多个。而Spring中,简而言之,就是:IoC就是一个对象定义其依赖关系而不创建它们的过程。

从上面的过程,可以想象一个代码从复杂到简单的过程,是如何实现了松耦合。

3. Spring Bean生命周期


在传统Java项目中,一个对象的生命周期很简单,先使用new进行实例化,然后使用,一旦不再使用,就被垃圾回收。

相比之下,Spring中的Bean对象的生命周期要复杂的多,正确了解Spring Bean的生命周期非常重要,不但是面试可能问到,还有可能需要利用Spring提供的扩展点来自定义Bean的创建过程。如下图,展示了Bean装载到Spring应用上下文(就是项目运行环境中)的一个典型的生命周期过程。

上面的内容可以详细描述为以下步骤:

    1. Spring 对Bean进行实例化
    1. Spring将值和Bean的引用注入到bean对应的属性中
    1. 如果bean实现了BeanNameAware接口,Spring将bean的id传递给setBeanName方法
    1. 如果bean实现了BeanFactoryAware接口,Spring将调用setBeanFactory方法,将BeanFactory容器实例传入
    1. 如果bean实现了ApplicationContextAware接口,Spring将调用setApplicationContext方法,将bean所在的应用上下文的引用传入进来
    1. 如果bean实现了BeanPostProcessor接口,Spring将调用它的postProcessBeforeInitialization方法
    1. 如果bean实现了InitializingBean接口,Spring将调用它的afterPropertiesSet方法,类似的,如果bean使用init-method生命了初始化方法,该方法也会被调用
    1. 如果bean实现了BeanPostProcessor接口,Spring将调用它的 postProcessAfterInitialization方法
    1. 此时,bean已经准备就绪,会一直存在于上下文中,直到该程序应用上下文环境被销毁
    1. 最后是销毁,如果bean实现了DisposableBean接口,Spring将调用它的destory方法,如果bean使用了destory-method生命了销毁的方法,该方法也会被调用。

上面的过程对于使用过Spring的人来说,应该会熟悉部分或者全部,但是对于初学者来说可能有点晕。不过这个过程很重要,从面试经验来看,能记住最好记住,最起码多看几遍熟悉起来,后面的学习中会一点点去了解,到时候再记起来就容易了。

4. 装配Bean的可选方案


在Spring中,对象无需自己查找和创建与其相关联的其他对象,相反,Spring容器会负责把相互有关系,需要合作的对象引用赋予各个对象中,这样才能实现控制翻转Ioc。

创建各个对象,也就是各个bean直接的协作关系的行为叫做装配。这也是依赖注入的本质。在Spring中,装配Bean有很多种方式,下面介绍常见的三种方式。

  • 在XML中进行显式装配
  • 在Java配置类代码中进行显式装配
  • 隐式的bean发现机制和自动装配

多种方式虽然会让spring使用变得复杂,而且功能上有重叠,但是大部分情况只要根据自己的喜好使用就行,特殊情况,可能只有其中一种情况合适。三种方式推荐的是,能用隐式就用隐式最好,如果需要写配置,Java配置类比XML更好一些。

5. 自动化装配Bean


下图是一张Spring官网的图片,从上面可以看出Spring Framework的结构:

其中最下面基础的组成就是beans,core,context,spel几个部分,因此我们在开发spring时,最起码要引入的依赖如下:

也就是说,我们使用spring ioc开发,引入上面五个就可以了。下面就用上面这些来开发第一个spring程序。

Spring可以使用XML和Java代码来进行装配,但是最强大的还是自动化装配,其从两个角度实现自动化装配:

  • 组件扫描:Spring会自动发现应用上下文中的bean
  • 自动装配:Spring自动满足bean之间的依赖

先来写一个基础的Java类:

这个类只有两个基础的属性,为了方便,我们直接赋上了初始值。特殊的地方是,类上面有一个@Component注解,这个简单的注解表示该类会作为一个组件类,并告知Spring要为这个类创建bean。这个注解只是一个基础注解,衍生出来的还有很多特殊用途的,像@Service,@Controller等,后面会一一介绍。同时在注解上面还可以给对应的bean命名,如:


上面表示为该bean设置了一个id=helloUser005,如果不写,默认的id就是类名,只不过首字母要小写。

然后在这个类的同一目录下,创建一个配置类:

@Configuration注解表示这是一个配置类,@ComponentScan注解表示配置类会扫描该配置类所在的包以及所有子包,寻找所有带@Component注解的类。

@ComponentScan后面并没有配置属性,就表示扫描配置类所在的包以及子包,这里还可以手动配置从哪个包开始扫描:

或者为了更清楚,这样写:@ComponentScan(basePackages = "ioc0051"),这样就表示扫描 ioc0051 包以及其子包。不仅如此,还可以配置扫描多个包:


因为包属性值是字符串,有出错的可能,也可以指定包中所含的类或者接口:

这样就能保住正确性。总之各种方法任你挑选。这样通过配置类实现简单的自动装配就完成了。下面我们来测试一下,首先是传统的方法,从Spring上下文启动,因为这里使用的是类配置,所以使用 AnnotationConfigApplicationContext 应用上下文类。具体测试方法如下:

通过getBean方法可以获取Spring为我们创建的对象实例,来看运行结果:

可以看到,我们并没有手动new对象,但是Spring为我们创建的对象实例,就是bean,获取bean就能获取。自动装配不仅可以使用Java配置类,还可以使用XML配置文件实现:

配置文件是以beans标签开始的,可见这里就是和所有bean相关的定义。<context:component-scan 元素是与注解@ComponentScan现对于的,这里可以按照需求定义需要的扫描位置。

测试xml方式,需要使用 ClassPathXmlApplicationContext 应用上下文类,具体代码如下:

和前面的配置类方法类似,这里不再说明。Spring还有另一种更加友好的方法加载应用上下文进行测试,就是使用测试组件,需要加入两个依赖:

junit不用描述,spring-test是为测试spring代码的测试组件,上面的配置类启动测试可以写成:

这里使用了Spring的SpringJUnit4ClassRunner,可以在测试的时候自动创建应用上下文。注解@ContextConfiguration用来加载配置,classes表示加载一个或者多个配置类,如果是xml的方式可以使用属性:

注解@Autowired表示将一个bean,也就是类的对象实例注入到此处,这里对象之间的引用就这样就可以,不用手动new一个。这种使用方式是最简单的方式,还有一些其它方式,如果类作为入参传入到方法中,那么方法就上面就可以加上注解@Autowired,最经典的比如构造方法和setter方法:

另外,如果在启动时,不确定bean是否创建了,那么可以加上属性:

这样就不会报错了。不过要小心这个属性,如果没有进行null检测就使用,有可能出现空指针异常。上面几种可以选择自己喜欢的方式使用。后面会继续讨论自动装配的复杂性。@Component和@Autowired都是Spring的注解,如果更喜欢Java的依赖注入,可以使用@Named和@Inject,不过使用这两个注解的还是少数,因此这里主要学习Spring的注解。

6. 通过Java配置类装配Bean


尽管很多情况下,通过组件扫描和自动装配实现实现自动化配置是推荐的使用方式,但是也有不少的情况下,需要显示的配置bean,比如不是自己写的类,没法在代码中加Spring的注解等情况。这种情况下可以通过配置类或者xml文件来手动配置Spring,这里先来讨论配置类的方式。

前面的自动装配中,我们已经写过Java配置类,配置类需要在类上面加上@Configuration注解,因为这里不需要自动扫描,需要显式的进行配置Bean,所以@ComponentScan注解就不用写了。如下:

下面来定义一个普通的类,这里是显示装配,因此@Component注解不用写:

配置类中的配置如下:

方法内容就是如果创建并返回一个新的对象,方法上面加了一个@Bean注解,这个注解会告诉Spring这里将返回一个对象,该对象会注册到Spring的应用上下文中,这样就显式定义好了一个简单的bean。

默认情况bean的id就是带有@Bean注解的方法名,如果想指定一个不同的名字可以像下面这样配置:

可以看到通过配置类配置一个bean也是非常简单的。下面我们再来配置一个类,这个类的创建过程需要依赖上面的bean,如果在配置类中装配它们呢?先来新建一个类:

然后在配置类中配置:

这里也是返回一个new对象,虽然看上去构造方法的参数是通过调用user0071()方法得到的,但是其实并非如此 ,因为user0071()方法上面已经加了@Bean注解,因此Spring会拦截所有对它的调用,直接返回该方法创建的bean。这样就能保证每个使用user0071()方法的地方不会每次都new一个新的对象。

上面的方式可能会有点不好理解,其实还有一个更容易理解的方法:

这里user0071直接作为方法的一个参数传入,Spring在创建Role0072的bean的时候,会自动装配一个user0071的bean到方法中,然后就可以使用它。

上面这种方式通常是装配bean的最佳选择,因为不仅可以装配同一个配置类中的bean,其它类中,甚至xml文件中配置的bean,自动装配里配置的bean等都可以装配到这里,最大实现了灵活性。

这里的装配都是通过构造方法,当然还可以通过其它比如setter方法:

根据实际情况,灵活编写代码即可。

7. 通过XML装配Bean


下面来看通过xml配置的方式装配。这种方式有很长的历史,spring开始的时候,xml是最主要的方法,但是现在不是了。

使用xml与Java配置类不同,配置类需要在类上面加上@Configuration注解,xml的方式,首先要创建一个xml文件,并且要以<beans>为根元素:

可以看到,xml方式确实很麻烦,不如Java配置类的方式简单。用来装配bean的最近本的xml元素包含在spring-beans模式中,在上面这个xml文件中,它被定义为根命名空间,<beans>是该模式中的一个元素,它是所有Spring配置文件的根元素。现在我们有了一个基本的配置文件,就像一个空配置类一样,它现在没有配置任何内容。

在xml文件中配置bean需要使用<bean>元素,类似配置类中的@Bean注解,下面是一个最简单的bean:

里面通过class属性指定了创建bean的类,这里需要使用全限定的类名,因为没有配置id,所以这里会根据全限定名命名一个id,这里的id就是 “ioc008.User0081#0” ,其中#0是一种计数形式,用来相同类型的其它bean,比如下一个就是#1。虽然自动命名很方便,但是引用起来却并不方便,因此更好的做法是明确给id赋值:

注意,这里只需要在用到id属性的时候再配置即可,不用给每个都配置。把上面这个<bean>标签来和配置类中的@Bean对比,可以发现几个特点,在xml中配置bean后,你不再需要手动new一个对象,当Spring发现这个<bean>时,它会调用该类的默认构造器,不过它没有Java配置类的形式强大,在配置类中,你可以通过任意方式创建一个对象。class属性的值也是一个字符串的形式,这就要求我们自己要保证把类的全限定名写正确,这是一个隐患。而且在类改名字的时候,这里也不会提示修改,不过现在很多IDE会在编译期进行提示,要充分利用这一点。

下面来看一下如何在xml中配置构造方法的依赖注入,这里有两种风格,也就是两类标签可以选择,一个是<constructor-arg>,另一个是 以 c: 开头的标签,使用这种标签需要引入c-命名空间:

使用<constructor-arg>比c标签要冗长一些,但是<constructor-arg>标签有些注入是c标签无法实现的,下面来举例说明各种情况。

首先来看一个最简单的构造方法注入:

在xml中配置就是:

<constructor-arg>元素会告知Spring将一个id为user0082的bean引用传递到Role8801的构造方法中。作为替代方案,下面来看如何使用c命名空间的写法,首先在顶部声明其模式:

然后看一下具体写法:

它作为bean标签的一个属性,属性名以“c:”开头,也就是命名空间的前缀,接下来就是构造方法中参数的名字(user0082),在此之后是“-ref”,这是一种约定命名,它告诉Spring正在装配的是一个bean的引用,这个bean的id是user0082。显然,使用c-命名空间属性比<constructor-arg>标签要简洁一些,不过有一个问题是参数的名字也包含在其中,这样修改参数名字的时候,xml中的配置属性名也要跟着改,因此还有一种替代方案,是使用参数的位置:

这个属性名看上去更奇怪一些,把参数的名字改成了0,也就是参数的位置索引,0表示第一个参数,xml中不允许属性名以数字开头,因此前面加了下划线。上面的几种方法就是如何将一个bean装配到另一个bean中,下面来看如何装配字面量。假设构造方法中的两个参数是String类型:

来看一下xml中的装配配置:

在bean标签中,定义了两个<constructor-arg>元素,每个元素都是直接赋值,并没有应用其它bean,因此不使用ref,直接使用value属性,该属性表示给定的值以字面量的形式赋值到构造方法的参数中。除了这种方法,来看一下使用c-命名空间属性的写法:

可以看到,属性的名字如果后面是引用其它bean就加-ref,如果是直接赋值就去掉-ref,同理,使用参数位置索引的方式如下:

在普通的装配引用和字母量值方面,<constructor-arg>元素和c-命名空间属性两种方法都可以,但是有一种情况是只有<constructor-arg>才能实现的,就是参数中有集合类型,如下:

首先可以将集合的值直接设置为null,这样也算一种赋值方式:

赋值为null直接在<constructor-arg>元素内使用<null>元素即可,当然大部分情况下是需要赋值一些实际的值,这种情况因为参数是List类型,可以使用<list>标签声明一个列表:

其中<list>是<constructor-arg>的子元素,这样配置会将一个包含值的list列表传递到构造方法中,<list>下用<value>元素代表List中每个元素的值。还有一种略微麻烦的方法,可以使用<ref>元素代替<value>元素,当然有ref就表示要引用其它bean,这里先配置几个String类型的bean:

接下来引用这些bean:

参数是List集合,使用<list>元素,如何是Set集合,就使用<set>元素,

上面的几个例子中,都是只有一个带参数的构造方法,因此我们只能强制使用构造器注入的,在正常有选择的情况下,还可以使用setter方法注入,来看一个类:

如果注入不成功,say方法就无法运行,这个类既可以使用构造函数注入,也可以使用setter方法注入,下面来看setter的注入,对一个属性的注入,需要使用<property>元素,

<property>元素中,name属性表示参数的名字,ref表示引用的哪个bean,这里同样写bean的id值。我们知道,<constructor-arg>元素提供了c-命名空间属性作为替代方案,属性的注入同样如此,提供了p-命名空间作为替代方案,不过同样需要在xml中进行声明:

把上面的例子修改一下:

p-命名空间属性规则与c-命名空间十分类似,以p:开头,后面跟的是属性参数的名字加上-ref,表示引用,值填写引用的bean的id。与c-命名空间属性类似,p-命名空间属性也同样无法表示list的赋值,下面先看一个用<property>元素表示的例子,先看类:

<bean>配置如下:

与<constructor-arg>元素类似,name属性都表示参数的名字,ref表示引用其它bean,value表示直接赋值,<property>元素直接同样可以添加<list>等表示集合的元素,并对集合赋值。上面的例子使用p-命名空间属性的写法只能这样:

可见集合类型装配起来确实很别扭。这里还有一种方案,就是使用util-命名空间元素标签首先配置集合,然后再用p-命名空间属性引用,同样新的命名空间需要在xml中声明:

来看一下如何定义:

定义好以后,就可以使用:

<util:list>只是util-命名空间中的众多元素之一, 下面列出来其它元素:

8. 导入和混合配置


上面的各种方式肯定是自动化装配最受欢迎,不过在必须写配置的时候,JavaConfig类比XML更好一些,不过实际项目中并不总是按照自己想的发展,很多时候,我们写的配置类不仅可能要整合别人的jar包里写的配置类,还看需要整合别的xml配置。幸好在Spring中,这几种方案并不是互斥的,可以混合使用。来看一个例子。

首先新建一个package,然后创建一个类,并配置到Java配置类中,

然后在上面这个package同级的目录下再创建一个package(这样互不影响,可以理解为多个项目),同样创建一个类和一个配置类:

然后再创建一个同级的package,这次只创建一个普通类:

将这个类配置到xml文件中:

假设上面三种配置我们要一起使用,但是却无法修改,那么最好的办法就是,我们创建一个自己的JavaConfig类,将这三种配置都引入进来,由于我们新创建的是一个配置类,第一步要做的就是引入两个其它配置类,配置类之间的需要用到@Import注解,因此引入方法是

这样就可以将一个或者多个配置类引入到自己的配置类中,然后是引入一个xml配置,这里需要使用注解@ImportResource,配置方法如下:

这样就在我们自己的配置类中引入了其它的配置类和xml配置。定义好的bean也可以直接使用:

上面就是以JavaConfig类为主,引入其它配置类和xml文件的方式,下面来看以xml为主,引入其它xml和JavaConfig类。

首先创建一个普通的类和配置类:

然后在上面的类的package的同级package下,创建另一个类,并配置在xml中:

在第三个同级package下,新建一个引用它俩的类:

然后新建一个xml文件,将上面两个配置整合,并注入到类Role0101中,首先来看,在xml中引入其它xml,需要使用元素<import>:

在xml中引入一个Java配置类,引入这个的方式虽然我们熟悉,但是并不只管,就是直接创建一个bean:

通过上面两种方式,可以在xml中引入其它xml和JavaConfig配置类。我们讨论了很多混合配置,在实际项目中,如果配置很多且分散的话,最好的做法就是创建一个自己的总的配置类,然后将其它都引入进来,这样方便管理,如果单个的配置类或者配置文件过长,也可以根据组件拆分。

9. 总结


上面初步介绍了Spring的Bean,以及Bean的简单装配和混合装配,除了装配,就是Spring Bean的生命周期,这些是重点。

代码地址:https://gitee.com/blueses/spring-framework-demo
1-6:自动装配
7:通过Java类装配
8:通过XML配置装配
9-10:混合装配

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 158,736评论 4 362
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,167评论 1 291
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,442评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,902评论 0 204
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,302评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,573评论 1 216
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,847评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,562评论 0 197
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,260评论 1 241
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,531评论 2 245
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,021评论 1 258
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,367评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,016评论 3 235
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,068评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,827评论 0 194
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,610评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,514评论 2 269

推荐阅读更多精彩内容