目录
  1. 第1章 Spring 概述
    1. 1.1 spring 是什么
    2. 1.2 Spring 的发展历程
    3. 1.3 spring 的优势
    4. 1.4 spring 的体系结构
  2. 第2章 IOC 的概念和作用
    1. 2.1 什么是程序的耦合
    2. 2.2 解决程序耦合的思路
    3. 2.3 工厂模式解耦
    4. 2.4 控制反转-Inversion Of Control
  3. 第3章 使用IOC解耦(XML)
    1. 3.1 环境搭建
    2. 3.2 spring 中工厂的类结构图
    3. 3.3 IOC 中 bean 标签和管理对象细节
      1. bean 标签
      2. bean 的作用范围和生命周期
      3. 实例化(注入)Bean 的三种方式
    4. 3.4 spring 的依赖注入
      1. 依赖注入的概念
      2. 构造函数注入
      3. set 方法注入
      4. 使用 p 名称空间注入数据
      5. 注入集合属性
    5. 3.5 使用IoC实现账户的CRUD
      1. 数据库脚本
      2. 工程搭建
  4. 第4章 使用IOC解耦(注解)
    1. 4.1 环境搭建
    2. 4.2 常用注解
      1. 4.2.1 用于创建对象的
        1. @Component
        2. @Controller @Service @Repository
      2. 4.2.2 用于注入数据的
        1. @Autowired
        2. @Qualifier
        3. @Resource
        4. @Value
      3. 4.2.3 用于改变作用范围的
        1. @Scope
      4. 4.2.4 和生命周期相关的 (了解)
        1. @PostConstruct
        2. @PreDestroy
    3. 4.3 关于Spring注解和XML的选择问题
    4. 4.4 spring 管理对象细节
    5. 4.5 spring 的纯注解配置
      1. 4.5.1 待改造的问题
      2. 4.5.2 新注解说明
        1. @Configuration
        2. @ComponentScan
        3. @Bean
        4. @PropertySource
        5. @Import
      3. 4.5.3 目录结构
  5. 第5章 Spring 整合 Junit
    1. 5.1 问题
    2. 5.2 解决思路分析
    3. 5.3 配置步骤
    4. 5.4 为什么不把测试类配到 xml 中?
  6. 第6章 AOP概念和Spring中的AOP
    1. 6.1 什么是 AOP
    2. 6.2 AOP 的作用、优势以及实现方式
Spring那些事

第1章 Spring 概述

1.1 spring 是什么

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

1.2 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)

1.3 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 技术的最佳实践的范例。

1.4 spring 的体系结构

第2章 IOC 的概念和作用

2.1 什么是程序的耦合

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

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

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

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

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

内聚和耦合是密切相关的,同其他模块存在高耦合的模块意味着低内聚,而高内聚的模块意味着该模块同其他模块之间是低耦合。在进行软件设计时,应力争做到高内聚,低耦合。
我们在开发中,有些依赖关系是必须的,有些依赖关系可以通过优化代码来解除的。

请看下面的示例代码:

1
2
3
4
5
6
7
/**
* 账户的业务层实现类
* @Version 1.0
*/
public class AccountServiceImpl implements IAccountService {
private IAccountDao accountDao = new AccountDaoImpl();
}

上面的代码表示:业务层调用持久层,并且此时业务层在依赖持久层的接口和实现类。如果此时没有持久层实现类,编译将不能通过。这种编译期依赖关系,应该在我们开发中杜绝。我们需要优化代码解决。

再比如:
早期我们的 JDBC 操作,注册驱动时,我们为什么不使用 DriverManager 的 register 方法,而是采用 Class.forName 的方式?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class JdbcDemo {
/**
* @author 黑马程序员
* @Version 1.0
* @param args
* @throws Exception
*/
public static void main(String[] args) throws Exception {
//1.注册驱动
//DriverManager.registerDriver(new com.mysql.jdbc.Driver());
Class.forName("com.mysql.jdbc.Driver");
//2.获取连接
//3.获取预处理 sql 语句对象
//4.获取结果集
//5.遍历结果集
}
}

原因就是:
我们的类依赖了数据库的具体驱动类(MySQL),如果这时候更换了数据库品牌(比如 Oracle),需要修改源码来重新数据库驱动。这显然不是我们想要的。

2.2 解决程序耦合的思路

当是我们讲解 jdbc 时,是通过反射来注册驱动的,代码如下:

1
Class.forName("com.mysql.jdbc.Driver");//此处只是一个字符串

此时的好处是,我们的类中不再依赖具体的驱动类,此时就算删除 mysql 的驱动 jar 包,依然可以编译(运行就不要想了,没有驱动不可能运行成功的)。
同时,也产生了一个新的问题,mysql 驱动的全限定类名字符串是在 java 类中写死的,一旦要改还是要修改源码。
解决这个问题也很简单,使用配置文件配置。

2.3 工厂模式解耦

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

2.4 控制反转-Inversion Of Control

上一小节(2.1.3)解耦的思路有 2 个问题:
1、并存,存哪去?
分析:由于我们是很多对象,肯定要找个集合来存。这时候有 Map 和 List 供选择。到底选 Map 还是 List 就看我们有没有查找需求。有查找需求,选 Map。
所以我们的答案就是:在应用加载时,创建一个 Map,用于存放三层对象。我们把这个 map 称之为容器。

2、什么是工厂?
工厂就是负责给我们从容器中获取指定对象的类。这时候我们获取对象的方式发生了改变。原来:我们在获取对象时,都是采用 new 的方式。是主动的。

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

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

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

第3章 使用IOC解耦(XML)

3.1 环境搭建

本章我们使用的案例是,账户的业务层和持久层的依赖关系解决。在开始 spring 的配置之前,我们要先准备一下环境。由于我们是使用 spring 解决依赖关系,并不是真正的要做增删改查操作,所以此时我们没必要写实体类。并且我们在此处使用的是 java Maven工程,不是 java web 工程。

案例目录结构

让Spring管理资源,在配置文件中配置 service 和 dao,修改bean.xml

1
2
3
4
5
6
7
8
9
10
<?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的方式:使用默认无参构造函数-->
<bean id="accountService" class="com.spring.service.impl.AccountServiceImpl"></bean>

<bean id="accountDao" class="com.spring.dao.impl.AccountDaoImpl"></bean>
</beans>

