Spring框架笔记(三)——Spring容器、属性注入和构造器注入详解

Spring 容器

在 Spring IOC 容器读取 Bean 配置创建 Bean 实例之前, 必须对它进行实例化. 只有在容器实例化后, 才可以从 IOC 容器里获取 Bean 实例并使用.

Spring 提供了两种类型的 IOC 容器实现.

BeanFactory: IOC 容器的基本实现.

ApplicationContext: 提供了更多的高级特性. 是 BeanFactory 的子接口.

BeanFactory 是 Spring 框架的基础设施,面向 Spring 本身;

ApplicationContext 面向使用 Spring 框架的开发者,几乎所有的应用场合都直接使用 ApplicationContext 而非底层的 BeanFactory

无论使用何种方式, 配置文件时相同的.

ApplicationContext 的主要实现类:

ClassPathXmlApplicationContext:从 类路径下加载配置文件

FileSystemXmlApplicationContext: 从文件系统中加载配置文件

ConfigurableApplicationContext 扩展于 ApplicationContext,新增加两个主要方法:refresh() 和 close(), 让 ApplicationContext 具有启动、刷新和关闭上下文的能力。(后面的博文会提到Bean的声明周期方法的时候会详细介绍)

ApplicationContext 在初始化上下文时就实例化所有单例的 Bean。(顺便提一下,是Bean可以不是单实例的,这个等到Bean的作用域大的时候再说)

WebApplicationContext 是专门为 WEB 应用而准备的,它允许从相对于 WEB 根目录的路径中完成初始化工作

我们之前用的ClassPathXmlApplicationContext对象的getBean方法其实源自接口BeanFactory。

在 Spring 的 IOC 容器里配置 Bean(在applicationContext.xml中配置)

<bean id="BKBeanId" class="com.happBKs.spring.iocaop.beans.BKBean">
		<property name="name" value="defaultName"></property>
	</bean>

id是Bean 的名称,之前我在博文中已经介绍过,这里我们对其细节归纳为以下三点:

1. 在 IOC 容器中必须是唯一的

2. 若 id 没有指定,Spring 自动将权限定性类名作为 Bean 的名字

3. id 可以指定多个名字,名字之间可用逗号、分号、或空格分隔

使用Spring IOC容器的方法如下:

@Test
	public void test2()
	{
		//创建spring IOC容器对象。ClassPathXmlApplicationContext表示配置文件在类路径下,它是接口ApplicationContext的一个实现类。实现类从类路径下加载配置文件。
		ApplicationContext ctx=new ClassPathXmlApplicationContext("applicationContext.xml");
		//从IOC容器中获取Bean实例
		BKBean dfBean=(BKBean)ctx.getBean("BKBeanId");
		//调用对象方法
		dfBean.doJob();
	}

这个BeanFactory的getBean方法有多个重载方法,我们还可以利用Bean的类来进行getBean

@Test
	public void test5()
	{
		//创建spring IOC容器对象。ClassPathXmlApplicationContext表示配置文件在类路径下,它是接口ApplicationContext的一个实现类。
		ApplicationContext ctx=new ClassPathXmlApplicationContext("applicationContext.xml");
		//从IOC容器中获取Bean实例
		BKBean dfBean=(BKBean)ctx.getBean(BKBean.class);
		//调用对象方法
		dfBean.doJob();
	}

但是这里运行的时候报错了!!

org.springframework.beans.factory.NoUniqueBeanDefinitionException: No qualifying bean of type [com.happBKs.spring.iocaop.beans.BKBean] is defined: expected single matching bean but found 4: BKBeanId,BKBeanId_BK1,BKBeanId_BK2,BKBeanId_BK3
	at org.springframework.beans.factory.support.DefaultListableBeanFactory.getBean(DefaultListableBeanFactory.java:365)
	at org.springframework.beans.factory.support.DefaultListableBeanFactory.getBean(DefaultListableBeanFactory.java:331)
	at org.springframework.context.support.AbstractApplicationContext.getBean(AbstractApplicationContext.java:968)
	at com.happBKs.spring.iocaop.TestApp1.test5(TestApp1.java:65)
	at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
	at sun.reflect.NativeMethodAccessorImpl.invoke(Unknown Source)
	at sun.reflect.DelegatingMethodAccessorImpl.invoke(Unknown Source)
	at java.lang.reflect.Method.invoke(Unknown Source)
	at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:45)
	at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:15)
	at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:42)
	at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:20)
	at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:263)
	at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:68)
	at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:47)
	at org.junit.runners.ParentRunner$3.run(ParentRunner.java:231)
	at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:60)
	at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:229)
	at org.junit.runners.ParentRunner.access$000(ParentRunner.java:50)
	at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:222)
	at org.junit.runners.ParentRunner.run(ParentRunner.java:300)
	at org.eclipse.jdt.internal.junit4.runner.JUnit4TestReference.run(JUnit4TestReference.java:50)
	at org.eclipse.jdt.internal.junit.runner.TestExecution.run(TestExecution.java:38)
	at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:459)
	at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:675)
	at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.run(RemoteTestRunner.java:382)
	at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.main(RemoteTestRunner.java:192)

原因是我们的容器中,即applicationContext.xml中存在多个Bean的class制定的类是相同的。

<?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 http://www.springframework.org/schema/beans/spring-beans.xsd">

	<bean id="BKBeanId" class="com.happBKs.spring.iocaop.beans.BKBean">
		<property name="name" value="defaultName"></property>
	</bean>

	<bean id="BKBeanId_BK1" class="com.happBKs.spring.iocaop.beans.BKBean">
		<property name="name" value="BK1"></property>
	</bean>
	<bean id="BKBeanId_BK2" class="com.happBKs.spring.iocaop.beans.BKBean">
		<property name="name" value="BK2"></property>
	</bean>
	<bean id="BKBeanId_BK3" class="com.happBKs.spring.iocaop.beans.BKBean">
		<property name="name" value="BK3"></property>
	</bean>

</beans>

因此,getBean无法知道应该加载那个bean的对象。所以我们需要其他相同类的bean去除。

注意:当applicationContext.xml中有多个相同类型的bean时,请不要使用class的这种getBean的方法,而是使用id来getBean。即利用类型返回IOC容器中的bean,但要求IOC容器中只有一个该类型的bean。

依赖注入的方式

Spring 支持 3 种依赖注入的方式

属性注入

构造器注入

工厂方法注入(很少使用,不推荐)

属性注入

属性注入即通过 setter 方法注入Bean 的属性值或依赖的对象

属性注入使用 <property> 元素, 使用 name 属性指定 Bean 的属性名称,value 属性或 <value> 子节点指定属性值

属性注入是实际应用中最常用的注入方式

这种方式,实际我们之前的例已经介绍过了,这里不再累述。

构造方法注入

通过构造方法注入Bean 的属性值或依赖的对象,它保证了 Bean 实例在实例化后就可以使用。

构造器注入在 <constructor-arg> 元素里声明属性

我们现在先构造一个Car的bean类

package com.happBKs.spring.iocaop.beans;

public class Car {
	String name;
	double price;
	public String getName() {
		return name;
	}
	public void setName(String name) {
		this.name = name;
	}
	public double getPrice() {
		return price;
	}
	public void setPrice(double price) {
		this.price = price;
	}
	public Car() {
		super();
	}
	public Car(String name1, double price1) {
		super();
		this.name = name1;
		this.price = price1;
	}
	@Override
	public String toString() {
		return "Car [name=" + name + ", price=" + price + "]";
	}
}

这里有三种方法,来实现构造器注入的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 http://www.springframework.org/schema/beans/spring-beans.xsd">

	<bean id="car_id" class="com.happBKs.spring.iocaop.beans.Car">
		<constructor-arg value="BM"></constructor-arg>
		<constructor-arg value="500000"></constructor-arg>
	</bean>

	<bean id="car_Index_id" class="com.happBKs.spring.iocaop.beans.Car">
		<constructor-arg value="400000" index="1"></constructor-arg>
		<constructor-arg value="BM" index="0"></constructor-arg>
	</bean>

	<bean id="car_name_id" class="com.happBKs.spring.iocaop.beans.Car">
		<constructor-arg name="price1" value="300000"></constructor-arg>
		<constructor-arg name="name1" value="BM"></constructor-arg>
	</bean>

</beans>

第一种方式:利用构造器的参数顺序,依次排列各个construcctor-arg

第二种方式:用index属性指定构造器参数的顺序,从0开始

第三种方式:构造器参数的名字来指定是哪一个参数赋值

测试方法:

@Test
	public void test6_1()
	{
		//创建spring IOC容器对象。ClassPathXmlApplicationContext表示配置文件在类路径下,它是接口ApplicationContext的一个实现类。
		ApplicationContext ctx=new ClassPathXmlApplicationContext("applicationContext.xml");
		//从IOC容器中获取Bean实例
		Car cBean=(Car)ctx.getBean("car_id");
		//调用对象方法
		System.out.println(cBean);
	}

	@Test
	public void test6_2()
	{
		//创建spring IOC容器对象。ClassPathXmlApplicationContext表示配置文件在类路径下,它是接口ApplicationContext的一个实现类。
		ApplicationContext ctx=new ClassPathXmlApplicationContext("applicationContext.xml");
		//从IOC容器中获取Bean实例
		Car cBean=(Car)ctx.getBean("car_Index_id");
		//调用对象方法
		System.out.println(cBean);
	}

	@Test
	public void test6_3()
	{
		//创建spring IOC容器对象。ClassPathXmlApplicationContext表示配置文件在类路径下,它是接口ApplicationContext的一个实现类。
		ApplicationContext ctx=new ClassPathXmlApplicationContext("applicationContext.xml");
		//从IOC容器中获取Bean实例
		Car cBean=(Car)ctx.getBean("car_name_id");
		//调用对象方法
		System.out.println(cBean);
	}

