第2章.IoC容器

IoC容器概述

abstract: 介绍IoC和bean的用处和使用

IoC容器处于整个Spring框架中比较核心的位置:Core Container: Beans, Core, Context, SpEL

为上层AOP/Aspects/Instrumentation/Messaging提供支持

IoC容器的用途:

创建对象的依赖,最后组装成所需的业务对象

容器通过业务对象和配置(application-context.xml; xxxController.java)-->生成可用对象

在Spring场景下,把对象A/B/C都称作Bean

从代码角度看,一个ApplicationContext (org.springframework.context包下,属于spring-context依赖中)对象就是一个IoC容器

初始化IoC容器:

在web项目的web.xml中,用

<context-param>

<param-name>contextConfigLocation</param-name>

<param-value>classpath:application-context.xml</param-value>

</context-param>

ApplicationContext context = new ClassPathXmlApplicationContext("application-context.xml");

(文件系统中:ApplicationContext context = new FileSystemXmlApplicationContext("/home/user/conf/application-context.xml");)

来初始化。

定义一个Bean:

在application-context.xml中

<beans>

...

<bean id="screwDriver" class="package_url"></bean>

</beans>

使用Bean:

1. 初始化容器:ApplicationContext context = new ClassPathXmlApplicationContext("application-context.xml");

2. 通过getBean()获取对象:ScrewDriver screwDriver = context.getBean("screwDriver", ScrewDriver.class);

3. 使用对象:screwDriver.use();

实例演练:

1. New Maven Project -> Create a simple project (skip archetype selection)

GroupId: com.netease.nanodegree

ArtifactId: spring-container

2. 将所需依赖加入pom.xml

<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>4.2.1.RELEASE</version>
</dependency>
</dependencies> 

发现依赖的jar包自动被加入

3. 在src/main/resources/下新建application-context.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"
xmlns:p="http://www.springframework.org/schema/p"
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"> </beans>

此时,还未定义任何bean,因此还不能从容器中获取任何bean

4. 在src/main/java/下新建类ScrewDriver.java

package: com.netease.nanodegree;

package com.netease.nanodegree;

public class ScrewDriver {
public void ues () {
System.out.println("Use screw driver");
}
}

5. 此时有了bean,在application-context.xml中加入相关bean的定义

<bean id = "screwDriver" class = "com.netease.nanodegree.ScrewDriver"></bean>

6. 在src/main/java/com.netease.nanodegree下新建测试类TestContainer.java

7. Run as -> Java Application

Bean的作用域:Bean的有效范围

singleton:与设计模式中的singleton有点像

保证对象在整个容器中最多只能有一个单例,也保证在整个生命周期中只会创建一个实例

上例中bean定义的默认作用域就是singleton

也可以显式定义<bean id="screwDriver" class="com.netease.nanodegree.ScrewDriver" scope="singleton"></bean>

例子Test:

ScrewDriver.java

public class ScrewDriver {
private String color = "red";
public void use () {
//System.out.println("Use screw driver");
System.out.println("Use " + color + " screw driver");
} public void setColor (String color) {
this.color = color;
}
}

TestContainer.java

public class TestContainer {
public static void main(String[] args) {
ApplicationContext context = new ClassPathXmlApplicationContext("application-context.xml"); ScrewDriver screwDriver = context.getBean("screwDriver", ScrewDriver.class);
screwDriver.setColor("green");
screwDriver.use(); ScrewDriver screwDriver2 = context.getBean("screwDriver", ScrewDriver.class);
screwDriver2.use();
}
}

要想测试是不是单例对象,使用getBean()得到两个对象并设置属性为"green",如果是单例,则两个对象的属性均为"green"

应用:大部分场景下使用singleton模式就能满足业务需求,比如Servlet实例,开始时创建实例,结束时销毁实例,在中间时只需这一个实例来处理即可。

prototype:每次引用时都需要创建一个新的对象/实例,比如每次使用时不希望保留上一次的属性值/状态

<bean id=... class=... scope="prototype"></bean>

例子Test:

将bean screwDriver的scope改为prototype;

此时打印出来的结果应该是第一次为green(因为使用setColor()改了),第二次为red(因为初始为red且并未无改动)

在web应用场景下才出现的作用域:

request:在每个request中分别创建和销毁bean

比如在request1开始时创建了若干个beans,结束时这些beans就被销毁了;而另一个request中又是一个request scope,有它自己的bean的创建和销毁