IAccountDao

1
2
3
4
5
6
public interface IAccountDao {
/**
* 保存账户
*/
void saveAccount();
}

AccountDaoImpl

1
2
3
4
5
6
public class AccountDaoImpl implements IAccountDao {
@Override
public void saveAccount() {
System.out.println("保存了账户");
}
}

IAccountService

1
2
3
4
5
6
public interface IAccountService {
/**
* 模拟保存账户
*/
void saveAccount();
}

AccountServiceImpl

1
2
3
4
5
6
7
8
9
10
11
12
13
public class AccountServiceImpl implements IAccountService {

private IAccountDao accountDao ;

public AccountServiceImpl(){
System.out.println("对象创建了");
}

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

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
28
29
/**
* 模拟一个表现层,用于调用业务层
*/
public class Client {

/**
* 获取spring的Ioc核心容器,并根据id获取对象
* @param args
*/
public static void main(String[] args) {
//1.获取核心容器对象
ApplicationContext ac = new ClassPathXmlApplicationContext("bean.xml");
//ApplicationContext ac = new FileSystemXmlApplicationContext("C:\\Users\\zhy\\Desktop\\bean.xml");
//2.根据id获取Bean对象
IAccountService as = (IAccountService)ac.getBean("accountService");
IAccountDao adao = ac.getBean("accountDao",IAccountDao.class);

System.out.println(as);
System.out.println(adao);
adao.saveAccount();


//--------BeanFactory----------
//Resource resource = new ClassPathResource("bean.xml");
//BeanFactory factory = new XmlBeanFactory(resource);
//IAccountService as = (IAccountService)factory.getBean("accountService");
//System.out.println(as);
}
}

运行结果:

到此,环境搭建完成,并且测试成功。

核心容器的两个接口引发出的问题:
ApplicationContext:它在构建核心容器时,创建对象采取的策略是采用立即加载的方式。也就是说,只要一读取完配置文件马上就创建配置文件中配置的对象。单例对象适用,可以采用此接口。
BeanFactory:它在构建核心容器时,创建对象采取的策略是采用延迟加载的方式。也就是说,什么时候根据id获取对象了,什么时候才真正的创建对象。适合多例对象使用

3.2 spring 中工厂的类结构图

BeanFactory 和 ApplicationContext 的区别
BeanFactory 才是 Spring 容器中的顶层接口。ApplicationContext 是它的子接口。

区别: 创建对象的时间点不一样。

  • ApplicationContext:只要一读取配置文件,默认情况下就会创建对象。
  • BeanFactory:什么时候使用,什么时候创建对象。

ApplicationContext 接口的实现类
ClassPathXmlApplicationContext:它是从类的根路径下加载配置文件,推荐使用这种
FileSystemXmlApplicationContext:它是从磁盘路径上加载配置文件,配置文件可以在磁盘的任意位置。
AnnotationConfigApplicationContext:当我们使用注解配置容器对象时,需要使用此类来创建 spring 容器。它用来读取注解。

3.3 IOC 中 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:指定类中销毁方法名称。

bean 的作用范围和生命周期

单例对象:scope=”singleton”
一个应用只有一个对象的实例。它的作用范围就是整个引用。
生命周期:

  • 对象出生:当应用加载,创建容器时,对象就被创建了。
  • 对象活着:只要容器在,对象一直活着。
  • 对象死亡:当应用卸载,销毁容器时,对象就被销毁了。

多例对象:scope=”prototype”
每次访问对象时,都会重新创建对象实例。
生命周期:

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

实例化(注入)Bean 的三种方式

第一种方式:使用默认无参构造函数

1
2
3
<!--在默认情况下:
它会根据默认无参构造函数来创建类对象。如果 bean 中没有默认无参构造函数,将会创建失败。-->
<bean id="accountService" class="com.spring.service.impl.AccountServiceImpl"/>

第二种方式:spring 管理静态工厂-使用静态工厂的方法创建对象

1
2
3
4
5
6
7
8
<!--模拟一个静态工厂,创建业务层实现类-->
public class StaticFactory {
public static IAccountService createAccountService(){
return new AccountServiceImpl();
}
}
<!-- 此种方式是:使用 StaticFactory 类中的静态方法 createAccountService 创建对象,并存入 spring 容器id 属性:指定 bean 的 id,用于从容器中获取class 属性:指定静态工厂的全限定类名factory-method 属性:指定生产对象的静态方法-->
<bean id="accountService" class="com.spring.factory.StaticFactory" factory-method="createAccountService"></bean>

第三种方式:spring 管理实例工厂-使用实例工厂的方法创建对象

1
2
3
4
5
6
7
8
public class InstanceFactory {
public IAccountService createAccountService(){
return new AccountServiceImpl();
}
}
<!-- 此种方式是:先把工厂的创建交给 spring 来管理。然后在使用工厂的 bean 来调用里面的方法factory-bean 属性:用于指定实例工厂 bean 的 id。factory-method 属性:用于指定实例工厂中创建对象的方法。-->
<bean id="instancFactory" class="com.spring.factory.InstanceFactory"></bean>
<bean id="accountService" factory-bean="instancFactory" factory-method="createAccountService"></bean>

3.4 spring 的依赖注入

依赖注入的概念

依赖注入:Dependency Inje ction。它是 spring 框架核心 ioc 的具体实现。

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

那这种业务层和持久层的依赖关系,在使用 spring 之后,就让 spring 来维护了。

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

构造函数注入

顾名思义,就是使用类中的构造函数,给成员变量赋值。注意,赋值的操作不是我们自己做的,而是通过配置的方式,让 spring 框架来为我们注入。具体代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class AccountServiceImpl implements IAccountService {

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 void saveAccount() {
System.out.println(name+","+age+","+birthday);
}
}

使用构造函数的方式,给 service 中的属性传值。
要求:类中需要提供一个对应参数列表的构造函数。
涉及的标签:constructor-arg
属性:

  • index:指定参数在构造函数参数列表的索引位置
  • type:指定参数在构造函数中的数据类型
  • name:指定参数在构造函数中的名称 用这个找给谁赋值
    =======上面三个都是找给谁赋值,下面两个指的是赋什么值的==============
  • value:它能赋的值是基本数据类型和 String 类型
  • ref:它能赋的值是其他 bean 类型,也就是说,必须得是在配置文件中配置过的 bean
1
2
3
4
5
6
<bean id="accountService" class="com.spring.service.impl.AccountServiceImpl">
<constructor-arg name="name" value="张三"></constructor-arg>
<constructor-arg name="age" value="18"></constructor-arg>
<constructor-arg name="birthday" ref="now"></constructor-arg>
</bean>
<bean id="now" class="java.util.Date"></bean>

测试:调用saveAccount方法

1
2
3
4
5
6
7
8
public static void main(String[] args) {
//1.获取核心容器对象
ApplicationContext ac = new ClassPathXmlApplicationContext("bean.xml");
//2.根据id获取Bean对象
IAccountService as = (IAccountService)ac.getBean("accountService");
//调用saveAccount
as.saveAccount();
}

set 方法注入

顾名思义,就是在类中提供需要注入成员的 set 方法。具体代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public class AccountServiceImpl implements IAccountService {
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 void saveAccount() {
System.out.println(name + "," + age + "," + birthday);
}
}

通过配置文件给 bean 中的属性传值:使用 set 方法的方式
涉及的标签:

  • property

属性:

  • name:找的是类中 set 方法后面的部分
  • ref:给属性赋值是其他 bean 类型的
  • value:给属性赋值是基本数据类型和 string 类型的
    实际开发中,此种方式用的较多。
1
2
3
4
5
6
<bean id="accountService" class="com.spring.service.impl.AccountServiceImpl">
<property name="name" value="test"></property>
<property name="age" value="21"></property>
<property name="birthday" ref="now"></property>
</bean>
<bean id="now" class="java.util.Date"></bean>

测试:调用saveAccount方法

1
2
3
4
5
6
7
8
public static void main(String[] args) {
//1.获取核心容器对象
ApplicationContext ac = new ClassPathXmlApplicationContext("bean.xml");
//2.根据id获取Bean对象
IAccountService as = (IAccountService)ac.getBean("accountService");
//调用saveAccount
as.saveAccount();
}

使用 p 名称空间注入数据

此种方式是通过在 xml 中导入 p 名称空间,使用 p:propertyName 来注入数据,它的本质仍然是调用类中的set 方法实现注入功能。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public class AccountServiceImpl implements IAccountService {
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 void saveAccount() {
System.out.println(name + "," + age + "," + birthday);
}
}
1
2
3
4
5
6
7
8
9
10
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:p="http://www.springframework.org/schema/p"
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="com.spring.service.impl.AccountServiceImpl"
p:name="test" p:age="21" p:birthday-ref="now"/>
<bean name="now" class="java.util.Date"></bean>
</beans>

测试:调用saveAccount方法

1
2
3
4
5
6
7
8
public static void main(String[] args) {
//1.获取核心容器对象
ApplicationContext ac = new ClassPathXmlApplicationContext("bean.xml");
//2.根据id获取Bean对象
IAccountService as = (IAccountService)ac.getBean("accountService");
//调用saveAccount
as.saveAccount();
}

注入集合属性

顾名思义,就是给类中的集合成员传值,它用的也是set方法注入的方式,只不过变量的数据类型都是集合。我们这里介绍注入数组,List,Set,Map,Properties。具体代码如下:

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
public class AccountServiceImpl implements IAccountService {

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 void saveAccount() {
System.out.println(Arrays.toString(myStrs));
System.out.println(myList);
System.out.println(mySet);
System.out.println(myMap);
System.out.println(myProps);
}
}

注入集合数据:

List 结构的:

  • array,list,set

Map 结构的