运行结果1:

Car [name=BM, price=500000.0]

运行结果2:

Car [name=BM, price=400000.0]

运行结果3:

Car [name=BM, price=300000.0]

注意:方式三的constructor-arg的name属性必须与bean的构造方法的形参名称一样,而不是和bean的属性名一样,需要特别注意!

构造方法的重载问题

好,现在我们还说明一个问题。请看这样一个例子。现在有一个bean, Car2的定义如下:

package com.happBKs.spring.iocaop.beans;

public class Car2 {
	String name;
	double price;
	int version;
	double speed;
	public String getName() {
		return name;
	}
	public void setName(String name) {
		this.name = name;
	}
	public double getPrice() {
		return price;
	}
	public void setPrice(double price) {
		this.price = price;
	}
	public int getVersion() {
		return version;
	}
	public void setVersion(int version) {
		this.version = version;
	}
	public double getSpeed() {
		return speed;
	}
	public void setSpeed(double speed) {
		this.speed = speed;
	}
	public Car2() {
		super();
	}
	public Car2(String name, double price, int version) {
		super();
		this.name = name;
		this.price = price;
		this.version = version;
	}
	public Car2(String name, double price, double speed) {
		super();
		this.name = name;
		this.price = price;
		this.speed = speed;
	}
	public Car2(String name, double price, int version, double speed) {
		super();
		this.name = name;
		this.price = price;
		this.version = version;
		this.speed = speed;
	}
	@Override
	public String toString() {
		return "Car2 [name=" + name + ", price=" + price + ", version="
				+ version + ", speed=" + speed + "]";
	}

}

我们的容器配制如下: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 http://www.springframework.org/schema/beans/spring-beans.xsd">

	<bean id="car2_id" class="com.happBKs.spring.iocaop.beans.Car2">
		<constructor-arg value="BM" index="0"></constructor-arg>
		<constructor-arg value="500000" index="1"></constructor-arg>
		<constructor-arg value="1" index="2"></constructor-arg>
	</bean>

</beans>

现在我们尝试调用测试:使用赋予参数name、price、version的构造器:

@Test
	public void test7()
	{
		//创建spring IOC容器对象。ClassPathXmlApplicationContext表示配置文件在类路径下,它是接口ApplicationContext的一个实现类。
		ApplicationContext ctx=new ClassPathXmlApplicationContext("applicationContext.xml");
		//从IOC容器中获取Bean实例
		Car2 c2Bean=(Car2)ctx.getBean("car2_id");
		//调用对象方法
		System.out.println(c2Bean);
	}

但是运行结果却是:

Car2 [name=BM, price=500000.0, version=0, speed=1.0]

getBean返回的使容器中由构造器public Car2(String name, double price, double speed)返回的对象。这就是构造器重载可能带来的问题。

解决方法是:

方法1:利用constructor-arg标签中的type属性,指定参数类型。

将刚才的容器配制改为:

<bean id="car2_id" class="com.happBKs.spring.iocaop.beans.Car2">
		<constructor-arg value="BM" index="0"></constructor-arg>
		<constructor-arg value="500000" index="1"></constructor-arg>
		<constructor-arg value="1" index="2" type="int"></constructor-arg>
	</bean>

在运行test7测试方法:

Car2 [name=BM, price=500000.0, version=1, speed=0.0]

这样就OK了。

方法2:利用constructor-arg标签中的name属性,指定参数名称。

<bean id="car2_id" class="com.happBKs.spring.iocaop.beans.Car2">
		<constructor-arg value="BM" index="0"></constructor-arg>
		<constructor-arg value="500000" index="1"></constructor-arg>
		<constructor-arg value="1" index="2" name="version"></constructor-arg>
	</bean>

在运行test7测试方法:

Car2 [name=BM, price=500000.0, version=1, speed=0.0]

大家记住spring的容器对应于applicationContext.xml配制文件,容器构造对象时在加载该配制文件的时候,而不是实际getBean的时候。

时间: 2024-10-15 22:59:18

Spring框架笔记(三)——Spring容器、属性注入和构造器注入详解的相关文章

Spring MVC笔记(三) Spring MVC表单处理