session:在每个session中的作用域

每个用户对应一个session,在session中可能若干个requests,但是sessionBean在这些request之间是可以共享的

而多个session会有各自的session scope

global session:在此不做介绍

application:每个session之间需要共享的信息

Bean的生命周期回调:

有了Bean的作用域后,我们知道/控制bean的创建/销毁,但是我们不能控制创建/销毁时可以做哪些事情(比如申请/释放资源、创建线程池)--生命周期回调的功能可以支持实现这些功能

Spring提供了两种回调:

InitializingBean接口中的afterPropertiesSet()函数:

public interface InitializingBean {
void afterPropertiesSet() throws Exception;
}

当一个bean创建完成后调用

例子:

<bean id=... class=... init-method="init"></bean>

public class ScrewDriver {
public void init() {
Syso("Init screw driver");
}
}

DisposableBean接口中的destroy()函数:

public interface DisposableBean {
void destroy() throws Exception;
}

当一个bean被销毁之后调用

例子:

<bean id=... class=... destroy-method="cleanup"></bean>

public class ScrewDriver {
public void cleanup() {
Syso("Clean up screw driver");
}
}

测试Test结果:

<bean id = "screwDriver" class = "com.netease.nanodegree.ScrewDriver" scope="prototype" init-method="init" destroy-method="cleanup"></bean>

output:

发现没有进行cleanup的回调:因为bean没有被销毁

手动关闭:

((ConfigurableApplicationContext) context).close();

output:还是相同

将bean定义中的scope="prototype"改为默认的singleton后

成功执行cleanup()回调函数

依赖注入

第一章中举的例子:在ScrewDriver中声明变量private Header header = new StraightHeader();

虽然有抽象类header的想法,但是直接用new StraightHeader()创建实例,并没有将抽象的想法实施到位。

如果需要一个带有CrossHeader的screwDriver呢?需要修改ScrewDriver的代码:

private Header header = new CrossHeader();

如果要修改一字刀口螺丝刀的属性,比如颜色等,则需要修改constructor:

private Header header = new StraightHeader("green", 15);

此时:ScrewDriver和StraightHeader的关系是强依赖关系

在第一章中的IoC解决方法是在ScrewDriver的constructor中传入一个Header(抽象接口对象)作为参数

public ScrewDriver(Header header) { this.header = header; }

1. 定义接口:抽象接口Header

2. 实现接口:如StraightHeader和CrossHeader

3. 以接口为基础来进行依赖的注入

基于构造函数的依赖注入方式:如上例中的在constructor中传入接口对象的方法

基于Setter方法的依赖注入方式:和基于构造函数的方式很相近

public class ScrewDriver {
private Header header; public setHeader(Header header) {
this.header = header;
}
}

那...究竟是选择哪一种方法呢?

实际开发中的原则:

如果关系是强依赖的,则基于构造函数(比如ScrewDriver必须有一个Header)

如果关系是可选依赖的,则基于Setter方法(可有可无的依赖)

实际开发中可能注入的是基本类型、集合、Bean,或通过配置文件注入等等

例子(只演示基于构造函数的依赖注入):

定义Header接口:Header.java

package com.netease.nanodegree;

public interface Header {
public void doWork();
public String getInfo();
}

定义实现类StraightHeader.java

package com.netease.nanodegree;