  • map,entry,props,prop
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
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:p="http://www.springframework.org/schema/p"
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="com.spring.service.impl.AccountServiceImpl">
<!-- 在注入集合数据时,只要结构相同,标签可以互换 -->
<!-- 给数组注入数据 -->
<property name="myStrs">
<set>
<value>AAA</value>
<value>BBB</value>
<value>CCC</value>
</set>
</property>
<!-- 注入 list 集合数据 -->
<property name="myList">
<array>
<value>AAA</value>
<value>BBB</value>
<value>CCC</value>
</array>
</property>
<!-- 注入 set 集合数据 -->
<property name="mySet">
<list>
<value>AAA</value>
<value>BBB</value>
<value>CCC</value>
</list>
</property>
<!-- 注入 Map 数据 -->
<property name="myMap">
<props>
<prop key="testA">aaa</prop>
<prop key="testB">bbb</prop>
</props>
</property>
<!-- 注入 properties 数据 -->
<property name="myProps">
<map>
<entry key="testA" value="aaa"></entry>
<entry key="testB">
<value>bbb</value>
</entry>
</map>
</property>
</bean>

</beans>

测试:调用saveAccount方法

1
2
3
4
5
6
7
8
public static void main(String[] args) {
//1.获取核心容器对象
ApplicationContext ac = new ClassPathXmlApplicationContext("bean.xml");
//2.根据id获取Bean对象
IAccountService as = (IAccountService)ac.getBean("accountService");
//调用saveAccount
as.saveAccount();
}

3.5 使用IoC实现账户的CRUD

环境搭建

数据库脚本

1
2
3
4
5
6
7
8
9
create table account(
id int primary key auto_increment,
name varchar(40),
money float
)character set utf8 collate utf8_general_ci;

insert into account(name,money) values('aaa',1000);
insert into account(name,money) values('bbb',1000);
insert into account(name,money) values('ccc',1000);

工程搭建

创建一个maven工程,目录结构如下:

pom文件

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
<?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>com.spring</groupId>
<artifactId>spring</artifactId>
<version>1.0-SNAPSHOT</version>
<packaging>jar</packaging>

<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.0.2.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>5.0.2.RELEASE</version>
</dependency>
<dependency>
<groupId>commons-dbutils</groupId>
<artifactId>commons-dbutils</artifactId>
<version>1.4</version>
</dependency>

<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.6</version>
</dependency>

<dependency>
<groupId>c3p0</groupId>
<artifactId>c3p0</artifactId>
<version>0.9.1.2</version>
</dependency>

<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
</dependency>
</dependencies>
</project>

IAccountService

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
/**
* 账户的业务层接口
*/
public interface IAccountService {

/**
* 查询所有
* @return
*/
List<Account> findAllAccount();

/**
* 查询一个
* @return
*/
Account findAccountById(Integer accountId);

/**
* 保存
* @param account
*/
void saveAccount(Account account);

/**
* 更新
* @param account
*/
void updateAccount(Account account);

/**
* 删除
* @param acccountId
*/
void deleteAccount(Integer acccountId);
}

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
36
/**
* 账户的业务层实现类
*/
public class AccountServiceImpl implements IAccountService{

private IAccountDao accountDao;

public void setAccountDao(IAccountDao accountDao) {
this.accountDao = accountDao;
}

@Override
public List<Account> findAllAccount() {
return accountDao.findAllAccount();
}

@Override
public Account findAccountById(Integer accountId) {
return accountDao.findAccountById(accountId);
}

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

@Override
public void updateAccount(Account account) {
accountDao.updateAccount(account);
}

@Override
public void deleteAccount(Integer acccountId) {
accountDao.deleteAccount(acccountId);
}
}

Account

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
/**
* 账户的实体类
*/
public class Account implements Serializable {

private Integer id;
private String name;
private Float money;

public Integer getId() {
return id;
}

public void setId(Integer id) {
this.id = id;
}

public String getName() {
return name;
}

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

public Float getMoney() {
return money;
}

public void setMoney(Float money) {
this.money = money;
}

@Override
public String toString() {
return "Account{" +
"id=" + id +
", name='" + name + '\'' +
", money=" + money +
'}';
}
}

IAccountDao

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
/**
* 账户的持久层接口
*/
public interface IAccountDao {

/**
* 查询所有
* @return
*/
List<Account> findAllAccount();

/**
* 查询一个
* @return
*/
Account findAccountById(Integer accountId);

/**
* 保存
* @param account
*/
void saveAccount(Account account);

/**
* 更新
* @param account
*/
void updateAccount(Account account);

/**
* 删除
* @param acccountId
*/
void deleteAccount(Integer acccountId);
}

AccountDaoImpl

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
52
53
54
55
56
/**
* 账户的持久层实现类
*/
public class AccountDaoImpl implements IAccountDao {

private QueryRunner runner;

public void setRunner(QueryRunner runner) {
this.runner = runner;
}

@Override
public List<Account> findAllAccount() {
try{
return runner.query("select * from account",new BeanListHandler<Account>(Account.class));
}catch (Exception e) {
throw new RuntimeException(e);
}
}

@Override
public Account findAccountById(Integer accountId) {
try{
return runner.query("select * from account where id = ? ",new BeanHandler<Account>(Account.class),accountId);
}catch (Exception e) {
throw new RuntimeException(e);
}
}

@Override
public void saveAccount(Account account) {
try{
runner.update("insert into account(name,money)values(?,?)",account.getName(),account.getMoney());
}catch (Exception e) {
throw new RuntimeException(e);
}
}

@Override
public void updateAccount(Account account) {
try{
runner.update("update account set name=?,money=? where id=?",account.getName(),account.getMoney(),account.getId());
}catch (Exception e) {
throw new RuntimeException(e);
}
}

@Override
public void deleteAccount(Integer accountId) {
try{
runner.update("delete from account where id=?",accountId);
}catch (Exception e) {
throw new RuntimeException(e);
}
}
}

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
<?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">
<!-- 配置Service -->
<bean id="accountService" class="com.spring.service.impl.AccountServiceImpl">
<!-- 注入dao -->
<property name="accountDao" ref="accountDao"></property>
</bean>

<!--配置Dao对象-->
<bean id="accountDao" class="com.spring.dao.impl.AccountDaoImpl">
<!-- 注入QueryRunner -->
<property name="runner" ref="runner"></property>
</bean>

<!--配置QueryRunner-->
<bean id="runner" class="org.apache.commons.dbutils.QueryRunner" scope="prototype">
<!--注入数据源-->
<constructor-arg name="ds" ref="dataSource"></constructor-arg>
</bean>

<!-- 配置数据源 -->
<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
<!--连接数据库的必备信息-->
<property name="driverClass" value="com.mysql.jdbc.Driver"></property>
<property name="jdbcUrl" value="jdbc:mysql://118.89.216.120:3306/spring"></property>
<property name="user" value="root"></property>
<property name="password" value="562644"></property>
</bean>
</beans>

测试类

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
/**
* 使用Junit单元测试:测试我们的配置
*/
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = "classpath:bean.xml")
public class AccountServiceTest {

@Autowired
private IAccountService as;

@Test
public void testFindAll() {
//3.执行方法
List<Account> accounts = as.findAllAccount();
for(Account account : accounts){
System.out.println(account);
}
}

@Test
public void testFindOne() {
//3.执行方法
Account account = as.findAccountById(1);
System.out.println(account);
}

@Test
public void testSave() {
Account account = new Account();
account.setName("test");
account.setMoney(12345f);
//3.执行方法
as.saveAccount(account);

}

@Test
public void testUpdate() {
//3.执行方法
Account account = as.findAccountById(4);
account.setMoney(23456f);
as.updateAccount(account);
}

@Test
public void testDelete() {
//3.执行方法
as.deleteAccount(4);
}
}

第4章 使用IOC解耦(注解)

学习基于注解的 IoC 配置,大家脑海里首先得有一个认知,即注解配置和 xml 配置要实现的功能都是一样的,都是要降低程序间的耦合。只是配置的形式不一样。关于实际的开发中到底使用xml还是注解,每家公司有着不同的使用习惯。所以这两种配置方式我们都需要掌握。

4.1 环境搭建

创建一个Maven工程,目录结构如下:

Pom文件配置如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<?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>com.spring</groupId> <!-- 对应包路径,根据自己项目实际情况酌情修改 -->
<artifactId>SpringIoc</artifactId> <!--对应自己项目名称-->
<version>1.0-SNAPSHOT</version>
<packaging>jar</packaging>

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

Bean配置文件:

1
2
3
4
5
6
<?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">
</beans>

IAccountDao

1
2
3
4
5
6
7
8
9
10
/**
* 账户的持久层接口
*/
public interface IAccountDao {

/**
* 模拟保存账户
*/
void saveAccount();
}

AccountDaoImp

1
2
3
4
5
6
7
8
9
10
/**
* 账户的持久层实现类
*/
public class AccountDaoImpl implements IAccountDao {

public void saveAccount(){

System.out.println("保存了账户");
}
}

IAccountService

