内容概览
- 简化Java开发
- Spring Bean
- Spring Bean生命周期
- 装配Bean的可选方案
- 自动化装配Bean
- 通过Java配置类装配Bean
- 通过XML装配Bean
- 导入和混合配置
- 总结
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应用上下文(就是项目运行环境中)的一个典型的生命周期过程。
上面的内容可以详细描述为以下步骤:
- Spring 对Bean进行实例化
- Spring将值和Bean的引用注入到bean对应的属性中
- 如果bean实现了BeanNameAware接口,Spring将bean的id传递给setBeanName方法
- 如果bean实现了BeanFactoryAware接口,Spring将调用setBeanFactory方法,将BeanFactory容器实例传入
- 如果bean实现了ApplicationContextAware接口,Spring将调用setApplicationContext方法,将bean所在的应用上下文的引用传入进来
- 如果bean实现了BeanPostProcessor接口,Spring将调用它的postProcessBeforeInitialization方法
- 如果bean实现了InitializingBean接口,Spring将调用它的afterPropertiesSet方法,类似的,如果bean使用init-method生命了初始化方法,该方法也会被调用
- 如果bean实现了BeanPostProcessor接口,Spring将调用它的 postProcessAfterInitialization方法
- 此时,bean已经准备就绪,会一直存在于上下文中,直到该程序应用上下文环境被销毁
- 最后是销毁,如果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的方式可以使用属性:
- @ContextConfiguration(locations = {"classpath:spring-ioc004.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-命名空间:
- xmlns:c="http://www.springframework.org/schema/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:混合装配