public class StraightHeader implements Header {
private String color;
private int size; public StraightHeader(String color, int size) {
this.color = color;
this.size = size;
} @Override
public void doWork() {
System.out.println("Do work with straight header");
} @Override
public String getInfo() {
return "Straight Header: " + this.color + ", " + this.size;
}

定义实现类CrossHeader.java(相似)

定义StraightHeader bean和CrossHeader bean

<bean id = "header" class = "com.netease.nanodegree.StraightHeader">
<constructor-arg index="0" type="java.lang.String" value="red"></constructor-arg>
<constructor-arg index="1" type="int" value="15"></constructor-arg>
</bean>

可以通过constructor-arg中的index来说明param的顺序;如index="0"; index="1";

可以通过type来说明param的类型;如type="int"; type="java.lang.String";

可以通过name来说明param的对应的变量名;如name="size"; name="color";

(index/type/name都不是必要的)

Test.java

public static void main(String[] args) {
ApplicationContext context = new ClassPathXmlApplicationContext("application-context.xml"); Header header = context.getBean("header", StraightHeader.class);
System.out.println(header.getInfo());
header.doWork();
}

output:

如何注入集合呢?

通过map的方式注入

假设StraightHeader的构造函数为:

public StraightHeader(Map<String, String> params) {
this.color = params.get("color");
this.size = Integer.parseInt(params.get("size"));
}

此时,定义bean为(使用map的方法)

<bean id = "header" class = "com.netease.nanodegree.StraightHeader">
<constructor-arg>
<map>
<entry key="color" value="red"></entry>
<entry key="size" value="15"></entry>
</map>
</constructor-arg>
</bean> 

也可以注入List/Set之类的集合,配置也相似

<bean id = "header" class = "com.netease.nanodegree.StraightHeader">
<constructor-arg>
<list>
<value>red</value>
<value>14</value>
</list>
</constructor-arg>
</bean>

也可以通过配置文件加载一些配置项:比如注入Property的一些属性

<bean id = "header" class = "com.netease.nanodegree.StraightHeader">
<constructor-arg>
<props>
<prop key="color">red</prop>
<prop key="size">14</prop>
</props>
</props>
</bean>

对应的构造函数:

public StraightHeader(Properties props) {
this.color = props.xxx;
this.size = props.xxx;
}

那怎么通过配置文件注入呢?

在src/main/resources/下创建一个配置文件header.properties

color=green
size=16

在application-context.xml中加载该配置文件:

<bean id="headerProperties"
class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
<property name="location" value="classpath:header.properties"/>
</bean>

property->value为配置文件所在地址

读取配置文件中的信息:

<bean id = "header" class = "com.netease.nanodegree.StraightHeader">
<constructor-arg name="color" value="${color}"></constructor-arg>
<constructor-arg name="size" value="${size}"></constructor-arg>
</bean> 

上例注入的都是一些基础类型,如果是ScrewDriver需要注入一个Header类型(bean)的参数呢?

ScrewDriver.java

public class ScrewDriver {
private Header header; public ScrewDriver(Header header) {
this.header = header;
} public void use () {
System.out.println("Use screw driver with header " + header.getInfo());
}
}

注入:

<bean id = "screwDriver" class = "com.netease.nanodegree.ScrewDriver">
<constructor-arg>
<ref bean="header"/>
</constructor-arg>
</bean>

Test.java

ScrewDriver screwDriver = context.getBean("screwDriver", ScrewDriver.class);
screwDriver.use(); 

输出:

通过Setter实现注入:

需要删除constructor

加上Setter()

public void setColor(String color) {
this.color = color;
} public void setSize(int size) {
this.size = size;
}
<!-- setter方法注入 -->
<bean id = "header" class = "com.netease.nanodegree.StraightHeader">
<property name="color" value="${color}"></property>
<property name="size" value="${size}"></property>
</bean>
<bean id="headerProperties" class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
<property name="location" value="classpath:header.properties"/>
</bean>

如果一个bean有很多依赖呢?使用一个依赖添加一个配置的方法吗?

Spring提供了自动装配(Autowiring)的机制(三种)

byName: 根据Bean的名称 (Setter方法)

byType: 根据Bean的类型 (Setter方法)

constructor: 构造函数,根据类型

例子:


<!-- autowiring自动装配的方式 -->

<bean id = "screwDriver" class = "com.netease.nanodegree.ScrewDriver" autowire="byName"></bean>

<bean id = "screwDriver" class = "com.netease.nanodegree.ScrewDriver" autowire="byType"></bean>

在ScrewDriver.java中,不再需要setter,只需在private Header header;前加上@Autowired即可

或使用@Resource(@Autowired和@Resource是存在不同包中的,前者在springframework中,后者在javax.annotation中,相对标准一些)

通过Annotation的方式

@Component:定义Bean

@Value:properties注入

@Autowired (Spring)/ @Resource (Java):自动装配依赖

@PostConstruct & @PreDestroy:生命周期回调

例子:

application-context.xml中:

<context:component-scan base-package="com.netease.nanodegree" />

表示支持通过Annotation来定义bean或其他属性。扫描/搜索com.netease.nanodegree包,使用Annotation的方式进行配置

<?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"
xmlns:p="http://www.springframework.org/schema/p"
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="com.netease.nanodegree" /> <bean id="headerProperties"
class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
<property name="location" value="classpath:header.properties"/>
</bean> <bean id = "screwDriver" class = "com.netease.nanodegree.ScrewDriver" autowire="byName"></bean>
</beans> 

StraightHeader.java中:

删除setters;

添加Annotation:

import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component; @Componentpublic class StraightHeader implements Header {
@Value("${color}")
private String color;
@Value("${size}")
private int size; @Override
public void doWork() {...} @Override
public String getInfo() {...}
}

报错:No bean named 'header' is defined

因为写@Component时,默认name为StraightHeader而不是header,因此需要specify

@Component("header")

与生命周期有关的Annotation:

@PostConstruct
public void init() {
System.out.println("init the header");
} @PreDestroy
public void destroy() {
System.out.println("destroy the header");
}

利弊:

XML文件:繁琐,但是代码独立(代码和配置分离);实际开发中有些东西需要XML文件来配置

Annotation:简洁,但是配置和代码耦合,配置位置分散,不好管理;实际开发中用于controller/service等

IoC容器单元测试

本次得分为:12.50/14.00, 本次测试的提交时间为:2017-09-11
1单选(2分)

下列关于IoC容器的描述错误的是:

  • A.容器必须由应用代码主动创建;2.00/2.00
  • B.应用可以从容器中获取所配置的对象;
  • C.容器负责所管理对象的生命周期管理;
  • D.根据会根据依赖关系组装对象;
2单选(2分)

如下关于自动装配(Autowiring)说法中,错误的是:

  • A.可以根据Bean名称自动注入;
  • B.可以根据Bean的类型通过Setter方法注入;
  • C.不可以根据Bean的类型通过构建函数注入;2.00/2.00
  • D.可以根据Bean的类型自动注入;
3单选(2分)

关于依赖注入方式,说法错误的是:

  • A.可选依赖可以通过构造函数注入;
  • B.可以通过构造函数注入;
  • C.强依赖必须通过构造函数注入;2.00/2.00
  • D.可以通过Setter方法注入;
4多选(3分)

关于Bean作用域的singleton和prototype说法正确的是:

  • A.prototype只在容器中创建一个对象;
  • B.prototype在每次通过容器获取实例时创建一个对象;1.50/3.00
  • C.singleton在每次通过容器获取实例时创建一个对象;
  • D.singleton只在容器中创建一个对象;1.50/3.00
5多选(3分)

关于Bean的生命周期回调,说明正确的是:

  • A.生命周期回调发生在运行时;
  • B.可以通过创建(初始化时)回调进行申请资源的操作;0.75/3.00
  • C.销毁回调发生在对象回收前
  • D.可以通过销毁时回调进行资源回收的操作0.75/3.00
5多选(3分)

关于Bean的生命周期回调,说明正确的是:

  • A.销毁回调发生在对象回收后
  • B.可以通过创建(初始化时)回调进行申请资源的操作;1.50/3.00
  • C.生命周期回调发生在编译时;
  • D.生命周期回调发生在运行时;1.50/3.00
6判断(2分)

IoC容器在初始化时,必须要加载名称为application-context.xml的配置文件;

  • A.√
  • B.×2.00/2.00

IoC容器作业

https://my.oschina.net/hava/blog/758277

1(14分)

我们在示例中通过

context.getBean("header", StraightHeader.class);

方式来获取容器管理的对象,查找并学习Spring的API文档,列出getBean函数的其它形式接口,并解释各个接口的差异。

2(16分)

假设我们有个应用(Application),它包含一个写文件的服务(FileWriterService),该服务在创建时会创建并打开一个文件,销毁时关闭该文件,并且该服务包含一个write(String content)方法:会将content写入到文件中,要求:

1. 使用Spring IoC的相关内容组织该程序;

2. 文件路径通过配置文件(properties)指定;

(如果不清楚或者忘记如何打开,关闭以及写入文件,请回顾一个翁老师的 Java基础 课程)

基本要求:必须附加一个项目说明文档,说明每个功能点对应程序的运行结果(截图),项目的接口说明或者关键代码(不要把全部代码贴出来)等可以反映项目结果的内容。提交作业的时候必须有这个项目说明文档,否则会影响最终评分。


Java开发工程师(Web方向) - 04.Spring框架 - 第2章.IoC容器的更多相关文章

  1. Java开发工程师(Web方向) - 04.Spring框架 - 第1章.Spring概述

    第1章.Spring概述 Spring概述 The Spring Framework is a lightweight solution and a potential one-stop-shop f ...

  2. Java开发工程师(Web方向) - 04.Spring框架 - 第3章.AOP技术

