Spring IoC容器与Bean管理
- 一.Spring IoC容器与Bean管理
- 1.Spring快速入门
- IoC控制反转
- DI依赖注入
- Spring概述
- Spring IoC初体验
- 使用XML方式实现Spring IoC
- 2.XML管理对象(Bean)
- 3.三种XML实例化Bean的配置方式:
- 基于默认无参构造方法实例化对象
- 利用带参构造方法实例化对象
- 基于静态工厂实例化对象
- 基于工厂实例方法来实例化对象
- 4.从IoC容器中获取Bean
- 5.路径表达式
- 6.对象依赖注入
- 利用setter实现静态数值注入
- 基于构造方法注入对象
- 注入集合对象
- 查看容器内对象
- 7.Bean对象的作用域及生命周期
- bean scope属性详解
- IoC的生命周期
- 8.基于注解与Java Config配置IoC容器
- 组件类型注解
- 自动装配注解
- 元数据注解
- 9.基于Java Config配置Spring IoC容器
- 10.Spring与JUnit4整合---Spring Test测试模块
从本节开始,我将要学习作为java高级阶段最最最最重要的一个框架体系,名为Spring。Spring是整个Java生态中最重要的一环。因为我也是初学,所以我的概括也不一定全面和精炼。写这一章只是为自己以后复习。
一.Spring IoC容器与Bean管理
这一小节,要学习Spring入门、Spring XML的配置、对象实例化配置、依赖注入配置、注解与Java Config、Spring实现单元测试。
1.Spring快速入门
IoC控制反转
◈ IoC控制反转,全称为Inversion of Control,是一种设计理念。
◈ 它并不是一种技术,而是一种宏观的设计理念,是现代程序设计遵循的标准,由代理人来创建与管理对象,调用者通过代理人来获取对象。IoC的目的是用来减低对象(或计算机代码)之间的直接耦合。
◈ 加入IoC容器将对象统一管理,让对象关联变为弱耦合。
下面举一个生活中的案例:
我非常喜欢吃苹果,但是摆在我面前有一件非常苦恼的事情,市场上各种各样的苹果琳琅满目,苹果有很多品种,口感和价格。假如现在在我面前放有各种各样的苹果,但我并不知道他们是什么品种,多少钱。我平时喜欢吃甜的而且脆的苹果,那么问题来了,如果不在他人介入的情况下,我想得到摆在我面前的几种苹果中最符合我口味的,该怎么做呢?是不是我就得翻页大量的资料去了解每一种水果的类型和场地或者把他们全部品尝一遍啊?这是很麻烦的一件事情。这个控制权是由我这个客户主动发起的,我必须要掌握所有对象的细节特征以后,才可以做出正确的选择。如果摆在我面前的是100种呢?除非我是专业人士,否则肯定蒙圈。
但幸运的是,有的聪明人就看到了其中的商机,随着市场经济的不断发展,各大水果超市和水果店孕育而生。水果摊是有老板的,我们去买水果的时候,我们并不需要知道这些苹果具体的特征。只需要找到水果摊的老板,和他说”哪个苹果是脆甜的啊?“ 然后老板就会告诉我”这种这种和这种苹果是脆甜的,他们的甜度各有不同,口感也各有不同。“ 由于水果摊老板常年从事这个行业,他当然比我要了解得多。我只需要听从他的意见进行采购就可以了。正是由于有水果摊老板的存在,我就把我获取对象的权力交给了水果摊老板。是由他替我做出了相应的决定。水果摊老板使我们日常生活得到了极大的便利。消费者和产品对象通过代理人进行了解耦,这种解耦带来的直接的好处就是对象之间可以灵活的变化,假如水果店老板发现市场上有了一种更脆甜的苹果,我们再去向老板购买苹果时,它就会向我推荐这种更好的苹果了,我完全不用关心它的其他特性,因为这是老板的事情,我唯一的目的就是获取到最好吃的脆甜的苹果,这就是IoC的控制反转的目的。诸如这样的例子其实还有很多。比如你来到一个全新的城市需要安居,这是要找的是中介,而不是随便地去乱找房东。通过中介地信息来选择在哪住,解决了找房子的各种各样的麻烦。这些都是软件工程里面IoC控制反转的核心体现。
·
DI依赖注入
IoC是一种宏观的设计理念,与编程语言无关,是现代程序设计遵循的标准。
DI(Dependency Injection)是具体技术实现,是微观实现。编程环境下到底使用哪些编程技术来完成在程序运行过程中对象的创建与绑定的工作。DI在不同语言中使用到的具体技术是不一样的。
DI在Java中利用反射技术实现对象注入(Injection)
·
Spring概述
Spring的含义:Spring可以从狭义和广义两个角度看待。
狭义的Spring是指Spring框架(Spring Framework) ,广义的Spring是指Spring生态体系。
狭义的Spring框架:
Spring框架是企业开发复杂性的一站式解决方案。也就是说,通过Spring框架,帮我们把以前在实际开发中那些开发体验不太友好,功能缺失的部分通过Spring的补充让整个体系更加的完整。Spring框架的核心是IoC容器与AOP面向切面编程。 IoC容器是所有对象管理的基础,AOP是建立在IoC容器的基础上才得到的。Spring IoC负责创建与管理系统对象,并在此基础上拓展功能。
广义的Spring生态体系:
Spring IoC是整个Spring生态体系中最核心,也是最基础的一部分。这是因为系统中所有的对象被统筹管理,在此基础上生根发芽,拓展出来了若干不同功能的子项目。可以说,目前的spring是一站式的综合解决方案,无论要做web应用开发,还是做安卓开发,或者是分布式应用,都是可以的。
以上就是Spring能做到的事情,在这些事情的基础上派生出来了几十种不同的项目,有Spring Boot 、Spring Framework、spring data 、spring cloud 、spring cloud data flow、spring security 、spring session、SpringGraphQL等等等,具体参考https://spring.io/projects
现阶段,我要学习的是Spring Framework这一部分。
传统的开发方式:对象直接引用导致对象硬性关联,程序难以拓展维护。
假如有一个使用者,要使用A这个对象,但是A对象实现某个功能,要额外的去new一个B对象。可以把A对象看成是一个service服务,B对象是一个Dao。这个过程是我们以前标准的开发方式。但是它也有一个巨大的弊端,当这个Dao随着我们开发的过程不断地演化,已经不适合了,由其他程序员开发了一个C对象,这是如果A要抛弃B,选择C,就要修改源代码,去new C对象才可以。这就意味着我们的程序就需要经过编译以后重新上线,才能够生效。就会涉及重新测试,重新发版,重新审批等等等,这一套流程是非常繁琐的。所以在我们的项目实践的过程中,并不推荐由具体的使用者来去new创建对应的对象。
那就要用到Spring IoC 容器了,采用被动的形式,来创建和管理这些对象,而我们使用者只是单纯的将容器中的对象进行提取就可以了。IoC是Spring生态的低基,用于统一创建与管理对象依赖。
就拿刚才的例子,如果换成Spring来做的话,首先,作为Spring,它会提供一个Spring IoC容器,这个容器其实是一个抽象的东西,相当于在我们Java的运行的内存中开辟了一段空间,这个空间是由Spring进行管理的。所有的对象都不是由我们使用者或A对象来创建,而都是由Spring IoC容器统一负责创建。创建好了以后,A对象是依赖于B对象的,当然也不会通过A对象去new B对象,而是通过反射技术,将A对象的依赖 B对象 注入到A对象中。作为使用者来说,我并不需要关注在容器内部到底有几个对象,对象之间的关系是什么样的,我只要关注在什么地方将我需要的对象提取出来就行了。也就是说不再面向具体的对象,而是面向容器,通过容器获取到需要的对象。
再拿那个水果摊的例子来比喻一下,spring IoC就是水果的仓库,spring框架就是水果摊老板,使用者就是顾客,顾客告诉水果摊老板给我来红的大的天的水果,老板就将对应符合要求的水果提取出来。
Spring IoC容器职责:
将对象的控制权交给第三方控制管理(IoC控制反转),利用java反射技术实现运行时对象创建与关联(DI依赖注入),基于配置提高应用程序的可维护性与可拓展性。
·
Spring IoC初体验
案例如下:
针对于孩子们的口味不同,我们如何让孩子直接得到自己喜欢的苹果呢?
下面通过传统的程序来演示一下:
打开Idea,新建一个空的Maven项目,项目名为s01
在entity里面创建两个java实体类,分别是苹果类Apple和孩子类Child,代码如下:
Apple.java
package com.haiexijun.ioc.entity;
public class Apple {
private String title;
private String color;
private String origin;
public Apple() {
}
public Apple(String title, String color, String origin) {
this.title = title;
this.color = color;
this.origin = origin;
}
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
}
public String getColor() {
return color;
}
public void setColor(String color) {
this.color = color;
}
public String getOrigin() {
return origin;
}
public void setOrigin(String origin) {
this.origin = origin;
}
}
Child.java
package com.haiexijun.ioc.entity;
public class Child {
private String name;
private Apple apple;
public Child() {
}
public Child(String name, Apple apple) {
this.name = name;
this.apple = apple;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Apple getApple() {
return apple;
}
public void setApple(Apple apple) {
this.apple = apple;
}
public void eat(){
System.out.println(name+"吃到了"+apple.getOrigin()+"种植的"+apple.getTitle());
}
}
```java
然后创建一个Application来编写逻辑代码:
package com.haiexijun.ioc;
import com.haiexijun.ioc.entity.Apple;
import com.haiexijun.ioc.entity.Child;
public class Application {
public static void main(String[] args) {
//创建3个苹果
Apple apple1=new Apple("红富士","红色","欧洲");
Apple apple2=new Apple("青苹果","绿色","中亚");
Apple apple3=new Apple("金帅","黄色","中国");
//创建孩子,完成了苹果与孩子两个对象的关联
Child lily=new Child("莉莉",apple1);
Child andy=new Child("安迪",apple2);
Child luna=new Child("露娜",apple3);
//孩子吃苹果
lily.eat();
andy.eat();
luna.eat();
}
}
运行后可以得到我们需要的结果。
但是,上面的这些代码在实际工作中会有很多的弊端,下面依依来分析:
首先,苹果的这些描述,都写死在程序代码中了,而我们知道,苹果的这些属性随着季节和时间会相应的变化。一旦属性发生变化后,我们就必须去修改程序的源代码,就要对应用程序进行重新发布和重新上线。而且我程序里写了3个对象,那么它就只会创建3个对象。如果新加入一个孩子,就要修改源代码,程序的可维护性和可扩展性非常不足。而且是硬关联,通过构造方法参数来创建对象进行设置,者意味着我们程序运行以后,这个孩子和苹果的关系就已经是确定了,这个确定关系是在程序编译时就完成的,这是一件非常死板的事情。举个例子,现在露娜长大了,不再喜欢此软软的金帅了,他也开始喜欢吃红富士,而且莉莉也想尝尝金帅的味道,如果按上面传统的写法,就得去修改源代码进行调整。
下一小节就用Spring IoC来实现一下这个案例。
使用XML方式实现Spring IoC
下面就将上面的代码修改为Spring IoC管理的程序,体验一下Spring IoC的强大。
在这之前,我必须要强调一下,因为这是我们第一次接触spring,这个案例的目的是为了让我们对Spring有一个感性的认知,针对于里面的各个配置以及属性,会在后面的小节里详细进行学习。
1.配置Maven依赖
spring-context是spring IoC容器最小的依赖范围,只有引入了spring-context,我们才可以对程序进行管理。
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.3.13</version>
</dependency>
引入依赖后,可以点击External Libraries看看导入了那些依赖:
2.在resources目录下创建一个xml文件,applicationContext.xml
applicationContext.xml这个文件是spring IoC的核心配置文件,所有对象的创建和关联的设置,都是在这个xml来进行的。
那么如何对这个xml进行配置呢?我们先打开spring官网,然后进入到里面的Project里的Spring Framework的帮助文档的Core部分。然后寻找到1.2.1的xml配置,复制其schema约束到配置文件中。
https://docs.spring.io/spring-framework/docs/current/reference/html/core.html#beans-factory-metadata
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd">
</beans>
然后idea也非常智能,会马上显示是否创建context。
我们点击创建,然后点击OK确定
书写bean
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd">
<!--在IoC容器启动时,自动由Spring实例化Apple,取名为sweetApple放入到容器中-->
<bean id="sweetApple" class="com.haiexijun.ioc.entity.Apple">
<property name="title" value="红富士"></property>
<property name="origin" value="欧洲"></property>
<property name="color" value="红色"></property>
</bean>
<bean id="sourApple" class="com.haiexijun.ioc.entity.Apple">
<property name="title" value="青苹果"></property>
<property name="origin" value="中亚"></property>
<property name="color" value="绿色"></property>
</bean>
<bean id="softApple" class="com.haiexijun.ioc.entity.Apple">
<property name="title" value="金帅"></property>
<property name="origin" value="中国"></property>
<property name="color" value="黄色"></property>
</bean>
</beans>
3.创建一个有main方法的java类,编写如下代码:
package com.haiexijun.ioc;
import com.haiexijun.ioc.entity.Apple;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class SpringApplication {
public static void main(String[] args) {
//加载指定的xml文件,来初始化IoC容器,context本身就指代了spring IoC容器
ApplicationContext context=new ClassPathXmlApplicationContext("classpath:applicationContext.xml");
Apple sweetApple = context.getBean("sweetApple", Apple.class);
System.out.println(sweetApple.getTitle());
}
}
运行后打印红富士。
我们对比就会发现一个非常大的好处,就是把原本的代码变成了可配置的文本,在xml文本中更改配置,不用动任何一行的源代码,就能对内容进行调整。
下面继续来写代码,演示一下对象关联。
4.在applicationContext.xml中增加3个孩子对象的bean
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd">
<!--在IoC容器启动时,自动由Spring实例化Apple,取名为sweetApple放入到容器中-->
<bean id="sweetApple" class="com.haiexijun.ioc.entity.Apple">
<property name="title" value="红富士"/>
<property name="origin" value="欧洲"/>
<property name="color" value="红色"/>
</bean>
<bean id="sourApple" class="com.haiexijun.ioc.entity.Apple">
<property name="title" value="青苹果"/>
<property name="origin" value="中亚"/>
<property name="color" value="绿色"/>
</bean>
<bean id="softApple" class="com.haiexijun.ioc.entity.Apple">
<property name="title" value="金帅"/>
<property name="origin" value="中国"/>
<property name="color" value="黄色"/>
</bean>
<bean id="lily" class="com.haiexijun.ioc.entity.Child">
<property name="name" value="莉莉"/>
<property name="apple" ref="sweetApple"/>
</bean>
<bean id="andy" class="com.haiexijun.ioc.entity.Child">
<property name="name" value="安迪"/>
<property name="apple" ref="sourApple"/>
</bean>
<bean id="luna" class="com.haiexijun.ioc.entity.Child">
<property name="name" value="露娜"/>
<property name="apple" ref="softApple"/>
</bean>
</beans>
里面的Child通过ref属性来关联苹果。
下面编写测试代码
package com.haiexijun.ioc;
import com.haiexijun.ioc.entity.Apple;
import com.haiexijun.ioc.entity.Child;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class SpringApplication {
public static void main(String[] args) {
//加载指定的xml文件,来初始化IoC容器,context本身就指代了spring IoC容器
ApplicationContext context=new ClassPathXmlApplicationContext("classpath:applicationContext.xml");
Apple sweetApple = context.getBean("sweetApple", Apple.class);
System.out.println(sweetApple.getTitle());
//从IoC容器中提取beanId=lily的对象
Child lily=context.getBean("lily",Child.class);
lily.eat();
Child andy=context.getBean("andy",Child.class);
andy.eat();
Child luna=context.getBean("andy",Child.class);
luna.eat();
}
}
运行后,都吃到了水果。
可能这里你并没有完全发现IoC的好处。但是通过xml的配置,在以后的项目中确实可以提高程序的可维护性和可拓展性。
到这里,对spring IoC的算是有了基本的认识。下面会对里面的各项配置进行详细的讲解。
·
2.XML管理对象(Bean)
在我们Spring管理的时候,说起对象,都是一个个的JavaBean。JavaBean并不是一项特殊的技术,它就是对于Java的可重用的对象的一些编码的要求。如属性私有,构造方法,getter和setter方法等。而Spring IoC容器中,管理的就是这一个个的JavaBean。所以我以后就用Bean来指代容器中的对象。
spring对于Bean的管理,包含了3种配置方式。
第一种是基于XML配置Bean、第二种是基于注解配置Bean、第三种是基于Java代码配置Bean。
这三种配置bean的方式的本质都是一样的,都是告诉Spring IoC 容器如何实例化和管理这些Bean。只是他们的表现形式不一样而已。
下面就来讲解基于XML来管理对象。还是回到上面的例子。
applicationContext.xml这个文件名是约定俗称的,一般都叫这个名字。在配置文件中,所有的对象都统一使用Bean这个标签。里面的id属性指的是指定的类被IoC容器实例化后,对象的(唯一标识)名字是什么,这里我们叫它sweetApple ,class指定要被IoC容器实例化的那个类。至于<bean>标签里面的<property>并不是必须的。在后面讲解属性注入的时候再详细解释。
spring提供了ApplicationContext来创建IoC容器。ApplicationContext是一个接口,有很多具体的实现类,其中ClassPathXmlApplicationContext这个类就实现了从当前类路径去读取指定的xml文件并解析加载的过程。ClassPathXmlApplicationContext是最常用的核心类之一。当运行了这一段代码,就意味着在我们Java的运行内存中保存了IoC容器,并且IoC容器根据刚才的配置,创建了对应的对象,并对这些对象进行管理。
ClassPathXmlApplicationContext允许加载多个xml文件的,采用传入字符串数组的形式。
String[] configLocations=new String[]{"classpath:applicationContext.xml","classpath:applicationContext-1.xml"};
ApplicationContext context=new ClassPathXmlApplicationContext(configLocations);
3.三种XML实例化Bean的配置方式:
1.基于默认无参构造方法实例化对象
2.基于静态工厂实例化对象
3.基于工厂实例方法来实例化对象
基于默认无参构造方法实例化对象
创建一个名为s02的空的Maven项目,引入依赖spring-context。和之前一样创建entity包,里面创建苹果类Apple和孩子类Child类。
然后编写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"
xsi:schemaLocation="http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd">
<!--bean标签默认通过类的默认无参构造方法创建对象-->
<bean id="apple1" class="com.haiexijun.entity.Apple">
</bean>
</beans>
在里面配置了一个Apple的对象,名字为apple1。
然后在Apple类的无参默认的构造方法里面添加一行代码:
public Apple() {
System.out.println("Apple对象已创建,"+this);
}
在有main方法的java类里编写如下代码运行,会发现Apple的默认无参构造方法
package com.haiexijun.app;
import com.haiexijun.entity.Apple;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class SpringApplication {
public static void main(String[] args) {
ApplicationContext context=new ClassPathXmlApplicationContext("classpath:applicationContext.xml");
}
}
运行结果:
那如果要用带参数的构造方法来创建对象该怎么做,下一节在进行详解。
·
利用带参构造方法实例化对象
在bean下面插入一个<constructor-arg>的子标签,在这子标签中,我们可以通过设置name属性来为带参构造方法的指定参数,用value属性来设置对应的值。
除了可以用name属性来指定构造方法的参数外,还可以用index属性来设置指定的参数,用value属性来设置值。index说明参数的前后位置。index从0开始。
实际工作中,更推荐第一种方式来传参。这里就不作演示了。但有参构造方法有多个时,比如一个类有一个3个参数的构造方法,有一个4个参数的构造方法。我们在bean中传入几个参数,程序就调用对应数量参数的构造方法。
·
基于静态工厂实例化对象
其实这种实例化对象的方式用的不多,主要还是构造方法来实例化对象。我们通常说的工厂,其实指的是工厂模式。作为工厂模式,他的根本用途就是隐藏创建类的细节。通过一个额外的工厂类来组织创建我们需要的对象。按照工厂的表现形式,又可以分为静态工厂和工厂实例。
我们通过代码案例来了解和体验基于静态工厂实例化对象。
创建一个factory子包,保存工厂类。然后创建一个java类AppleStaticFactory。里面有一个方法,用于创建甜苹果。
package com.haiexijun.factory;
import com.haiexijun.entity.Apple;
public class AppleStaticFactory {
public static Apple createSweetApple(){
Apple apple=new Apple();
apple.setTitle("红富士");
apple.setOrigin("欧洲");
apple.setColor("红色");
return apple;
}
}
然后在applicationContext.xml中调用这个工厂创建对象。
<!--利用静态工厂获取对象-->
<bean id="apple4" class="com.haiexijun.factory.AppleStaticFactory" factory-method="createSweetApple"/>
这里你可能会问了,这种方式创建对象不久和以前传统new创建对象一样吗?其实是有很大的不同的,工厂的职责就是通过静态方法来创建对象,隐藏创建对象的细节。里面如何创建对象,对于我们来说是不可见的。创建者只需要知道但调用了工厂类的createSweetApple后,会创建一个甜苹果就行了。
·
基于工厂实例方法来实例化对象
工厂实例方法创建对象是指IoC容器对工厂类进行实例化并调用对应的工厂实例方法创建对象的过程。
在factory包下创建一个java类AppleFactoryInstance , 里面的方法没有static关键字。
package com.haiexijun.factory;
import com.haiexijun.entity.Apple;
/**
* 工厂实例方法创建对象是指IoC容器对工厂类进行实例化并调用对应的工厂实例方法创建对象的过程。
*/
public class AppleFactoryInstance {
public Apple createSweetApple(){
Apple apple=new Apple();
apple.setTitle("红富士");
apple.setOrigin("欧洲");
apple.setColor("红色");
return apple;
}
}
在applicationContext.xml中调用这个工厂创建对象,这里要创建2个bean。
<!--利用工厂实例方法获取对象-->
<bean id="factoryInstance" class="com.haiexijun.factory.AppleFactoryInstance"/>
<bean id="apple5" factory-bean="factoryInstance" factory-method="createSweetApple"/>
有一说一,随着spring的功能越来越强大,在实际的使用环境中,基于工厂来创建对象的这种情况越来越少了。我们只做一般性了解就可以了。
·
4.从IoC容器中获取Bean
·
xml文件的bean有id和name属性。
id与name属性的相同点有:
bean 中的id与name都是设置对象在IoC容器中的唯一标识。本质上讲是一样的。两者在同一个配置文件中都不允许出现重复。两者允许在多个配置文件中出现重复,新对象覆盖旧对象。
但是他们也有很多不同点:id要求更加严格,一次只能定义一个对象标识。name更为宽松,一次允许定义多个对象标识。我们推荐使用id。
注意:id与name的命名要求要有意义,按驼峰命名书写。
了解:在没有进行设置id和name的情况下
<bean class="com.haiexijun.entity.Apple">
<constructor-arg index="0" value="红富士"/>
<constructor-arg index="1" value="红色"/>
<constructor-arg index="2" value="欧洲"/>
<constructor-arg index="3" value="12.1"/>
</bean>
使用全类名来作为bean的标识.
Apple apple3 = context.getBean("com.haiexijun.entity.Apple",Apple.class);
·
5.路径表达式
作为ClassPathXmlApplicationContext的参数,要求传入一个表达式。这个表达式的含义是去加载我们当前类路径下名为applicationContext的xml文件。
在源代码目录下的resources并不是我们的类路径,而是项目生成的target目录下的classes目录下才是真是的类路径。
·
ClassPathXmlApplicationContext可以加载多个配置文件,创建一个String数组,按顺序来加载xml配置文件。
6.对象依赖注入
本节,我们来学习如何在spring IoC容器中设置对象的依赖关系。这个过程我们称为依赖注入。依赖注入说白了就是将两个对象关联起来。
依赖注入是指运行时将容器内对象利用反射赋值给其他对象的操作。
利用setter实现静态数值注入
这个bean,里面包含的属性一律采用property这个标签来指代。property标签最基础的有2个属性,第一个是name,指属性名,第二个是value,指属性值。在创建好这个bean以后,便会通过反射机制调用各属性的setter方法来动态设置数值。
·
基于构造方法注入对象
·
注入集合对象
前面写的注入对象都是单个对象,这一节我要讲解List或Set等这样的集合对象注入。注意各标签的大小写。
可以用value标签来设置具体的数值,或者ref引入具体的bean。
list和set的区别是list里面的元素不允许出现重复,而set里面的元素可以出现重复。
properties只允许key和value是字符串类型的。
下面是一个样例:
<bean id="company" class="com.haiexijun.ioc.entity.Company">
<property name="rooms">
<list>
<value>201-总裁办</value>
<value>202-总经理办公室</value>
<value>203-研发部会议室</value>
<value>203-研发部会议室</value>
</list>
</property>
<property name="computers">
<map>
<entry key="dev-112" value-ref="computer1"></entry>
<entry key="dev-113">
<bean class="com.haiexijun.ioc.entity.Computer">
<constructor-arg name="brand" value="联想"/>
<constructor-arg name="price" value="2222"/>
<constructor-arg name="sn" value="12312412"/>
<constructor-arg name="type" value="台式机"/>
</bean>
</entry>
</map>
</property>
<property name="info">
<props>
<prop key="phone">13214124</prop>
<prop key="address">zjian</prop>
</props>
</property>
</bean>
<bean id="computer1" class="com.haiexijun.ioc.entity.Computer">
<constructor-arg name="brand" value="联想"/>
<constructor-arg name="price" value="2222"/>
<constructor-arg name="sn" value="12312412"/>
<constructor-arg name="type" value="台式机"/>
</bean>
这里就不作过多的解释了。
·
查看容器内对象
前面学习了如何在容器内创建对象,以及设计对象的关系。但是所有这些信息都是通过脑补来完成的。如果一个工程越来越大,
对象越来越多,那我们如何知道当前容器中到底有多少对象,这些对象又是什么类型呢?
可以通过ApplicationContext对象的getBeanDefinitionNames()方法获取到bean的名字的String数组。通过类对象.getClass().getName()来获取全类名,如context.getBean(beanName).getClass().getName()。如果要获取实体类具体的内容,用实体类对象.toString() 方法。
·
·
7.Bean对象的作用域及生命周期
·
bean scope属性详解
scope的原意是范围的意思。
bean scope属性用于决定对象何时被创建与作用的范围。通过设置 scope属性会影响到容器内对象的数量。默认情况下bean会在IoC容器创建后自动实例化,全局唯一。
下面是singleton单例示意图
单例的执行效率高,不用多次创建同一个对象,但也会引发很多线程安全问题。
下面是prototype多例示意图:
·
·
singleton 与 prototype对比:
·
IoC的生命周期
·
·
下面写一个案例来演示IoC的生命周期:
配置好了项目后,创建一个entity实体类,名为Order:
package com.haiexijun.ioc.entity;
public class Order {
private Float price;
private Integer quantity;
private Float total;
public Order(){
System.out.println("创建Order对象"+this);
}
public void init(){
System.out.println("执行Init方法");
total=price*quantity;
}
public void destroy(){
System.out.println("销毁容器,释放Order对象相关的资源");
}
public void pay(){
System.out.println("订单金额为:"+total);
}
public Float getPrice() {
return price;
}
public void setPrice(Float price) {
System.out.println("设置price:"+price);
this.price = price;
}
public Integer getQuantity() {
return quantity;
}
public void setQuantity(Integer quantity) {
System.out.println("设置quantity:"+quantity);
this.quantity = quantity;
}
public Float getTotal() {
return total;
}
public void setTotal(Float total) {
this.total = total;
}
}
然后在applicationContext.xml文件中编写bean
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="order1" class="com.haiexijun.ioc.entity.Order" init-method="init" destroy-method="destroy">
<property name="price" value="19.8"/>
<property name="quantity" value="1000"/>
</bean>
</beans>
会发现,我并没有在bean中为total属性设置属性和属性值,而是通过init-method属性指定order类里面的init方法来计算total来设置total属性的。我们还定义了一个destroy-method的属性,用于指定IoC容器被销毁后执行的方法,一般用于释放与该对象关联的资源。
最后编写代码运行:
package com.haiexijun.ioc;
import com.haiexijun.ioc.entity.Order;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class SpringApplication {
public static void main(String[] args) {
ApplicationContext context=new ClassPathXmlApplicationContext("classpath:applicationContext.xml");
System.out.println("====IoC容器以初始化完成====");
Order order= context.getBean("order1", Order.class);
order.pay();
//销毁容器,会自动调用xml中设置的destroy-method方法
((ClassPathXmlApplicationContext)context).registerShutdownHook();
}
}
运行结果如下:
·
·
8.基于注解与Java Config配置IoC容器
基于注解的优势:
拜托繁琐的XML形式的bean与依赖注入配置。基于”声明式“的原则,更适合轻量级的现代企业应用。让代码可读性变得更好,研发人员拥有更好的开发体验。
Spring三类注解:
组件类型注解:声明当前类的功能与职责。
自动装配注解:根据属性特征自动注入对象
元数据注解:更细化的辅助IoC容器管理对象
组件类型注解
四种组件类型注解
注解 | 说明 |
---|---|
@Component | 组件注解,通用注解,被该注解描述的类将被IoC容器管理并实例化 |
@Controller | 语义注解,说明当前类是MVC应用程序中的控制器类 |
@Service | 语义注解,说明当前类是Service业务逻辑类 |
@Repository | 语义注解,说明当前类用于业务持久层,通常描述对应Dao类 |
这些注解如果要被Spring识别的话,还要配置开启组件扫描:
<context:component-scan base-package="项目的包名">
<!--可选,排除不想被扫描的包-->
<context:exclude-filter type="regex" expression="包名"/>
</context:component-scan>
下面用一个案例来演示一下组件注解:
创建一个项目,配置好依赖后,在resources目录下创建applicationContext.xml文件。你可可能会有疑问,为什么明明用注解了,还要这个xml文件干什么。这是因为一些最基本的配置,我们还是要写在这个xml配置文件中。而且基于注解的xml约束和之前的xml约束不太一样,我们要复制官网文档1.9的https://docs.spring.io/spring-framework/docs/current/reference/html/core.html#beans-annotation-config的注解的schema配置。
<?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
https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
https://www.springframework.org/schema/context/spring-context.xsd">
<context:component-scan base-package="扫描注解的包名"/>
</beans>
下面以是一个案例:
<?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
https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
https://www.springframework.org/schema/context/spring-context.xsd">
<context:component-scan base-package="com.haiexijun"/>
</beans>
我们指定扫描com.haiexijun包下面的spring注解。
然后,在dao包下面创建一个UserDao的类,我们对其使用@Repository注解。
package com.haiexijun.ioc.dao;
import org.springframework.stereotype.Repository;
//组件类型注解默认的beanId为首字母小写(如:下面UserDao的beanId默认为userDao)
//也可以向@Repository()被传入自定义的beanId(如:@Repository("udao"))
@Repository
public class UserDao {
public UserDao(){
}
}
只需要在类名前面书写@Repository就行,这里要注意一个点,组件类型注解默认的beanId为首字母小写(如:下面UserDao的beanId默认为userDao)
我们编写main方法演示一下:
package com.haiexijun.ioc;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class SpringApplication {
public static void main(String[] args) {
ApplicationContext context=new ClassPathXmlApplicationContext("classpath:applicationContext.xml");
String[] ids=context.getBeanDefinitionNames();
for (String id:ids){
System.out.println(id+":"+context.getBean(id));
}
}
}
这个main方法打印了IoC容器内创建的所有的bean对象,运行结果如下:
注意,这里没有给UserDao设置beanId,所以spring会默认使他的beanId为首字母小写。
我们可以用@Repository(“udao”)为其设置我们自定义的beanId
此时的运行结果如下:
如果要给service业务逻辑类添加注解,就用@Service注解
package com.haiexijun.ioc.service;
import org.springframework.stereotype.Service;
@Service
public class UserService {
}
如果要对控制器用注解,就用@controller注解
package com.haiexijun.ioc.controller;
import org.springframework.stereotype.Controller;
@Controller
public class UserController {
}
如果要为一个工具类添加注解,spring并没有为工具类的注解,所以用@Component这个注解就可以
package com.haiexijun.ioc.utils;
import org.springframework.stereotype.Component;
@Component
public class StringUtils {
}
所有的注解,都可以为其设置自定义的beanId。而且这些bean在容器中都是单例的。
自动装配注解
两种自动装配注解,自动装配注解就是为了让我们在IoC容器运行的过程中自动地为某个属性注入数据,是为依赖注入所存在的。
在行业中按类型装配不推荐使用,更多鼓励按名称装配。@Named要于前面的@Inject要配合使用。@Resource注解会先按名称进行依赖注入,但名称不满足时,会再按照类型来进行依赖注入。@Resource是功能最强大的自动装配注解了。
下面进入代码演示的环节:
我们回到上面那个案例,再学习完MVC以后我们都知道,作为MVC是采用分层的方式依次地逐层进行调用。就是controller依赖于service,而service依赖于dao。
下面来简单了解一下@Autowired使用就行
package com.haiexijun.ioc.service;
import com.haiexijun.ioc.dao.UserDao;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
@Service
public class UserService {
@Autowired
private UserDao userDao;
}
@Autowired注解用于要注入的属性上使用就行。要注意,这种注解是按类型来注入属性的,如果有两个类的实现于UserDao接口的话,它就会报错,解决方法,是把实现类的@Repository注解去掉,或在要注入的类的@Repository注解后添加一个@primary注解就行,@Autowired会优先注入有@primary注解的类。
下面要演示一下@Resource注解的基本使用:
如果@Resource注解设置name属性,则按name在IoC容器中注入。如果@Resource注解没有设置name属性,会以属性名作为bean的name在IoC容器中匹配bean,如果有匹配,则注入。按属性名未匹配,则按类型进行匹配,同@Autowired一样,需要加入@Primary注解来解决类型冲突。使用建议:在使用@Resource时推荐设置name或者保证属性名与bean名称一致。
package com.haiexijun.ioc.service;
import com.haiexijun.ioc.dao.UserDao;
import javax.annotation.Resource;
import org.springframework.stereotype.Service;
@Service
public class DepService {
@Resource
private UserDao userDao;
}
无论是@Autowired还是@Resource,它们都可以基于不使用setter方法来完成对象的注入。他们的本质都是在运行时,采用反射机制,将要注入的属性从private改为public,再完成属性的直接赋值,赋值完以后,再将其改回到private。
元数据注解
它的作用就是为Spring IoC容器管理对象时提供一些辅助信息。
@Scope和之前xml里的scope是一个用法,设置单例和多例。singleton和prototype。
这里不方便演示,以后项目中回顾。
9.基于Java Config配置Spring IoC容器
Java Config是再Spring3.0以后推出的一种全新的配置方式,他的主要原理是使用Java代码来替代传统的XML文件。
基于Java Config的优势:完全拜托了XML的束缚,使用独立Java类管理对象与依赖。注解相对分散,利用Java Config可以对配置集中管理。可以在编译时进行依赖检查,不容易出错。基于Java Config的注解配置拥有更好的开发体验,而基于xml的配置则拥有更好的可维护性。我们在实际项目中根据不同的情况,选择不同的配置方案。
Java Config核心注解
重新创建一个工程来进行演示基本使用:
分别创建dao,service,controller包,然后创建对应的java类,如下:
UserDao.java
package com.haiexijun.dao;
public class UserDao {
}
UserService.java
package com.haiexijun.service;
import com.haiexijun.dao.UserDao;
public class UserService {
private UserDao userDao;
//作为要使用Java Config,要保留getter和setter
public UserDao getUserDao() {
return userDao;
}
public void setUserDao(UserDao userDao) {
this.userDao = userDao;
}
}
UserController.java
package com.haiexijun.controller;
import com.haiexijun.service.UserService;
public class UserController {
private UserService userService;
public UserService getUserService() {
return userService;
}
public void setUserService(UserService userService) {
this.userService = userService;
}
}
要使用Java Config,要保留其getter和setter方法。
然后只要再创建一个Config类来替代xml进行配置:
用@configuration注解来标识这是一个Config配置类,里面把要管理的bean用@bean注解标注,Java Config利用方法创建对象,将方法返回对象放入容器中,beanId为方法名。
package com.haiexijun;
import com.haiexijun.controller.UserController;
import com.haiexijun.dao.UserDao;
import com.haiexijun.service.UserService;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
//当前类是一个配置类,用于替代applicationContext.xml
@Configuration
public class Config {
//Java Config利用方法创建对象,将方法返回对象放入容器中,beanId为方法名
@Bean
public UserDao userDao(){
UserDao userDao=new UserDao();
return userDao;
}
@Bean
public UserService userService(){
UserService userService=new UserService();
return userService;
}
@Bean
public UserController userController(){
UserController userController=new UserController();
return userController;
}
}
然后在main方法里面编写代码演示:
package com.haiexijun;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
public class SpringApplication {
public static void main(String[] args) {
//这里使用创建AnnotationConfigApplicationContext对象来初始话IoC容器
ApplicationContext context=new AnnotationConfigApplicationContext(Config.class);
String[] ids= context.getBeanDefinitionNames();
for (String id:ids){
System.out.println(id+":"+context.getBean(id));
}
}
}
这里不再是使用ClassPathXmlApplicationContext来创建IoC容器,而是使用AnnotationConfigApplicationContext来创建IoC容器了。然后我便利了一下IoC容器管理的Bean的相关信息,运行结果如下:
上面只是介绍了如何配置使IoC容器管理Bean,下面来学习配置对象依赖注入。
对上面的Config类的代码进行更改:
package com.haiexijun;
import com.haiexijun.controller.UserController;
import com.haiexijun.dao.UserDao;
import com.haiexijun.service.UserService;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
//当前类是一个配置类,用于替代applicationContext.xml
@Configuration
public class Config {
//Java Config利用方法创建对象,将方法返回对象放入容器中,beanId为方法名
@Bean
public UserDao userDao(){
UserDao userDao=new UserDao();
System.out.println("已创建userDao");
return userDao;
}
@Bean
//会按name尝试注入,name不存在则按类型注入
public UserService userService(UserDao userDao){
UserService userService=new UserService();
System.out.println("已创建userServices");
userService.setUserDao(userDao);
System.out.println("调用setUserDao"+userDao);
return userService;
}
@Bean
public UserController userController(UserService userService){
UserController userController=new UserController();
System.out.println("已创建userController");
userController.setUserService(userService);
System.out.println("调用setUserService"+userService);
return userController;
}
}
要实现依赖注入,只需要在@Bean的方法里面传入要注入的对象。它会按name尝试注入,name不存在则按类型注入。
然后编写main方法测试:
package com.haiexijun;
import com.haiexijun.controller.UserController;
import com.haiexijun.dao.UserDao;
import com.haiexijun.service.UserService;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
//当前类是一个配置类,用于替代applicationContext.xml
@Configuration
public class Config {
//Java Config利用方法创建对象,将方法返回对象放入容器中,beanId为方法名
@Bean
public UserDao userDao(){
UserDao userDao=new UserDao();
System.out.println("已创建userDao");
return userDao;
}
@Bean
//会按name尝试注入,name不存在则按类型注入
public UserService userService(UserDao userDao){
UserService userService=new UserService();
System.out.println("已创建userServices");
userService.setUserDao(userDao);
System.out.println("调用setUserDao"+userDao);
return userService;
}
@Bean
public UserController userController(UserService userService){
UserController userController=new UserController();
System.out.println("已创建userController");
userController.setUserService(userService);
System.out.println("调用setUserService"+userService);
return userController;
}
}
运行结果如下:
当然,上面说到spring它会先按name尝试注入,name不存在则按类型注入。那我们试试name不存在时,看看如何运行:
比如我们对Config类的一些代码进行更改,我们把要注入对象的参数的名字从userDao改为udao:
@Bean
//会按name尝试注入,name不存在则按类型注入
public UserService userService(UserDao udao){
UserService userService=new UserService();
System.out.println("已创建userServices");
userService.setUserDao(userDao);
System.out.println("调用setUserDao"+userDao);
return userService;
}
然后运行,其实也是不会报错的。因为spring他会在去找容器内相同类型的bean进行注入。
但是,如果在传入要注入的bean的name不正确的情况下,容器中又存在两个注入类型相同的bean,就会报错了,比如我们下面创建两个UserDao的bean。
@Bean
public UserDao userDao(){
UserDao userDao=new UserDao();
System.out.println("已创建userDao");
return userDao;
}
@Bean
public UserDao userDao1(){
UserDao userDao=new UserDao();
System.out.println("已创建userDao");
return userDao;
}
@Bean
//会按name尝试注入,name不存在则按类型注入
public UserService userService(UserDao udao){
UserService userService=new UserService();
System.out.println("已创建userServices");
userService.setUserDao(udao);
System.out.println("调用setUserDao"+udao);
return userService;
}
这时候运行main方法就会报错了。
它显示发现两个相同类型的Bean,它不知道该注入哪一个bean。
解决这个问题的办法有很多,比如给给其中一个bean添加一个@Primary的注解。使其优先选择被注入。
@Bean
public UserDao userDao(){
UserDao userDao=new UserDao();
System.out.println("已创建userDao");
return userDao;
}
@Bean
@Primary
public UserDao userDao1(){
UserDao userDao=new UserDao();
System.out.println("已创建userDao");
return userDao;
}
@Bean
//会按name尝试注入,name不存在则按类型注入
public UserService userService(UserDao udao){
UserService userService=new UserService();
System.out.println("已创建userServices");
userService.setUserDao(udao);
System.out.println("调用setUserDao"+udao);
return userService;
}
运行结果如下,这时就不会报错了:
下面介绍用@Scope注解配置bean的作用范围为singleton或prototype
@Bean
@Scope("prototype")
public UserController userController(UserService userService){
UserController userController=new UserController();
System.out.println("已创建userController");
userController.setUserService(userService);
System.out.println("调用setUserService"+userService);
return userController;
}
其实Java Config可以和注解配置一起使用,只需要配置@ComponentScan注解,代表扫描包内组件的注解,里面用basePackages来指定要扫描注解的包。
@Configuration
@ComponentScan(basePackages = "com.haiexijun")
public class Config {
//Java Config利用方法创建对象,将方法返回对象放入容器中,beanId为方法名
@Bean
public UserDao userDao(){
```````````````
}
10.Spring与JUnit4整合—Spring Test测试模块
Spring Test 是Spring中用于测试的模块,Spring Test对JUnit单元测试框架有良好的整合。在日常开发中,最常用的一个功能就是与JUnit单元测试进行整合,通过Spring Test这个模块可以在JUnit单元单元测试时自动初始化IoC容器。
Spring与JUnit4的整合过程
1.配置Maven工程依赖spring-test和junit4的依赖
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>5.3.13</version>
</dependency>
2.利用@RunWith与@ContextConfiguration描述测试用例类。通过@RunWith可以让spring接管JUnit4的控制权,完成IoC的初始化工作,@ContextConfiguration则用于说明在初始化IoC容器过程中到底要加载哪个配置文件。
3.测试用例类从容器获取对象完成测试用例的执行。
下面来演示一下:
创建好userDao类和UserService类后,配置xml文件
package com.haiexijun.ioc.dao;
public class UserDao {
public void insert(){
System.out.println("插入一条数据");
}
}
package com.haiexijun.ioc.service;
import com.haiexijun.ioc.dao.UserDao;
public class UserService {
private UserDao userDao;
public void createUser(){
System.out.println("调用创建用户的业务代码");
userDao.insert();
}
public UserDao getUserDao() {
return userDao;
}
public void setUserDao(UserDao userDao) {
this.userDao = userDao;
}
}
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="userDao" class="com.haiexijun.ioc.dao.UserDao">
</bean>
<bean id="userService" class="com.haiexijun.ioc.service.UserService">
<property name="userDao" ref="userDao"/>
</bean>
</beans>
之后再test包下面创建一个测试用例类SpringTestor:
import com.haiexijun.ioc.service.UserService;
import org.junit.Test;
import org.junit.runner.RunWith;
import javax.annotation.*;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
//将Junit4的执行权交给Spring Test,再测试用例执行前自动初始化IoC容器
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = {"classpath:applicationContext.xml"})
public class SpringTestor {
@Resource
private UserService userService;
@Test
public void testUserService(){
userService.createUser();
}
}