1
2
3
4
5
6
7
8
9
10
/**
* 账户业务层的接口
*/
public interface IAccountService {

/**
* 模拟保存账户
*/
void saveAccount();
}

AccountServiceImpl

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
/**
* 账户的业务层实现类
*/
public class AccountServiceImpl implements IAccountService {

private IAccountDao accountDao ;

public AccountServiceImpl(){
System.out.println("对象创建了");
}

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

测试类

1
2
3
4
5
6
public class Client {
public static void main(String[] args) {
//1.获取核心容器对象
ApplicationContext ac = new ClassPathXmlApplicationContext("bean.xml");
}
}

4.2 常用注解

4.2.1 用于创建对象的

相当于:<bean id=”” class=””>

@Component

作用:把资源让 spring 来管理。相当于在 xml 中配置一个 bean。把当前使用@Component注解的类对象存入Spring容器中。

属性:
value:指定 bean 的 id。如果不指定 value 属性,默认 bean 的 id 是当前类的类名。且首字母小写。

测试案例1:

@Component不指定Value值:找到AccountServiceImpl,添加@Component注解,代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
/**
* 账户的业务层实现类
*/
@Component //此处添加@Component注解,不指定Value值
public class AccountServiceImpl implements IAccountService {

private IAccountDao accountDao ;

public AccountServiceImpl(){
System.out.println("对象创建了");
}

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

修改测试类:

1
2
3
4
5
6
7
8
9
10
11
12
/**
* 模拟一个表现层,用于调用业务层
*/
public class Client {
public static void main(String[] args) {
//1.获取核心容器对象
ApplicationContext ac = new ClassPathXmlApplicationContext("bean.xml");
//2.根据id获取Bean对象
IAccountService as = (IAccountService)ac.getBean("accountServiceImpl");
System.out.println(as);
}
}

我们运行Client方法,程序会报错,提示accountServiceImpl这个bean对象是无效的:

这是因为bean.xml,没有对应的配置信息,导致Spring报错。这时候我们只需要告诉Spring创建容器时候,需要扫描哪些包。

修改bean.xml如下:

1
2
3
4
5
6
7
8
9
10
11
12
<?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
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd">
<!--告知spring在创建容器时要扫描的包,配置所需要的标签不是在beans的约束中,而是一个名称为
context名称空间和约束中,这里需要导入context约束-->
<context:component-scan base-package="com.spring"></context:component-scan>
</beans>

这时候在运行Client方法,就可以运行成功了。

测试案例2:

@Component指定Value值:找到AccountServiceImpl,添加@Component(value = "accountService")注解,代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
/**
* 账户的业务层实现类
*/
@Component(value = "accountService") //此处添加@Component注解,指定Value值为accountService
public class AccountServiceImpl implements IAccountService {

private IAccountDao accountDao ;

public AccountServiceImpl(){
System.out.println("对象创建了");
}

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

修改测试类:

1
2
3
4
5
6
7
8
9
10
11
12
/**
* 模拟一个表现层,用于调用业务层
*/
public class Client {
public static void main(String[] args) {
//1.获取核心容器对象
ApplicationContext ac = new ClassPathXmlApplicationContext("bean.xml");
//2.根据id获取Bean对象
IAccountService as = (IAccountService)ac.getBean("accountServiceImpl"); //这里改成Value值:accountService
System.out.println(as);
}
}

再次运行,同样会运行成功

细节:如果@Component注解中有且只有一个属性要赋值时,且名称是 value,value 在赋值是可以不写。

@Controller @Service @Repository

他们三个注解都是针对@Component一个的衍生注解,他们的作用及属性都是和@Component一模一样的。他们只不过是提供了更加明确的语义化。

  • @Controller:一般用于表现层的注解。
  • @Service:一般用于业务层的注解。
  • @Repository:一般用于持久层的注解。

细节:如果注解中有且只有一个属性要赋值时,且名称是 value,value 在赋值是可以不写。

修改AccountDaoImpl,增加@Repository注解。

1
2
3
4
5
6
7
8
9
10
11
/**
* 账户的持久层实现类
*/
@Repository("accountDao")
public class AccountDaoImpl implements IAccountDao {

public void saveAccount(){

System.out.println("保存了账户");
}
}

修改测试类:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
/**
* 模拟一个表现层,用于调用业务层
*/
public class Client {
public static void main(String[] args) {
//1.获取核心容器对象
ApplicationContext ac = new ClassPathXmlApplicationContext("bean.xml");
//2.根据id获取Bean对象
IAccountService as = (IAccountService)ac.getBean("accountService");
IAccountDao accountDao=ac.getBean("accountDao",IAccountDao.class);
System.out.println(as);
System.out.println(accountDao);
}
}

运行成功,说明我们能获取到对象。

4.2.2 用于注入数据的

相当于:<property name=”” ref=””>、<property name=”” value=””>

如果把测试类改成如图所示:

1
2
3
4
5
6
7
8
9
public class Client {
public static void main(String[] args) {
//1.获取核心容器对象
ApplicationContext ac = new ClassPathXmlApplicationContext("bean.xml");
//2.根据id获取Bean对象
IAccountService as = (IAccountService)ac.getBean("accountService");
as.saveAccount();
}
}

此时运行会报空指针错误,是因为我们没有注入对应的数据

@Autowired

作用:自动按照类型注入。只要容器中有唯一的一个bean对象类型和要注入的变量类型匹配,就可以注入成功。 如果ioc容器中没有任何bean的类型和要注入的变量类型匹配,则报错。
出现位置:可以是变量上,也可以是方法上
细节:在使用注解注入时,set方法就不是必须的了。

我们修改AccountServiceImpl,增加@Autowired注解:

1
2
3
4
5
6
7
8
9
10
11
12
/**
* 账户的业务层实现类
*/
@Component("accountService")
public class AccountServiceImpl implements IAccountService {
@Autowired
private IAccountDao accountDao;

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

再次运行测试类,会正常调用saveAccount()方法

1
2
3
4
5
6
7
8
9
public class Client {
public static void main(String[] args) {
//1.获取核心容器对象
ApplicationContext ac = new ClassPathXmlApplicationContext("bean.xml");
//2.根据id获取Bean对象
IAccountService as = (IAccountService)ac.getBean("accountService");
as.saveAccount();
}
}

此时控制台输出:保存了账户

@Autowired自动按照类型注入,容器中有多个bean对象类型和要注入的变量类型匹配 的情况

com.spring.dao.impl目录下新建一个文件:

1
2
3
4
5
6
7
8
9
10
11
/**
* 账户的持久层实现类
*/
@Repository("accountDao2")
public class AccountDaoImpl2 implements IAccountDao {

public void saveAccount(){

System.out.println("保存了账户2222222222222");
}
}

修改AccountDaoImpl为:

1
2
3
4
5
6
7
8
9
10
11
/**
* 账户的持久层实现类
*/
@Repository("accountDao1")
public class AccountDaoImpl implements IAccountDao {

public void saveAccount(){

System.out.println("保存了账户1");
}
}

运行测试类

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
public class Client {
public static void main(String[] args) {
//1.获取核心容器对象
ApplicationContext ac = new ClassPathXmlApplicationContext("bean.xml");
//2.根据id获取Bean对象
IAccountService as = (IAccountService)ac.getBean("accountService");
as.saveAccount();
}
}

会报如下错误:
```xml
警告: Exception encountered during context initialization - cancelling refresh attempt: org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'accountService': Unsatisfied dependency expressed through field 'accountDao'; nested exception is org.springframework.beans.factory.NoUniqueBeanDefinitionException: No qualifying bean of type 'com.spring.dao.IAccountDao' available: expected single matching bean but found 2: accountDao1,accountDao2
Exception in thread "main" org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'accountService': Unsatisfied dependency expressed through field 'accountDao'; nested exception is org.springframework.beans.factory.NoUniqueBeanDefinitionException: No qualifying bean of type 'com.spring.dao.IAccountDao' available: expected single matching bean but found 2: accountDao1,accountDao2
at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor$AutowiredFieldElement.inject(AutowiredAnnotationBeanPostProcessor.java:586)
at org.springframework.beans.factory.annotation.InjectionMetadata.inject(InjectionMetadata.java:91)
at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor.postProcessPropertyValues(AutowiredAnnotationBeanPostProcessor.java:372)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.populateBean(AbstractAutowireCapableBeanFactory.java:1344)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:582)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:502)
at org.springframework.beans.factory.support.AbstractBeanFactory.lambda$doGetBean$0(AbstractBeanFactory.java:312)
at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:228)
at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:310)
at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:200)
at org.springframework.beans.factory.support.DefaultListableBeanFactory.preInstantiateSingletons(DefaultListableBeanFactory.java:758)
at org.springframework.context.support.AbstractApplicationContext.finishBeanFactoryInitialization(AbstractApplicationContext.java:868)
at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:549)
at org.springframework.context.support.ClassPathXmlApplicationContext.<init>(ClassPathXmlApplicationContext.java:144)
at org.springframework.context.support.ClassPathXmlApplicationContext.<init>(ClassPathXmlApplicationContext.java:85)
at com.spring.ui.Client.main(Client.java:14)
Caused by: org.springframework.beans.factory.NoUniqueBeanDefinitionException: No qualifying bean of type 'com.spring.dao.IAccountDao' available: expected single matching bean but found 2: accountDao1,accountDao2
at org.springframework.beans.factory.config.DependencyDescriptor.resolveNotUnique(DependencyDescriptor.java:215)
at org.springframework.beans.factory.support.DefaultListableBeanFactory.doResolveDependency(DefaultListableBeanFactory.java:1113)
at org.springframework.beans.factory.support.DefaultListableBeanFactory.resolveDependency(DefaultListableBeanFactory.java:1062)
at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor$AutowiredFieldElement.inject(AutowiredAnnotationBeanPostProcessor.java:583)
... 15 more

我们仔细看这一句报错:
No qualifying bean of type 'com.spring.dao.IAccountDao' available: expected single matching bean but found 2: accountDao1,accountDao2:预期会有唯一的一个bean对象,但是找到了两个。

这时候我们修改AccountServiceImpl如下:

1
2
3
4
5
6
7
8
9
@Component("accountService")
public class AccountServiceImpl implements IAccountService {
@Autowired
private IAccountDao accountDao1; //因为我们之前注入了accountDao·、accountDao2,这里调用accountDao1,和accountDao2效果一样

public void saveAccount() {
accountDao1.saveAccount();
}
}

再次运行就成功了

如果有一个匹配时,直接注入。如果有多个匹配时,首先按照类型圈定出匹配的对象,使用变量名称作为bean的ID查找,在圈定出来的里面继续查找。查找到注入成功。查找不到,注入失败。如果使用@Autowired,有两个bean都符合相同类型时,需要改对应的ID,但这并不是我们想看见的。

@Qualifier

作用:
在自动按照类型注入的基础之上,再按照 Bean 的 id 注入。它在给类成员(字段)注入时不能独立使用,必须和@Autowire 一起使用;但是给方法参数注入时,可以独立使用。
属性:
value:指定 bean 的 id。

示例代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
**
* 账户的业务层实现类
*/
@Component("accountService")
public class AccountServiceImpl implements IAccountService {
@Autowired
@Qualifier(value = "accountDao1")
private IAccountDao accountDao;

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

@Resource

作用:
直接按照 Bean 的 id 注入。它也只能注入其他 bean 类型。
属性:
name:指定 bean 的 id。

示例代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
/**
* 账户的业务层实现类
*/
@Component("accountService")
public class AccountServiceImpl implements IAccountService {
/* @Autowired
@Qualifier(value = "accountDao1")*/
@Resource(name = "accountDao1")
private IAccountDao accountDao;

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

以上三个注入都只能注入其他bean类型的数据,而基本类型和String类型无法使用上述注解实现。
另外,集合类型的注入只能通过XML来实现。

@Value

作用:
注入基本数据类型和 String 类型数据的
属性:
value:用于指定值。它可以使用spring中SpEL(也就是spring的el表达式)。SpEL的写法:${表达式}

4.2.3 用于改变作用范围的

相当于:<bean id=”” class=”” scope=””>

@Scope

作用:用于指定bean的作用范围
属性:
value:指定范围的取值。常用取值:singleton(单例) prototype(多例)。默认是单例。

示例代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
/**
* 模拟一个表现层,用于调用业务层
*/
public class Client {
public static void main(String[] args) {
//1.获取核心容器对象
ApplicationContext ac = new ClassPathXmlApplicationContext("bean.xml");
//2.根据id获取Bean对象
IAccountService as = (IAccountService)ac.getBean("accountService");
IAccountService as1 = (IAccountService)ac.getBean("accountService");
System.out.println(as==as1);
}
}

输出结果为:true,证明默认是单例的。

使用@Scope注解,修改成多例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
/**
* 账户的业务层实现类
*/
@Component("accountService")
@Scope("prototype") //多例
public class AccountServiceImpl implements IAccountService {
/* @Autowired
@Qualifier(value = "accountDao1")*/
@Resource(name = "accountDao1")
private IAccountDao accountDao;

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

输出结果为:false,证明目前是多例的。

4.2.4 和生命周期相关的 (了解)

相当于:<bean id=”” class=”” init-method=”” destroy-method=””>

@PostConstruct

作用:用于指定初始化方法。

@PreDestroy

作用:用于指定销毁方法。

示例代码:

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
/**
* 账户的业务层实现类
*/
@Component("accountService")
//@Scope("prototype") 必须是单例对象销毁,多例对象销毁Spring是不负责的
public class AccountServiceImpl implements IAccountService {
/* @Autowired
@Qualifier(value = "accountDao1")*/
@Resource(name = "accountDao1")
private IAccountDao accountDao;

@PostConstruct
public void init() {
System.out.println("初始化方法执行了");
}

@PreDestroy
public void destroy() {
System.out.println("销毁方法执行了");
}

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

修改测试类:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
/**
* 模拟一个表现层,用于调用业务层
*/
public class Client {
public static void main(String[] args) {
//1.获取核心容器对象
//ApplicationContext ac = new ClassPathXmlApplicationContext("bean.xml");
ClassPathXmlApplicationContext ac=new ClassPathXmlApplicationContext("bean.xml");
//2.根据id获取Bean对象
IAccountService as = (IAccountService)ac.getBean("accountService");
as.saveAccount();
ac.close(); //一旦使用父类型关闭,无法关闭。因为子类的方法父类没有,所以这里我们使用子类对象
}
}

输出:
初始化方法执行了
保存了账户1
销毁方法执行了

4.3 关于Spring注解和XML的选择问题

注解的优势:配置简单,维护方便(我们找到类,就相当于找到了对应的配置)。
XML的优势:修改时,不用改源码。不涉及重新编译和部署。

4.4 spring 管理对象细节

基于注解的 spring IoC 配置中,bean 对象的特点和基于 XML 配置是一模一样的。

4.5 spring 的纯注解配置

写到此处,基于注解的 IoC 配置已经完成,但是有一个问题:我们依然离不开 spring 的 xml 配置文件,那么能不能不写这个 bean.xml,所有配置都用注解来实现呢?
当然,大家也需要注意一下,我们选择哪种配置的原则是简化开发和配置方便,而非追求某种技术

4.5.1 待改造的问题

我们发现,之所以我们现在离不开 xml 配置文件,是因为我们有一些很关键的配置:

1
2
 <!-- 告知spring在创建容器时要扫描的包 -->
<context:component-scan base-package="com.spring"></context:component-scan>

如果以上也能用注解配置,那么我们就离脱离 xml 文件又进了一步。

另外,数据源和 runner 的配置也需要靠注解来实现,这样就可以完全脱离xml了。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<!--配置QueryRunner-->
<bean id="runner" class="org.apache.commons.dbutils.QueryRunner" scope="prototype">
<!--注入数据源-->
<constructor-arg name="ds" ref="dataSource"></constructor-arg>
</bean>

<!-- 配置数据源 -->
<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
<!--连接数据库的必备信息-->
<property name="driverClass" value="com.mysql.jdbc.Driver"></property>
<property name="jdbcUrl" value="jdbc:mysql://localhost:3306/eesy"></property>
<property name="user" value="root"></property>
<property name="password" value="1234"></property>
</bean>

4.5.2 新注解说明

@Configuration

作用:用于指定当前类是一个 spring 配置类,当创建容器时会从该类上加载注解。获取容器时需要使用AnnotationApplicationContext(有@Configuration 注解的类.class)。
细节:当配置类作为AnnotationConfigApplicationContext对象创建的参数时,该注解可以不写。
属性:value:用于指定配置类的字节码

示例代码:

1
2
3
4
5
6
/**
* spring 的配置类,相当于 bean.xml 文件
*/
@Configuration
public class SpringConfiguration {
}

注意:我们已经把配置文件用类来代替了,但是如何配置创建容器时要扫描的包呢?请看下一个注解。

@ComponentScan

作用:用于通过注解指定spring在创建容器时要扫描的包
属性:

  • value:它和basePackages的作用是一样的,都是用于指定创建容器时要扫描的包。

我们使用此注解就等同于在xml中配置了:

1
<context:component-scan base-package="com.spring"></context:component-scan>

示例代码:

1
2
3
4
5
6
/**
* spring 的配置类,相当于 bean.xml 文件
*/
@ComponentScan("com.spring")
public class SpringConfiguration {
}

注意:我们已经配置好了要扫描的包,但是数据源和 QueryRunner 对象如何从配置文件中移除呢?请看下一个注解。

@Bean

作用:用于把当前方法的返回值作为bean对象存入spring的ioc容器中
属性:

  • name:用于指定bean的id。当不写时,默认值是当前方法的名称
    细节:

  • 当我们使用注解配置方法时,如果方法有参数,spring框架会去容器中查找有没有可用的bean对象。

  • 查找的方式和Autowired注解的作用是一样的

    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
     /**
    * 和spring连接数据库相关的配置类
    */
    public class JdbcConfig {
    /**
    * 用于创建一个QueryRunner对象
    * @param dataSource
    * @return
    */
    @Bean(name="runner")
    @Scope("prototype") //多例,单例线程不安全
    public QueryRunner createQueryRunner(@Qualifier("ds2") DataSource dataSource){
    return new QueryRunner(dataSource);
    }

    /**
    * 创建数据源对象
    * @return
    */
    @Bean(name="ds1")
    public DataSource createDataSource1(){
    try {
    ComboPooledDataSource ds = new ComboPooledDataSource();
    ds.setDriverClass("com.mysql.jdbc.Driver"");
    ds.setJdbcUrl("jdbc:mysql://localhost:3306/test");
    ds.setUser("root");
    ds.setPassword("1234");
    return ds;
    }catch (Exception e){
    throw new RuntimeException(e);
    }
    }
    }

注意:我们已经把数据源和 QueryRunner 从配置文件中移除了,此时可以删除 bean.xml 了。但是由于没有了配置文件,创建数据源的配置又都写死在类中了。如何把它们配置出来呢?请看下一个注解。

@PropertySource

作用:用于指定properties文件的位置
]属性:

  • value:指定文件的名称和路径。如果是在类路径下,需要写上 classpath:

  • 关键字:classpath,表示类路径下

    示例代码:

  • jdbc.properties 文件:*

    1
    2
    3
    4
    jdbc.driver=com.mysql.jdbc.Driver
    jdbc.url=jdbc:mysql://localhost:3306/test
    jdbc.username=root
    jdbc.password=1234
    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
     /**
    * 和spring连接数据库相关的配置类
    */
    public class JdbcConfig {

    @Value("${jdbc.driver}")
    private String driver;

    @Value("${jdbc.url}")
    private String url;

    @Value("${jdbc.username}")
    private String username;

    @Value("${jdbc.password}")
    private String password;

    /**
    * 用于创建一个QueryRunner对象
    * @param dataSource
    * @return
    */
    @Bean(name="runner")
    @Scope("prototype") //多例,单例线程不安全
    public QueryRunner createQueryRunner(@Qualifier("ds2") DataSource dataSource){
    return new QueryRunner(dataSource);
    }

    /**
    * 创建数据源对象
    * @return
    */
    @Bean(name="ds1")
    public DataSource createDataSource1(){
    try {
    ComboPooledDataSource ds = new ComboPooledDataSource();
    ds.setDriverClass("com.mysql.jdbc.Driver"");
    ds.setJdbcUrl("jdbc:mysql://localhost:3306/test");
    ds.setUser("root");
    ds.setPassword("1234");
    return ds;
    }catch (Exception e){
    throw new RuntimeException(e);
    }
    }
    }

    注意:此时我们已经有了两个配置类,但是他们还没有关系。如何建立他们的关系呢?请看下一个注解。

@Import

作用:用于导入其他的配置类,在引入其他配置类时,可以不用再写@Configuration 注解。当然,写上也没问题。
属性:

  • value:用于指定其他配置类的字节码。
    当我们使用Import的注解之后,有Import注解的类就父配置类,而导入的都是子配置类

    示例代码:

    1
    2
    3
    4
    5
    6
     //@Configuration
    @ComponentScan("com.spring")
    @Import(JdbcConfig.class)
    @PropertySource("classpath:jdbcConfig.properties")
    public class SpringConfiguration {
    }

注意:我们已经把要配置的都配置好了,但是新的问题产生了,由于没有配置文件了,如何获取容器呢?请看下一小节。

1
ApplicationContext ac = new AnnotationConfigApplicationContext(SpringConfiguration.class);

4.5.3 目录结构

第5章 Spring 整合 Junit

5.1 问题

在测试类中,每个测试方法都有以下两行代码:

1
2
ApplicationContext ac = new ClassPathXmlApplicationContext("bean.xml");
IAccountService as = ac.getBean("accountService",IAccountService.class);

这两行代码的作用是获取容器,如果不写的话,直接会提示空指针异常。所以又不能轻易删掉。

5.2 解决思路分析

针对上述问题,我们需要的是程序能自动帮我们创建容器。一旦程序能自动为我们创建 spring 容器,我们就无须手动创建了,问题也就解决了。

我们都知道,junit 单元测试的原理(在 web 阶段课程中讲过),但显然,junit 是无法实现的,因为它自己都无法知晓我们是否使用了 spring 框架,更不用说帮我们创建 spring 容器了。不过好在,junit 给我们暴露了一个注解,可以让我们替换掉它的运行器。

这时,我们需要依靠 spring 框架,因为它提供了一个运行器,可以读取配置文件(或注解)来创建容器。我们只需要告诉它配置文件在哪就行了。

5.3 配置步骤

Spring整合junit的配置
1、导入spring整合junit的jar(坐标)
2、使用Junit提供的一个注解把原有的main方法替换了,替换成spring提供的@Runwith
3、告知spring的运行器,spring和ioc创建是基于xml还是注解的,并且说明位置
4、@ContextConfiguration

  • locations:指定xml文件的位置,如果是类路径下,需要用 classpath:表明
  • classes:指定注解类所在地位置

注:当我们使用spring 5.x版本的时候,要求junit的jar必须是4.12及以上

使用@RunWith 注解替换原有运行器使用@RunWith 注解替换原有运行器

1
2
3
4
5
6
@RunWith(SpringJUnit4ClassRunner.class)  //使用@RunWith 注解替换原有运行器(Main方法)
@ContextConfiguration(classes = SpringConfiguration.class) //指定 spring 配置文件的位置
public class AccountServiceTest {
@Autowired //给测试类中的变量注入数据
private IAccountService as = null;
}

5.4 为什么不把测试类配到 xml 中?

在解释这个问题之前,先解除大家的疑虑,配到 XML 中能不能用呢?
答案是肯定的,没问题,可以使用。
那么为什么不采用配置到 xml 中的方式呢?
这个原因是这样的:

  • 第一:当我们在 xml 中配置了一个 bean,spring 加载配置文件创建容器时,就会创建对象。
  • 第二:测试类只是我们在测试功能时使用,而在项目中它并不参与程序逻辑,也不会解决需求上的问题,所以创建完了,并没有使用。那么存在容器中就会造成资源的浪费。
    所以,基于以上两点,我们不应该把测试配置到 xml 文件中。

第6章 AOP概念和Spring中的AOP

6.1 什么是 AOP

AOP:全称是 Aspect Oriented Programming 即:面向切面编程。
简单的说它就是把我们程序重复的代码抽取出来,在需要执行的时候,使用动态代理的技术,在不修改源码的基础上,对我们的已有方法进行增强。

6.2 AOP 的作用、优势以及实现方式

作用:在程序运行期间,不修改源码对已有方法进行增强。
实现方式:使用动态代理技术
优势:

  • 减少重复代码
  • 提高开发效率
  • 维护方便
文章作者: LiuYu
文章链接: https://www.liuyu.pw/posts/1821512031.html
版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 LiuYuBLOG

评论