    第3章--AOP技术 Spring框架 - AOP概述 笔记https://my.oschina.net/hava/blog/758873Spring框架 - AOP使用 笔记https://my.o ...

  3. Java开发工程师(Web方向) - 04.Spring框架 - 第5章.Web框架

    第5章--Web框架 Web框架概述 Web框架单元测验 本次得分为:13.50/15.00, 本次测试的提交时间为:2017-09-25 1单选(2分) 关于Spring MVC中Dispatche ...

  4. Java开发工程师(Web方向) - 04.Spring框架 - 第4章.数据访问

    第4章--数据访问 Spring JDBC DAO (Data Access Object) 实现数据访问相关接口(接口和实现分离) ORM (Object Relation Mapping) 对象关 ...

  5. Java开发工程师(Web方向) - 04.Spring框架 - 期末测试

    Spring框架客观题 Spring框架编程题 http://blog.csdn.net/sinoacc/article/details/51702458 1 (25分) 假设有如下数据表: crea ...

  6. Java开发工程师(Web方向) - 02.Servlet技术 - 第1章.Servlet

    第1章--Servlet Servlet简介 Servlet应用于? 浏览器发出HTTP请求,服务器接收请求后返回响应给浏览器. 接收请求后到返回响应之间: 服务器将请求对象转交给Servlet容器 ...

  7. Java开发工程师(Web方向) - 02.Servlet技术 - 第3章.Servlet应用

    第3章.Servlet应用 转发与重定向 转发:浏览器发送资源请求到ServletA后,ServletA传递请求给ServletB,ServletB生成响应后返回给浏览器. 请求转发:forward: ...

  8. Java开发工程师(Web方向) - 02.Servlet技术 - 第4章.JSP

    第4章--JSP JSP JSP(Java Server Pages) - 中文名:Java服务器页面 动态网页技术标准 JSP = Html + Java + JSP tags 在服务器端执行,返回 ...

  9. Java开发工程师(Web方向) - 02.Servlet技术 - 第2章.Cookie与Session

    第2章--Cookie与Session Cookie与Session 浏览器输入地址--HTTP请求--Servlet--HTTP响应--浏览器接收 会话(session):打开浏览器,打开一系列页面 ...

随机推荐

  1. java中prepareStatement与createStatement的区别

    首先来看两段代码: 第一个使用createStatement() public void delete( int id){ try { Connection c = DBUtil.getConnect ...

  2. Spark Streaming编程示例

    近期也有开始研究使用spark streaming来实现流式处理.本文以流式计算word count为例,简单描述如何进行spark streaming编程. 1. 依赖的jar包 参考<分别用 ...

  3. NAT和DHCP

    涉及的命令 NAT 动态NAT清除:clear ip nat translation * 进入路由器环回接口:int loo 0 静态NAT清除: (config)#No ip nat inside ...

  4. Eclipse切换字体颜色

    打开window-preferences

  5. 「PHP」设计模式介绍

    引言   最近再看PHP设计模式相关的一些技术文章,网上有关PHP的设计模式范例很少,这里做一些总结仅供参考,不足之处望提出. 参考资料: <大话设计模式>程杰   什么是设计模式   设 ...

  6. 帝国CMS调用友情链接多种方法

    今天织梦模板给大家讲解帝国CMS调用友情链接的常见方法: 1.帝国内置标签:phomelink [phomelink]每行显示数,显示总数,操作类型,分类id,是否显示原链接[/phomelink] ...

  7. inotify和epoll

    参考EventHub.cpp 1.初始化inotify int mINotifyFd = inotify_init(); 2.将要监测的目录添加到inotify int result = inotif ...

  8. Waltz of love

    Waltz of love Love me tenderly Love me softly Close your eyes,fling to the dangcing hall Follow your ...

  9. 『Python基础-11』集合 (set)

    # 『Python基础-11』集合 (set) 目录: 集合的基本知识 集合的创建 访问集合里的值 向集合set增加元素 移除集合中的元素 集合set的运算 1. 集合的基本知识 集合(set)是一个 ...

  10. “子查询返回的值不止一个。当子查询跟随在 =、!=、<、<=、>、>= 之后,或子查询用作表达式时,这种情况是不允许的。”SQL查询错误解析

    为了实现下述代码,首先得有数据库和相应的表格,本文用的是https://blog.csdn.net/qaz13177_58_/article/details/5575711/中的案例,即先用链接中那些 ...