创建动态WEB工程 FormHandling,并添加SpringMVC相关jar包(同Hello world示例一致),添加DispatcherServlet配置,如下: web.xml 1 <?xml version="1.0" encoding="UTF-8"?> 2 <web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http

Spring框架笔记(四)——Spring容器的属性配置详解的六个专题

在spring IOC容器的配置文件applicationContext.xml里,有一些配置细节值得一提.我们将一些问题归结为以下几个专题. 专题一:字面值问题 配置的bean节点中的值,我们提出一个概念--字面值. 字面值:可用字符串表示的值. 字面值可以通过 <value> 元素标签或 value 属性进行注入. 基本数据类型及其封装类.String 等类型都可以采取字面值注入的方式 若字面值中包含特殊字符,可以使用 <![CDATA[]]> 把字面值包裹起来. 例如:(本文

spring的属性注入和构造器注入

spring在向IOC容器中注入Bean的时候,有三种注入方式: 属性注入构造器注入工厂方法注入平常中用到的前两种方法较多,下面对前两种方法举例.一.属性注入1.创建一个car类,作为注入的bean package com.lzj.spring;public class Car { private String brand; private float price; public String getBrand() { return brand; } public void setBrand(S

Spring Data 系列(三) Spring+JPA(spring-data-commons)

本章是Spring Data系列的第三篇.系列文章,重点不是讲解JPA语法,所以跑开了JPA的很多语法等,重点放在环境搭建,通过对比方式,快速体会Spring 对JPA的强大功能. 准备代码过程中,保持了每个例子的独立性,和简单性,准备的源码包,下载即可使用.如果,对JPA语法想深入研究的话,直接下载在此基础上进行测试. 前言 Spring Data 系列(一) 入门:简单介绍了原生态的SQL使用,以及JdbcTemplate的使用,在这里写SQL的活还需要自己准备. Spring Data 系

Spring Data Redis简介以及项目Demo,RedisTemplate和 Serializer详解

一.概念简介: Redis: Redis是一款开源的Key-Value数据库,运行在内存中,由ANSI C编写,详细的信息在Redis官网上面有,因为我自己通过google等各种渠道去学习Redis,走了不少弯路,所以总结一条我认为不错的学习路径给大家: 1.<The Little Redis Book> 是一本开源PDF,只有29页的英文文档,看完后对Redis的基本概念应该差不多熟悉了,剩下的可以去Redis官网熟悉相关的命令. 2.<Redis设计与实现> 如果想继续深入,推

Android 开源框架Universal-Image-Loader完全解析(二)--- 图片缓存策略详解

本篇文章继续为大家介绍Universal-Image-Loader这个开源的图片加载框架,介绍的是图片缓存策略方面的,如果大家对这个开源框架的使用还不了解,大家可以看看我之前写的一篇文章Android 开源框架Universal-Image-Loader完全解析(一)--- 基本介绍及使用,我们一般去加载大量的图片的时候,都会做缓存策略,缓存又分为内存缓存和硬盘缓存,我之前也写了几篇异步加载大量图片的文章,使用的内存缓存是LruCache这个类,LRU是Least Recently Used 近

“全栈2019”Java多线程第三十章:尝试获取锁tryLock()方法详解

难度 初级 学习时间 10分钟 适合人群 零基础 开发语言 Java 开发环境 JDK v11 IntelliJ IDEA v2018.3 文章原文链接 "全栈2019"Java多线程第三十章:尝试获取锁tryLock()方法详解 下一章 "全栈2019"Java多线程第三十一章:中断正在等待显式锁的线程 学习小组 加入同步学习小组,共同交流与进步. 方式一:关注头条号Gorhaf,私信"Java学习小组". 方式二:关注公众号Gorhaf,回复

Spring框架3:spring的依赖注入

本系列笔记均是对b站教程https://www.bilibili.com/video/av47952931 的学习笔记,非本人原创 spring的依赖注入(DI) 什么是依赖注入: 作者:Angry Bugs 链接:https://www.zhihu.com/question/32108444/answer/581948457 来源:知乎 著作权归作者所有.商业转载请联系作者获得授权,非商业转载请注明出处. 看了几个高赞答案,感觉说得还是太啰嗦了.依赖注入听起来好像很复杂,但是实际上炒鸡简单,一

Spring学习总结三——SpringIOC容器三

一:spring容器自动装配注入 为了减少xml中配置内容,可以使用自动装配注入,代替setter注入,只需要在 bean对象配置中添加属性autoWire即可,那么在类中就会自动扫描setXXX(),实现自动装配注入. autowire的装配方式分为以下几种: 示例如下: 1:创建UserService类 1 /** 2 * 3 */ 4 package com.hlcui.service; 5 6 import com.hlcui.dao.impl.OracleUserDAO; 7 impo