Spring(一)

W3C Spring 教程

Spring教程&面经

廖雪峰Spring开发

易百教程:Spring教程

  • Spring的概述
    • Spring是什么
    • Spring的两大核心(IoC和AOP)
    • Spring的发展历程和优势
    • Spring体系结构
  • 程序的耦合及解耦
    • 曾经案例中的问题(视频讲解
    • 工厂模式解耦
  • IOC概念和Spring中的IOC
    • Spring中基于XML的IOC环境搭建
  • 依赖注入(Dependency Injection)

Spring的概述

Spring 概述

W3C Spring概述

Spring是什么

Spring 是分层的 Java SE/EE 应用 full-stack 轻量级开源框架,以 **IoC(Inverse Of Control:反转控制)**和 **AOP(Aspect Oriented Programming:面向切面编程)**为内核,提供了展现层 Spring MVC 和持久层 Spring JDBC 以及业务层事务管理等众多的企业级应用技术,还能整合开源世界众多著名的第三方框架和类库,逐渐成为使用最多的 Java EE 企业应用开源框架。

三层架构

Spring的两大核心(IoC和AOP)

Spring 最核心的两个技术思想:IoC 和 Aop

IoCInversion of Control ,意为控制反转。

Spring 最认同的技术是控制反转的**依赖注入(DI)**模式。控制反转(IoC)是一个通用的概念,它可以用许多不同的方式去表达,依赖注入仅仅是控制反转的一个具体的例子。

当编写一个复杂的 Java 应用程序时,应用程序类应该尽可能的独立于其他的 Java 类来增加这些类可重用可能性,当进行单元测试时,可以使它们独立于其他类进行测试。依赖注入(或者有时被称为配线)有助于将这些类粘合在一起,并且在同一时间让它们保持独立。

到底什么是依赖注入?让我们将这两个词分开来看一看。这里将依赖关系部分转化为两个类之间的关联。例如,类 A 依赖于类 B。现在,让我们看一看第二部分,注入。所有这一切都意味着类 B 将通过 IoC 被注入到类 A 中。

依赖注入可以以向构造函数传递参数的方式发生,或者通过使用 setter 方法 post-construction。

AopAspect Oriented Programming,面向切面编程

Spring 框架的一个关键组件是面向切面的程序设计(AOP)框架。一个程序中跨越多个点的功能被称为横切关注点,这些横切关注点在概念上独立于应用程序的业务逻辑。有各种各样常见的很好的关于方面的例子,比如日志记录、声明性事务、安全性,和缓存等等。

在 OOP 中模块化的关键单元是类,而在 AOP 中模块化的关键单元是方切面AOP 帮助你将横切关注点从它们所影响的对象中分离出来,然而依赖注入帮助你将你的应用程序对象从彼此中分离出来

Spring 框架的 AOP 模块提供了面向方面的程序设计实现,允许你定义拦截器方法和切入点,可以实现将应该被分开的代码干净的分开功能。

Spring的发展历程

  • 1997 年 IBM 提出了 EJB 的思想
  • 1998 年,SUN 制定开发标准规范 EJB1.0
  • 1999 年,EJB1.1 发布
  • 2001 年,EJB2.0 发布
  • 2003 年,EJB2.1 发布
  • 2006 年,EJB3.0 发布
  • Rod Johnson(Spring 之父)
    • Expert One-to-One J2EE Design and Development(2002):阐述了 J2EE 使用 EJB 开发设计的优点及解决方案
    • Expert One-to-One J2EE Development without EJB(2004):阐述了 J2EE 开发不使用 EJB 的解决方式(Spring 雏形)
  • 2017 年 9 月份发布了 spring 的最新版本 spring 5.0 通用版(GA)

Spring的优势

  • 方便解耦,简化开发:通过 Spring 提供的 IoC 容器,可以将对象间的依赖关系交由 Spring 进行控制,避免硬编码所造成的过度程序耦合。用户也不必再为单例模式类、属性文件解析等这些很底层的需求编写代码,可以更专注于上层的应用
  • AOP 编程的支持:通过 Spring 的 AOP 功能,方便进行面向切面的编程,许多不容易用传统 OOP 实现的功能可以通过 AOP 轻松应付
  • 声明式事务的支持:可以将我们从单调烦闷的事务管理代码中解脱出来,通过声明式方式灵活的进行事务的管理,提高开发效率和质量
  • 方便程序的测试:可以用非容器依赖的编程方式进行几乎所有的测试工作,测试不再是昂贵的操作,而是随手可做的事情
  • 方便集成各种优秀框架:Spring 可以降低各种框架的使用难度,提供了对各种优秀框架(Struts、Hibernate、Hessian、Quartz等)的直接支持
  • 降低 JavaEE API 的使用难度:Spring 对 JavaEE API(如 JDBC、JavaMail、远程调用等)进行了薄薄的封装层,使这些 API 的使用难度大为降低
  • Java 源码是经典学习范例:Spring 的源代码设计精妙、结构清晰、匠心独用,处处体现着大师对 Java 设计模式灵活运用以及对 Java 技术的高深造诣。它的源代码无意是 Java 技术的最佳实践的范例

Spring体系结构

W3C Spring 体系结构

Spring体系结构

Spring有可能成为所有企业应用程序的一站式服务点,然而,Spring是模块化的,允许你挑选和选择适用于你的模块,不必要把剩余部分也引入。

Spring体系结构

  • 核心容器:核心容器由 spring-core,spring-beans,spring-context,spring-context-support和spring-expression(SpEL,Spring 表达式语言,Spring Expression Language)等模块组成
  • 数据访问/集成:数据访问/集成层包括 JDBC,ORM,OXM,JMS 和事务处理模块
  • Web:Web 层由 Web,Web-MVC,Web-Socket 和 Web-Portlet 组成
  • 其他:还有其他一些重要的模块,像 AOP,Aspects,Instrumentation,Web 和测试模块

具体可查看W3C Spring 体系结构

视频讲解


IoC的概念和作用

程序的耦合和解耦

程序的耦合和解耦思路

Spring程序间的耦合和解耦

程序的耦合

耦合性(Coupling),也叫耦合度,是对模块间关联程度的度量。耦合的强弱取决于模块间接口的复杂性、调用模块的方式以及通过界面传送数据的多少。模块间的耦合度是指模块之间的依赖关系,包括控制关系、调用关系、数据传递关系。模块间联系越多,其耦合性越强,同时表明其独立性越差( 降低耦合性,可以提高其独立性)。耦合性存在于各个领域,而非软件设计中独有的,但是我们只讨论软件工程中的耦合。

在软件工程中,耦合指的就是就是对象之间的依赖性。对象之间的耦合越高,维护成本越高。因此对象的设计应使类和构件之间的耦合最小。软件设计中通常用耦合度和内聚度作为衡量模块独立程度的标准。划分模块的一个准则就是高内聚低耦合

划分模块有以下分类:

  • 内容耦合:当一个模块直接修改或操作另一个模块的数据时,或一个模块不通过正常入口而转入另一个模块时,这样的耦合被称为内容耦合。内容耦合是最高程度的耦合,应该避免使用
  • 公共耦合:两个或两个以上的模块共同引用一个全局数据项,这种耦合被称为公共耦合。在具有大量公共耦合的结构中,确定究竟是哪个模块给全局变量赋了一个特定的值是十分困难的
  • 外部耦合:一组模块都访问同一全局简单变量而不是同一全局数据结构,而且不是通过参数表传递该全局变量的信息,则称之为外部耦合
  • 控制耦合:一个模块通过接口向另一个模块传递一个控制信号,接受信号的模块根据信号值而进行适当的动作,这种耦合被称为控制耦合
  • 标记耦合:若一个模块 A 通过接口向两个模块 B 和 C 传递一个公共参数,那么称模块 B 和 C 之间存在一个标记耦合
  • 数据耦合:模块之间通过参数来传递数据,那么被称为数据耦合。数据耦合是最低的一种耦合形式,系统中一般都存在这种类型的耦合,因为为了完成一些有意义的功能,往往需要将某些模块的输出数据作为另一些模块的输入数据
  • 非直接耦合:两个模块之间没有直接关系,它们之间的联系完全是通过主模块的控制和调用来实现的

总结耦合是影响软件复杂程度和设计质量的一个重要因素,在设计上我们应采用以下原则:如果模块间必存在耦合,就尽量使用数耦合,少用控制耦合,限制公共耦合的范围,尽量避免使用内容耦合

内聚与耦合

内聚标志一个模块内各个元素彼此结合的紧密程度,它是信息隐蔽和局部化概念的自然扩展。内聚是从功能角度来度量模块内的联系,一个好的内聚模块应当恰好做一件事。它描述的是模块内的功能联系耦合是软件结构中各模块之间相互连接的一种度量,耦合强弱取决于模块间接口的复杂程度、进入或访问一个模块的点以及通过接口的数据。 程序讲究的是**低耦合,高内聚。就是同一个模块内的各个元素之间要高度紧密,但是各个模块之间的相互依存度却要不那么紧密。**

内聚和耦合是密切相关的,同其他模块存在高耦合的模块意味着低内聚,而高内聚的模块意味着该模块同其他模块之间是低耦合。在进行软件设计时,应力争做到高内聚,低耦合

JDBC工程代码的程序耦合分析:视频讲解

解决程序耦合的思路

编译期依赖:视频讲解

实际开发中应该做到:编译期不依赖,运行时才依赖

解耦的思路:

  1. 使用反射来创建对象,避免使用new关键字
  2. 通过读取配置文件来获取要创建的对象的全限定类名

当是我们讲解 jdbc 时,是通过反射来注册驱动的,代码如下:Class.forName("com.mysql.jdbc.Driver");

此时的好处是,我们的类中不再依赖具体的驱动类,此时就算删除 mysql 的驱动 jar 包,依然可以编译(运行就不要想了,没有驱动不可能运行成功的)。

同时,也产生了一个新的问题,mysql 驱动的全限定类名字符串是在 java 类中写死的,一旦要改还是要修改源码。

解决这个问题也很简单,使用配置文件配置。

视频讲解

工厂模式解耦

视频讲解

在实际开发中我们可以把三层的对象都使用配置文件配置起来,当启动服务器应用加载的时候,让一个类中的方法通过读取配置文件,把这些对象创建出来并存起来。在接下来的使用的时候,直接拿过来用就好了。

那么,这个读取配置文件,创建和获取三层对象的类就是工厂

Bean,在计算机英语中,有可重用组件的含义

JavaBean:用Java语言编写的可重用组件

我们要创建一个Bean对象的工厂:

  • 需要一个配置文件来配置我们的dao和service(配置内容:唯一标识 = 全限定类名(key-value))
    • 配置文件可以是xml文件,也可以是properties文件
  • 通过读取配置文件配置的内容,使用反射创建对象

视频讲解

IoC:控制反转(Inversion Of Control)

Spring IoC 容器

Spring IoC是什么

廖雪峰 IoC原理

依赖注入和控制反转的理解

在上面解耦的思路中有2个问题:

  1. 存哪去?

    • 分析:由于我们是很多对象,肯定要找个集合来存。这时候有 Map 和 List 供选择。到底选 Map 还是 List 就看我们有没有查找需求。有查找需求,选 Map。所以我们的答案就是在应用加载时,创建一个 Map,用于存放三层对象。我们把这个Map称之为容器
  2. 什么是工厂?工厂就是负责给我们从容器中获取指定对象的类。这时候我们获取对象的方式发生了改变。之前我们在获取对象时,都是采用new的方式。是主动的:

    主动new对象

    而现在,我们获取对象时,同时跟工厂要,由工厂为我们查找或者创建对象,是被动的:

    由工厂查找或创建对象

这种被动接收的方式获取对象的思想就是IoC(控制反转),它是Spring框架的核心之一。

控制反转(Inversion of Control,缩写为IoC),是面向对象编程中的一种设计原则,可以用来降低计算机代码之间的耦合度。其中最常见的方式叫做依赖注入(Dependency Injection,简称DI),还有一种方式叫“依赖查找”(Dependency Lookup)。通过控制反转,对象在被创建的时候,由一个调控系统内所有对象的外界实体将其所依赖的对象的引用传递给它。也可以说,依赖被注入到对象中。

明确IoC的作用:削减计算机程序的耦合(解除我们代码中的依赖关系)

视频讲解

为何是反转,哪些方面反转了:有反转就有正转,传统应用程序是由我们自己在对象中主动控制去直接获取依赖对象,也就是正转;而反转则是由容器来帮忙创建及注入依赖对象;为何是反转?因为由容器帮我们查找及注入依赖对象,对象只是被动的接受依赖对象,所以是反转;哪些方面反转了?依赖对象的获取被反转了。

传统程序设计如下图,都是主动去创建相关对象然后再组合起来:

传统应用程序示意图(正转)

当有了IoC/DI的容器后,在客户端类中不再主动去创建这些对象了,如下图所示:

有IoC/DI容器后程序结构示意图(反转)

IoC 不是一种技术,只是一种思想,一个重要的面向对象编程的法则,它能指导我们如何设计出松耦合、更优良的程序。传统应用程序都是由我们在类内部主动创建依赖对象,从而导致类与类之间高耦合,难于测试;有了IoC容器后,把创建和查找依赖对象的控制权交给了容器,由容器进行注入组合对象,所以对象与对象之间是松散耦合,这样也方便测试,利于功能复用,更重要的是使得程序的整个体系结构变得非常灵活。

其实IoC对编程带来的最大改变不是从代码上,而是从思想上,发生了“主从换位”的变化。应用程序原本是老大,要获取什么资源都是主动出击,但是在IoC/DI思想中,应用程序就变成被动的了,被动的等待IoC容器来创建并注入它所需要的资源了。

IoC很好的体现了面向对象设计法则之一—— 好莱坞法则:“别找我们,我们找你”;即由IoC容器帮对象找相应的依赖对象并注入,而不是由对象主动去找。

IoC和DI

DI—Dependency Injection,即“依赖注入”组件之间依赖关系由容器在运行期决定,形象的说,即由容器动态的将某个依赖关系注入到组件之中。**依赖注入的目的并非为软件系统带来更多功能,而是为了提升组件重用的频率,并为系统搭建一个灵活、可扩展的平台。**通过依赖注入机制,我们只需要通过简单的配置,而无需任何代码就可指定目标需要的资源,完成自身的业务逻辑,而不需要关心具体的资源来自何处,由谁实现。

理解DI的关键是:“谁依赖谁,为什么需要依赖,谁注入谁,注入了什么”,那我们来深入分析一下:

●谁依赖于谁:当然是应用程序依赖于IoC容器

●为什么需要依赖:应用程序需要IoC容器来提供对象需要的外部资源

●谁注入谁:很明显是IoC容器注入应用程序某个对象,应用程序依赖的对象

●注入了什么:就是注入某个对象所需要的外部资源(包括对象、资源、常量数据)

IoC和DI由什么关系呢?其实它们是同一个概念的不同角度描述,由于控制反转概念比较含糊(可能只是理解为容器控制对象这一个层面,很难让人想到谁来维护对象关系),所以2004年大师级人物Martin Fowler又给出了一个新的名字:“依赖注入”,相对IoC 而言,“依赖注入”明确描述了“被注入对象依赖IoC容器配置依赖对象”。


使用Spring的IOC解决程序耦合

这里我们的案例是:账户的业务层和持久层的依赖关系解决。在开始 spring 的配置之前,我们要先准备一下环境。由于我们是使用 spring 解决依赖关系,并不是真正的要做增删改查操作,所以此时我们没必要写实体类。(因为IoC只是解决程序间的耦合问题)并且我们在此处使用的是java工程,不是java web 工程。(pom.xml文件使用<packaging>jar</packaging>

Spring IoC 容器完全由实际编写的配置元数据的格式解耦。有下面三个重要的方法把配置元数据提供给 Spring 容器:

  • 基于 XML 的配置文件
  • 基于注解的配置
  • 基于 Java 的配置

提示:对于基于 XML 的配置,Spring 2.0 以后使用 Schema 的格式,使得不同类型的配置拥有了自己的命名空间,使配置文件更具扩展性。

Bean 与 Spring 容器之间的关系

Spring Bean 定义

环境准备

首先准备Spring开发包:(其实jar包可以通过Maven来导入

Spring官网

下载地址

其中spring-framework-5.0.2.RELEASE-dist就是我们需要的Spring开发包,解压后目录结构如下:

  • docs:API 和开发规范
  • libs:jar 包和源码
  • schema:约束

这里只是以5.0.2版本举个例子,注意:

  • Spring5版本是用 jdk8 编写的,所以要求jdk 版本是8及以上
  • tomcat的版本要求8.5及以上

创建一个普通的Maven工程,修改pom.xml如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>

<groupId>top.qing</groupId>
<artifactId>Spring01_IoC_Xml</artifactId>
<version>1.0-SNAPSHOT</version>
<packaging>jar</packaging>

<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<source>6</source>
<target>6</target>
</configuration>
</plugin>
</plugins>
</build>

</project>

创建持久层接口AccountDao及其实现类如下:

1
2
3
4
5
6
7
8
9
package dao;

public interface AccountDao {

/**
* 保存账户
*/
void saveAccount();
}
1
2
3
4
5
6
7
8
9
10
11
package dao.impl;

import dao.AccountDao;

public class AccountDaoImpl implements AccountDao {

@Override
public void saveAccount() {
System.out.println("保存了账户");
}
}

创建业务层接口AccountService及其实现类如下:

1
2
3
4
5
6
7
8
9
package service;

public interface AccountService {

/**
* 保存用户(模拟保存)
*/
void saveAccount();
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
package service.impl;

import dao.AccountDao;
import dao.impl.AccountDaoImpl;
import service.AccountService;

public class AccountServiceImpl implements AccountService {

private AccountDao accountDao = new AccountDaoImpl();

@Override
public void saveAccount() {
accountDao.saveAccount();
}
}

注意:如果pom.xml中没有以下内容:

1
2
3
4
5
6
7
8
9
10
11
12
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<source>6</source>
<target>6</target>
</configuration>
</plugin>
</plugins>
</build>

在接口的实现类中@Override可能会报错:

@Override报错

Spring基于Xml配置的IOC

Bean 定义:被称作 bean 的对象是构成应用程序的支柱也是由 Spring IoC 容器管理的。bean 是一个被实例化,组装,并通过 Spring IoC 容器所管理的对象。这些 bean 是由用容器提供的配置元数据创建的.

Bean 与 Spring 容器的关系,如下图:

视频讲解

首先在pom.xml导入相关依赖:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<dependencies>
<!--Spring核心容器-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.2.10.RELEASE</version>
</dependency>

<!--log4j-->
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.17</version>
</dependency>

</dependencies>

我们在IDEA上可以通过Maven插件查看依赖关系视图

通过IDEA查看项目依赖关系视图

依赖关系视图

这里就要再提一下Spring体系结构中的Spring核心容器完整依赖图示:

具体可查看:W3C Spring 体系结构

接下来我们在resources文件夹下创建bean.xml,代码如下:

1
2
3
4
5
6
7
8
9
10
11
<?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">

<!--把对象的创建交给Spring来管理-->
<bean id="accountService" class="service.impl.AccountServiceImpl"></bean>
<bean id="accountDao" class="dao.impl.AccountDaoImpl"></bean>

</beans>

(注意这里bean.xml的约束)

<bean>标签:用于配置让Spring创建对象,并且存入IoC容器之中

  • id属性:对象的唯一标识
  • class属性:指定要创建对象的全限定类名

spring ioc(使用xml配置)|bean标签及其子标签

接下来我们在ui层下创建Client类,用于模拟客户端来测试配置是否成功:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
package ui;

import dao.AccountDao;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import service.AccountService;

/**
* 模拟一个表现层(客户端)
*/
public class Client {

/**
* 获取Spring的IoC核心容器,并根据id获取对象
* @param args
*/
public static void main(String[] args) {
// 1.使用ApplicationContext接口,获取Spring核心容器对象
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("bean.xml");
// 2.根据bean.xml文件中<bean>标签的的id来获取对象
AccountService accountService = (AccountService) applicationContext.getBean("accountService");
System.out.println(accountService);
AccountDao accountDao = (AccountDao) applicationContext.getBean("accountDao");
System.out.println(accountDao);

}
}

结果如下:

成功通过bean标签的id获取到对象

Spring基于Xml配置的IOC的补充说明(细节)

视频讲解

  • ApplicationContext
  • BeanFactory
  • BeanFactory和ApplicationContext的区别

ApplicationContext

Spring ApplicationContext 容器

我们可以先通过IDEA来看一下ApplicationContext的Diagrams,方法如下:

通过IDEA的Diagrams-->show Diagram

然后我们右键选择ApplicationContext,点击show Implementations可以查看ApplicationContext接口的实现类:

ApplicationContext接口的全部实现类

其中常用的三个实现类:

  • ClassPathXmlApplicationContext:从类的根路径下加载配置文件(推荐使用)
  • FileSystemXmlApplicationContext:从磁盘路径上加载配置文件,配置文件可以在磁盘的任意位置(注意要有访问权限)
  • AnnotationConfigApplicationContext当我们使用注解配置容器对象时,需要使用此类来创建Spring 容器,它用来读取注解

Application Context 是 BeanFactory 的子接口,也被称为 Spring 上下文。

Application Context 是 spring 中较高级的容器。和 BeanFactory 类似,它可以加载配置文件中定义的 bean,将所有的 bean 集中在一起,当有请求的时候分配 bean。 另外,它增加了企业所需要的功能,比如,从属性文件中解析文本信息和将事件传递给所指定的监听器。

ApplicationContext 包含 BeanFactory 所有的功能,一般情况下,相对于 BeanFactory,ApplicationContext 会更加优秀。当然,BeanFactory 仍可以在轻量级应用中使用,比如移动设备或者基于 applet 的应用程序。

最常被使用的 ApplicationContext 接口实现类:

  • FileSystemXmlApplicationContext:该容器从 XML 文件中加载已被定义的 bean。在这里,你需要提供给构造器 XML 文件的完整路径。
  • ClassPathXmlApplicationContext:该容器从 XML 文件中加载已被定义的 bean。在这里,你不需要提供 XML 文件的完整路径,只需正确配置 CLASSPATH 环境变量即可,因为,容器会从 CLASSPATH 中搜索 bean 配置文件。
  • WebXmlApplicationContext:该容器会在一个 web 应用程序的范围内加载在 XML 文件中已被定义的 bean。

BeanFactory

Spring BeanFactory 容器

Spring 的 BeanFactory 容器是一个最简单的容器,它主要的功能是为依赖注入 (DI) 提供支持,这个容器接口在 org.springframework.beans.factory.BeanFactory中被定义。BeanFactory 和相关的接口,比如BeanFactoryAware、DisposableBean、InitializingBean,仍旧保留在 Spring 中,主要目的是向后兼容已经存在的和那些 Spring 整合在一起的第三方框架。

在 Spring 中,有大量对 BeanFactory 接口的实现。其中,最常被使用的是 XmlBeanFactory 类。这个容器从一个 XML 文件中读取配置元数据,由这些元数据来生成一个被配置化的系统或者应用。

在资源宝贵的移动设备或者基于 applet 的应用当中, BeanFactory 会被优先选择。否则,一般使用的是 ApplicationContext,除非你有更好的理由选择 BeanFactory。

BeanFactory是Spring容器中的顶层接口,ApplicationContext是它的子接口

BeanFactory和ApplicationContext的区别

Spring中BeanFactory和ApplicationContext的区别

BeanFactory和ApplicationContext有什么区别?

  • ApplicationContext:在构建核心容器时,创建对象采取的策略是立即加载,只要一读完配置文件马上就创建配置文件中配置的对象(推荐使用
  • BeanFactory:在构建核心容器时,创建对象采取的策略是延迟加载,只有当根据id获取对象了才创建相应的对象

视频讲解

Spring中关于Bean的细节

  • 3种创建Bean对象的方式
  • Bean对象的作用范围和生命周期

Spring Bean 定义

Bean标签

作用:配置对象让Spring来创建。默认情况下它调用的是类中的无参构造函数,如果没有无参构造函数则不能创建成功

属性:

  • id:给对象在容器中提供一个唯一标识,用于获取对象
  • class:指定类的全限定类名,用于反射创建对象,默认情况下调用无参构造函数
  • scope:指定对象的作用范围,取值有
    • singleton:默认值,单例的
    • prototype:多例的
    • request:WEB 项目中,Spring 创建一个 Bean 的对象,将对象存入到 request 域中
    • session:WEB 项目中,Spring 创建一个 Bean 的对象,将对象存入到 session 域中
    • global session:WEB 项目中,应用在 Portlet 环境。如果没有 Portlet 环境那么globalSession 相当于 session
  • init-method:指定类中的初始化方法名称
  • destroy-method:指定类中销毁方法名称

3种创建Bean对象的方式

视频讲解

  1. 使用默认构造函数创建对象:在Spring的配置文件中使用**<bean>标签**,配以id和class属性之后,且没有其他属性和标签时,采用的就是默认构造函数创建bean对象,此时如果类中没有默认构造函数,则对象无法创建

    这也是在我们上面的例子中所使用的(使用默认构造函数创建对象),在bean.xml文件中:

    1
    2
    3
    <!--把对象的创建交给Spring来管理-->
    <!--方法1:使用默认构造函数创建对象-->
    <bean id="accountService" class="top.qing.service.impl.AccountServiceImpl"></bean>
  2. 使用普通工厂中的方法创建对象使用某个类中的方法创建对象,并存入Spring容器

    我们先来模拟一个工厂类,在factory包下创建InstanceFactory代码如下:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    package top.qing.factory;

    import top.qing.service.AccountService;
    import top.qing.service.impl.AccountServiceImpl;

    /**
    * 模拟一个工厂类(该类可能是存在于jar包中的,我们无法通过修改源码的方式来提供默认构造函数)
    */
    public class InstanceFactory {
    public AccountService getAccountService(){
    return new AccountServiceImpl();
    }
    }

    然后在在bean.xml文件中:

    1
    2
    3
    <!--方法2:使用普通工厂中的方法创建对象-->
    <bean id="instanceFactory" class="top.qing.factory.InstanceFactory"></bean>
    <bean id="accountService" factory-bean="instanceFactory" factory-method="getAccountService"></bean>

    此种方式是:先把工厂的创建交给 spring 来管理,然后在使用工厂的 bean 来调用里面的方法

    • factory-bean 属性:用于指定实例工厂 bean 的 id
    • factory-method 属性:用于指定实例工厂中创建对象的方法
  3. 使用工厂中的静态方法创建对象:使用某个类中的静态方法创建对象,并存入Spring容器

    还是先模拟一个工厂类,在factory包下创建StaticFactory代码如下:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    package top.qing.factory;

    import top.qing.service.AccountService;
    import top.qing.service.impl.AccountServiceImpl;

    /**
    * 模拟一个工厂类(该类可能是存在于jar包中的,我们无法通过修改源码的方式来提供默认构造函数)
    */
    public class StaticFactory {
    public static AccountService getAccountService(){
    return new AccountServiceImpl();
    }
    }

    然后在在bean.xml文件中:

    1
    2
    <!--方法3:使用工厂中的静态方法创建对象-->
    <bean id="accountService" class="top.qing.factory.StaticFactory" factory-method="getAccountService"></bean>

    此种方式是:使用 StaticFactory 类中的静态方法getAccountService创建对象,并存入 spring 容器

    • id 属性:指定 bean 的 id,用于从容器中获取
    • class 属性:指定静态工厂的全限定类名
    • factory-method 属性:指定生产对象的静态方法

Bean对象的作用范围和生命周期

上面已经详细介绍了Bean标签的相关属性了,这里再补充一下globalSession:

全局session

视频讲解

  • 单例对象:scope="singleton"
  • 多例对象:scope="prototype"
  • 单例对象:一个应用只有一个对象的实例,它的作用范围就是整个引用

    生命周期:

    • 对象出生:当应用加载,创建容器时,对象就被创建了(立即加载)
    • 对象活着:只要容器在,对象一直活着
    • 对象死亡:当应用卸载,销毁容器时,对象就被销毁了
  • 多例对象:每次访问对象时,都会重新创建对象实例

    生命周期:

    • 对象出生:当使用对象时,创建新的对象实例(延迟加载)
    • 对象活着:只要对象在使用中,就一直活着
    • 对象死亡:当对象长时间不用时,被 java 的垃圾回收器回收了

视频讲解

单例和多例的区别

单例模式和多例的区别


Spring中的依赖注入

Spring 依赖注入

在上面的[IoC:控制反转(Inversion Of Control)](#IoC:控制反转(Inversion Of Control))中,我们已经详细介绍了IoCDI

依赖注入:Dependency Injection,是Spring框架核心IoC的具体实现。

我们的程序在编写时,通过控制反转,把对象的创建交给了Spring,但是代码中不可能出现没有依赖的情况,IoC 解耦只是降低他们的依赖关系,但不会消除。例如:我们的业务层仍会调用持久层的方法。那这种业务层和持久层的依赖关系,在使用 spring 之后,就让 spring 来维护了。

简单的说,就是让框架把持久层对象传入业务层,而不用我们自己去获取

依赖注入:Dependency Injection;IOC的作用: 降低程序间的耦合(依赖关系)

依赖关系的管理:以后都交给spring来维护。在当前类需要用到其他类的对象,由spring为我们提供,我们只需要在配置文件中说明。依赖关系的维护就称之为依赖注入

依赖注入中能注入的数据有三类:

  • 基本类型和String
  • 其他bean类型(在配置文件中或者注解配置过的bean)
  • 复杂类型/集合类型

注入的方式有三种

  1. 使用构造函数注入
  2. 使用set方法注入(常用)
  3. 使用注解注入(后续学习)

视频讲解

构造函数注入

Spring 基于构造函数的依赖注入

首先修改一下AccountServiceImpl,代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
package top.qing.service.impl;


import top.qing.service.AccountService;

import java.util.Date;

public class AccountServiceImpl implements AccountService {

// 如果是经常变化的数据,并不适用于注入的方式
private String name;
private Integer age;
private Date birthday;

public AccountServiceImpl(String name, Integer age, Date birthday) {
this.name = name;
this.age = age;
this.birthday = birthday;
}

@Override
public String toString() {
return "AccountServiceImpl{" +
"name='" + name + '\'' +
", age=" + age +
", birthday=" + birthday +
'}';
}

@Override
public void saveAccount() {
System.out.println("service中的saveAccount方法执行了");
System.out.println(this);
}
}

然后在bean.xml中:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<?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="accountService" class="top.qing.service.impl.AccountServiceImpl">
<constructor-arg name="name" value="杰瑞"></constructor-arg>
<constructor-arg name="age" value="18"></constructor-arg>
<constructor-arg name="birthday" ref="date"></constructor-arg>
</bean>

<!--配置一个日期对象-->
<bean id="date" class="java.util.Date"></bean>

</beans>

构造函数注入:通过**<constructor-arg>标签**。标签在<bean>标签的内部

<constructor-arg>标签中的属性:

  • type:用于指定要注入的数据的数据类型,该数据类型也是构造函数中某个或某些参数的类型

  • index:用于指定要注入的数据给构造函数中指定索引位置的参数赋值。索引的位置是从0开始

  • name:用于指定给构造函数中指定名称的参数赋值(常用)

    以上三个用于指定给构造函数中哪个参数赋值

  • value:用于提供基本类型String类型的数据

  • ref:用于指定其他的bean类型数据,它指的就是在spring的Ioc核心容器中出现过的bean对象

用于模拟表现层的代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
package top.qing.ui;

import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import top.qing.service.AccountService;

/**
* 模拟一个表现层(客户端)
*/
public class Client {

/**
* 获取Spring的IoC核心容器,并根据id获取对象
* @param args
*/
public static void main(String[] args) {
// 1.使用ApplicationContext接口,获取Spring核心容器对象
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("bean.xml");
// 2.根据bean.xml文件中<bean>标签的的id来获取对象
AccountService accountService = (AccountService) applicationContext.getBean("accountService");
accountService.saveAccount();

}
}

结果如下:

关于构造函数注入的优缺点:

  • 优点:在获取bean对象时,注入数据是必须的操作,否则对象无法创建成功
  • 缺点:改变了bean对象的实例化方式,使我们在创建对象时,如果用不到这些数据,也必须提供

set方法注入

Spring 基于设值函数的依赖注入

创建AccountServiceImpl2代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
package top.qing.service.impl;


import top.qing.service.AccountService;

import java.util.Date;

public class AccountServiceImpl2 implements AccountService {

// 如果是经常变化的数据,并不适用于注入的方式
private String name;
private Integer age;
private Date birthday;

public void setName(String name) {
this.name = name;
}

public void setAge(Integer age) {
this.age = age;
}

public void setBirthday(Date birthday) {
this.birthday = birthday;
}

@Override
public String toString() {
return "AccountServiceImpl2{" +
"name='" + name + '\'' +
", age=" + age +
", birthday=" + birthday +
'}';
}

@Override
public void saveAccount() {
System.out.println("service中的saveAccount方法执行了");
System.out.println(this);
}
}

然后在bean.xml中添加代码如下:

1
2
3
4
5
6
7
8
9
<!--set方法注入-->
<bean id="accountServiceImpl2" class="top.qing.service.impl.AccountServiceImpl2">
<property name="name" value="tom"></property>
<property name="age" value="20"></property>
<property name="birthday" ref="date"></property>
</bean>

<!--配置一个日期对象-->
<bean id="date" class="java.util.Date"></bean>

set方法注入(常用),使用在<bean>标签内部的**<property>标签**

<property>标签中的属性

  • name:用于指定注入时所调用的属性属性根据set方法名判断,比如如果是setName那么属性就为name,如果是setUserName,那么属性就为username)
  • value:用于提供基本类型String类型的数据
  • ref:用于指定其他的bean类型数据。它指的就是在spring的Ioc核心容器中出现过的bean对象

用于模拟表现层的代码如下:

1
2
3
4
5
6
7
8
@Test
public void test2(){
// 1.使用ApplicationContext接口,获取Spring核心容器对象
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("bean.xml");
// 2.根据bean.xml文件中<bean>标签的的id来获取对象
AccountService accountService = (AccountService) applicationContext.getBean("accountServiceImpl2");
accountService.saveAccount();
}

结果如下:

关于set方法注入的优缺点:

  • 优点:创建对象时没有明确的限制,可以直接使用默认构造函数
  • 缺点:如果有某个成员必须有值,则获取对象时有可能set方法没有执行

在基于构造函数注入和基于设值函数注入中的 Beans.xml 文件的唯一的区别就是:在基于构造函数注入中,我们使用的是<bean>标签中的<constructor-arg>元素;而在基于设值函数的注入中,我们使用的是<bean>标签中的<property>元素。

注入集合数据

Spring 注入集合

上面我们学习了两种依赖注入的方式,也成功注入了基本数据类型String类型以及bean类型

接下来我们学习一下注入集合数据

创建AccountServiceImpl3代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
package top.qing.service.impl;


import top.qing.service.AccountService;

import java.util.*;

public class AccountServiceImpl3 implements AccountService {
private String[] myStrs;
private List<String> myList;
private Set<String> mySet;
private Map<String,String> myMap;
private Properties myProps;

public void setMyStrs(String[] myStrs) {
this.myStrs = myStrs;
}

public void setMyList(List<String> myList) {
this.myList = myList;
}

public void setMySet(Set<String> mySet) {
this.mySet = mySet;
}

public void setMyMap(Map<String, String> myMap) {
this.myMap = myMap;
}

public void setMyProps(Properties myProps) {
this.myProps = myProps;
}

@Override
public String toString() {
return "AccountServiceImpl3{" +
"myStrs=" + Arrays.toString(myStrs) + "\n" +
", myList=" + myList + "\n" +
", mySet=" + mySet + "\n" +
", myMap=" + myMap + "\n" +
", myProps=" + myProps +
'}';
}

@Override
public void saveAccount() {
System.out.println("service中的saveAccount方法执行了");
System.out.println(this);
}
}

然后在bean.xml中添加代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
<!--注入集合数据-->
<bean id="accountServiceImpl3" class="top.qing.service.impl.AccountServiceImpl3">
<property name="myStrs">
<array>
<value>aaaAry</value>
<value>BB</value>
<value>Ccc</value>
</array>
</property>

<property name="myList">
<list>
<value>aaaList</value>
<value>BB</value>
<value>Ccc</value>
</list>
</property>

<property name="mySet">
<set>
<value>aaaSet</value>
<value>BB</value>
<value>Ccc</value>
</set>
</property>

<property name="myMap">
<map>
<entry key="jj" value="林俊杰"></entry>
<entry key="jay" value="周杰伦"></entry>
</map>
</property>

<property name="myProps">
<props>
<prop key="jay">周杰伦</prop>
<prop key="jj">林俊杰</prop>
</props>
</property>

</bean>

注意

  • 用于给List结构集合注入的标签:<list ><array><set>
  • 用于个Map结构集合注入的标签:<map><props>

结构相同的话,对应的标签可以随意选择(我们只需要注意区分List结构和Map结构就行

用于模拟表现层的代码如下:

1
2
3
4
5
6
7
8
@Test
public void test3(){
// 1.使用ApplicationContext接口,获取Spring核心容器对象
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("bean.xml");
// 2.根据bean.xml文件中<bean>标签的的id来获取对象
AccountService accountService = (AccountService) applicationContext.getBean("accountServiceImpl3");
accountService.saveAccount();
}

结果如下: