Spring(黑马)
课程介绍
简化开发:Spring框架中提供了两个大的核心技术,分别是:- IOC
- AOP
- 事务处理
- IoC和AOP是Spring的重要知识点,事务处理可以简化项目中的事务管理,也是一大亮点
框架整合:Spring整合了市面几乎所有主流框架,如Hibernate、MyBatis、Struts2等
Spring相关概念
Spring发展史
Spring发展至今早已形成了一种开发的生态圈,提供了若干个项目,这些项目组合起来便被称为Spring全家桶

可以到Spring官方网站查看具体图标的含义,这里重点关注
Spring Framework、SpringBoot和SpringCloud:- Spring Framework:Spring框架,是Spring中最早最核心的技术,也是所有其他技术的基础。
- SpringBoot:Spring是来简化开发,而SpringBoot是来帮助Spring在简化的基础上能更快速进行开发。
- SpringCloud:这个是用来做分布式之微服务架构的相关开发。
接下来介绍Spring Framework是怎么来的:

- IBM(IT公司-国际商业机器公司)在1997年提出了EJB思想,早期的JAVAEE开发大都基于该思想。
- Rod Johnson(Java和J2EE开发领域的专家)在2002年出版的
Expert One-on-One J2EE Design and Development,书中有阐述在开发中使用EJB该如何做。 - Rod Johnson在2004年出版的
Expert One-on-One J2EE Development without EJB,书中提出了比EJB思想更高效的实现方案,并且在同年将方案进行了具体的落地实现,这个实现就是Spring1.0。 - 随着时间推移,版本不断更新维护,目前最新的是Spring5
- Spring1.0是纯配置文件开发
- Spring2.0为了简化开发引入了注解开发,此时是配置文件加注解的开发方式
- Spring3.0已经可以进行纯注解开发,使开发效率大幅提升,我们的课程会以注解开发为主
- Spring4.0根据JDK的版本升级对个别API进行了调整
- Spring5.0已经全面支持JDK8,所以学习期间最好用JDK8版本
- 本节介绍了Spring家族与Spring的发展史,需要我们重点掌握的是:
- Spring其实是Spring家族中的Spring Framework
- Spring Framework是Spring家族中其他框架的底层基础,学好Spring可以为其他Spring框架的学习打好基础
Spring系统架构
- SpringFramework是Spring其他项目的根基。
- Spring Framework的发展也经历了很多版本的变更,每个版本都有相应的调整

- Spring Framework的5版本目前没有最新的架构图,而最新的是4版本,所以接下来主要研究的是4的架构图

- 核心层
- Core Container:核心容器,这个模块是Spring最核心的模块,其他的都需要依赖该模块
- AOP层
- AOP(Aspect Oriented Programming):面向切面编程,它依赖核心层容器,目的是在不改变原有代码的前提下对其进行功能增强
- Aspects:AOP是思想,Aspects是对AOP思想的具体实现
- 数据层
- Data Access:数据访问,Spring全家桶中有对数据访问的具体实现技术
- Data Integration:数据集成,Spring支持整合其他的数据层解决方案,比如Mybatis
- Transactions:事务,Spring中事务管理是Spring AOP的一个具体实现,也是后期学习的重点内容
- Web层
- 这一层的内容将在SpringMVC框架具体学习
- Test层
- Spring主要整合了Junit来完成单元测试和集成测试
Spring学习路线
- Spring的IOC/DI
- Spring的AOP
- AOP的具体应用,事务管理
- IOC/DI的具体应用,整合Mybatis

Spring核心概念
这部分主要包括IOC/DI、IOC容器和Bean
目前的问题
分析代码在编写中的问题:
- 业务层需要调用数据层的方法,就需要在业务层new数据层的对象
- 如果数据层的实现类发生变化,那么业务层的代码也需要跟着改变,发生变更后,都需要进行编译打包和重部署
1 | public class BookServlet extends BookServlet { |
假如BookDaoImpl2比BookDaoImpl1好用,我们想更换数据层实现类,那么业务层的代码也得跟着变,耦合度太高了
1 | public class BookDaoImpl1 implements BookDao { |
1 | public class BookDaoImpl2 implements BookDao { |
- 主要耦合度高的原因是在业务层中直接new了数据层的实现类,如果我们不new对象,只声明一下,数据层跟业务层不就解耦了吗?
- 答案显然不行,因为不new对象此时BookDao是一个空引用,强行调用save()方法会报
空指针异常,所以现在主要问题是如何做到我们不创建对象,但运行时数据层对象被创建,且被赋值给业务层,这该如何实现?
- 答案显然不行,因为不new对象此时BookDao是一个空引用,强行调用save()方法会报
- 针对该问题,Spring提出的解决方案:使用对象时,程序中不再主动new对象,而让Spring为我们提供并管理这个对象
IOC/DI
IOC(Inversion of Control)控制反转
什么是控制反转?
- 控制反转是一种更宽泛的
设计模式或架构原则,能够用来降低代码代码之间的耦合度,符合依赖倒置原则。 - 控制反转的核心是:将对象的创建权交出去,将对象和对象之间关系的管理权交出去,有第三方容器来负责创建与维护
- 控制反转是一种更宽泛的
Spring和IOC之间的关系是什么呢?
- Spring技术对IOC思想进行了实现
- Spring提供了一个容器,称为
IOC容器,用来充当IOC思想中的”外部” - IOC思想中的
别人[外部]指的就是Spring的IOC容器
- IOC容器的作用以及内部存放的是什么?
- IOC容器负责对象的创建、初始化等一系列工作,其中包含了数据层和业务层的类对象
- 被创建或被管理的对象在IOC容器中统称为
Bean
- 当IOC容器中创建好service和dao对象后,程序就能正确执行了吗?
- 不行,因为service运行需要依赖dao对象
- IOC容器中虽然有service和dao对象,但此时service对象和dao对象是两个单独的类,没有任何关系,需要把dao对象交给service,也就是说要绑定service和dao对象之间的关系才行
- 像这种在容器中建立对象与对象之间的绑定关系就要用到
DI(Dependency Injection)依赖注入.
- DI(Dependency Injection)依赖注入
- 什么是依赖注入?
- 在容器中建立bean与bean之间的依赖关系的整个过程,称为
依赖注入- 业务层要用数据层的类对象,以前是自己
new的 - 现在自己不new了,靠
别人[外部其实指的就是IOC容器]来给注入进来
- 业务层要用数据层的类对象,以前是自己
- 在容器中建立bean与bean之间的依赖关系的整个过程,称为
- IOC容器中哪些bean之间要建立依赖关系呢?
- 这个需要程序员根据业务需求提前建立好关系,如业务层需要依赖数据层,service就要和dao建立依赖关系
- 我们会发现Spring容器中IOC和AOP这两个概念的最终目标就是:
充分解耦,具体实现靠:- 使用IOC容器管理bean(IOC)
- 在IOC容器内将有依赖关系的bean进行关系绑定(DI)
- 最终结果为:使用对象时不仅可以直接从IOC容器中获取,并且获取到的bean已经绑定了所有的依赖关系。
- 什么是依赖注入?
核心概念小结
- 什么是IOC/DI思想?
- IOC:控制反转,控制反转的是对象的创建权,由Spring为我们创建并管理对象
- DI:依赖注入,绑定对象与对象之间的依赖关系,即给对象的属性赋值
- 什么是IOC容器?
- Spring创建了一个容器用来存放所创建的对象,这个容器就叫IOC容器
- 什么是Bean?
- 容器中所存放的一个个对象就叫Bean
入门案例
IOC入门案例
- 创建Maven项目
- 添加Spring需要的依赖,仅仅使用IOC功能则引入Spring-context这个jar包即可
1
2
3
4
5<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.2.10.RELEASE</version>
</dependency> - 创建BookDao,BookDaoImpl,BookService和BookServiceImpl四个类
1
2
3public interface BookDao {
public void save();
}1
2
3
4
5public class BookDaoImpl implements BookDao {
public void save() {
System.out.println("book dao save ...");
}
}1
2
3public interface BookService {
public void save();
}1
2
3
4
5
6
7
8public class BookServiceImpl implements BookService {
// 这里不考虑依赖注入,仅仅学习IOC相关内容,所以手动new了一个BookDaoImpl对象
private BookDao bookDao = new BookDaoImpl();
public void save() {
System.out.println("book service save ...");
bookDao.save();
}
} - 创建Spring配置文件
在resource目录下新建->XML配置文件->Spring配置
- 在配置文件中完成Bean的配置
1
2
3
4
5
6
7
8
9
10
11
12
<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">
<!--
id属性标示给bean起名字
class属性写类的全路径,得是具体的实现类而不是接口,要靠这个造对象
还可以写name属性,作用和id一样,可以用来给对象起别名
-->
<bean id="bookDao" class="com.blog.dao.impl.BookDaoImpl"/>
<bean id="bookService" class="com.blog.service.impl.BookServiceImpl"/>
</beans> - 获取IOC容器
使用Spring提供的接口完成IoC容器的获取1
2
3
4
5
6
7public class App {
// ClassPathXmlApplicationContext是使用配置文件的方式获取IOC容器
public static void main(String[] args) {
ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
}
} - 从容器中获取对象并调用方法
1
2
3
4
5
6
7public class App {
public static void main(String[] args) {
ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
BookService bookService = (BookService) context.getBean("bookService");
bookService.save();
}
} - 运行程序
测试结果:book service save …
book dao save …
DI入门案例
- 删除service层new对象的行为
1
2
3
4
5
6
7
8public class BookServiceImpl implements BookService {
//private BookDao bookDao = new BookDaoImpl();
private BookDao bookDao;
public void save() {
System.out.println("book service save ...");
bookDao.save();
}
} - 在业务层提供需要注入的属性的set方法
1
2
3
4
5
6
7
8
9
10
11
12
13
14public class BookServiceImpl implements BookService {
private BookDao bookDao;
public void save() {
System.out.println("book service save ...");
bookDao.save();
}
public void setBookDao(BookDao bookDao) {
this.bookDao = bookDao;
// 添加该语句检验set方法是否被调用
System.out.println("set方法被调用啦");
}
} - 在配置文件中使用
<property>标签完成依赖注入1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<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="bookDao" class="com.blog.dao.impl.BookDaoImpl"/>
<!--主要变化在这里-->
<bean id="bookService" class="com.blog.service.impl.BookServiceImpl">
<!--配置server与dao的关系-->
<!--
property标签表示配置当前bean的属性
name属性表示配置哪一个具体的属性(这里是配置bookService的bookDao属性)
ref属性表示参照哪一个bean(需要当前配置文件中配置的bean的id)
-->
<property name="bookDao" ref="bookDao"></property>
</bean>
</beans>
- 运行程序
测试结果:set方法被调用啦
book service save …
book dao save …
IOC相关内容
Bean基础配置
name
name属性的作用和id属性一样,可以用来给bean起别名,name属性可以省略
- 配置别名
1
2
3
4
5
6
7
8
9
10
<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="bookDao" class="com.blog.dao.impl.BookDaoImpl"/>
<!--别名可以有多个,使用逗号,分号,空格进行分隔都可以-->
<bean id="bookService" name="service1 service2 service3" class="com.blog.service.impl.BookServiceImpl">
<property name="bookDao" ref="bookDao"></property>
</bean>
</beans> - 根据别名来获取Bean对象
1
2
3
4
5
6
7
8public class App {
public static void main(String[] args) {
ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
//此处根据bean标签的id属性和name属性的任意一个值来获取bean对象
BookService bookService = (BookService) context.getBean("service2");
bookService.save();
}
} - 运行程序
测试结果:set方法被调用啦
book service save …
book dao save …
scope
关于bean的作用范围是bean属性配置的一个重点内容。
bean的scope有两个取值:
- singleton:单例(默认)
- prototype:非单例
验证对象是否为单例
代码实现:输出结果发现地址值一样,则证明默认创建的为单例对象1
2
3
4
5
6
7
8
9
10public class App {
public static void main(String[] args) {
ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
//我这里使用了别名,其实还是同一个bean
BookService bookService2 = (BookService) context.getBean("service2");
BookService bookService3 = (BookService) context.getBean("service3");
System.out.println(bookService2);
System.out.println(bookService3);
}
}com.blog.service.impl.BookServiceImpl@25bbe1b6
com.blog.service.impl.BookServiceImpl@25bbe1b6
配置Bean为非单例
正常情况下,Spring为我们创建的对象都是单例的,如果想要创建非单例的对象,需要我们在配置文件中配置scope属性
- 在Spring的配置文件中,修改
<bean>的scope属性此时重新验证,便会发现地址值不一致。1
2
3<bean id="bookService" name="service1 service2 service3" class="com.blog.service.impl.BookServiceImpl" scope="prototype">
<property name="bookDao" ref="bookDao"></property>
</bean>
Scope总结
- 为什么Spring创建Bean默认为单例?
- Bean默认是单例意味着Spring的IOC容器只会存有该类的一个对象
- 对象只有一个有效避免了对象的频繁创建和销毁,达到了Bean对象的复用,性能更高
- Bean对象是单例的,是否会产生线程安全问题?
- 如果对象是有状态对象,即该对象有成员变量可以用来存储数据的,因为所有请求线程共用一个bean对象,所以会存在线程安全问题。
- 如果对象是无状态对象,即该对象没有成员变量没有进行数据存储的,因方法中的局部变量在方法调用完成后会被销毁,所以不会存在线程安全问题。
- 哪些bean对象适合交给容器进行管理?
- 表现层对象(controller)
- 业务层对象(service)
- 数据层对象(dao)
- 工具对象(util)
- 哪些bean对象不适合交给容器进行管理?
- 我们了解了通过配置文件来实例化Bean对象,但是容器是如何为我们创建对象的呢?
- 在之前的Dao实现类中为无参构造添加一句话,方便观察结果。输出结果:
1
2
3
4
5
6
7
8
9public class BookDaoImpl implements BookDao {
public void save() {
System.out.println("book dao save ...");
}
public BookDaoImpl() {
System.out.println("book dao constructor is running ...");
}
}book dao constructor is running …
com.blog.service.impl.BookServiceImpl@25bbe1b6
- 将构造器私有化后,继续进行测试
1
2
3
4
5
6
7
8
9public class BookDaoImpl implements BookDao {
public void save() {
System.out.println("book dao save ...");
}
private BookDaoImpl() {
System.out.println("book dao constructor is running ...");
}
}
运行程序,发现将构造器私有化,仍然能访问到构造方法来实例化Bean,这说明Spring底层通过反射调用构造方法来实例化Bean对象
book dao constructor is running …
com.blog.service.impl.BookServiceImpl@25bbe1b6
静态工厂实例化
创建一个工厂类,在工厂类中提供一个静态方法,用来创建Bean对象
1
2
3
4
5public class BookDaoFactory {
public static BookDao getBookDao() {
return new BookDaoImpl();
}
}修改App运行类,通过工厂来获得对象
1
2
3
4
5
6
7public class App {
public static void main(String[] args) {
//通过静态工厂创建对象
BookDao bookDao = BookDaoFactory.getBookDaoImpl();
bookDao.save();
}
}运行结果:
book dao save …
这样只是通过静态工厂创建对象,但没有将对象交给Spring容器进行管理,如何交给Spring容器管理呢?
具体实现步骤如下:
- 在Spring配置文件中修改BookDao的Bean
1
<bean id="bookDao" class="com.blog.factory.BookDaoFactory" factory-method="getBookDaoImpl"/>
将class改为工厂类的全类名
添加factory-method属性,指定工厂创建类的方法名 - 在App运行类,像之前一样从IOC容器中获得对象
1
2
3
4
5
6
7public class App {
public static void main(String[] args) {
ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
BookDao bookDao = (BookDao) context.getBean("bookDao");
bookDao.save();
}
} - 这样就能正常运行了,运行结果和之前一样,反而更加麻烦了,哪这样做的意义是什么呢?
book dao save …
- 在工厂的静态方法中,除了创建对象我们还可以在这里进行其他的业务操作
1
2
3
4
5
6
7public class BookDaoFactory {
public static BookDao getBookDaoImpl() {
System.out.println("book dao factory setup ...");//模拟必要的业务操作
//这里可以加一大堆业务逻辑
return new BookDaoImpl();
}
}
实例工厂和FactoryBean
修改BookDaoFactory类,这里和静态工厂的工厂类不同的是此处不是静态方法,而是普通方法
1 | public class BookDaoFactory { |
修改App运行类,通过工厂来获得对象
1 | public class App { |
运行结果:
book dao factory setup … 这个是模拟业务逻辑的输出,无视掉就行
book dao save …
将实例工厂交给Spring管理:
- 在Spring配置文件中修改bookDao的bean
1
2<bean id="bookDaoFactory" class="com.blog.factory.BookDaoFactory"/>
<bean id="bookDao" factory-bean="bookDaoFactory" factory-method="getBookDaoImpl"/>
- 配置后便可以在App中从Ioc容器中获取Bean对象
1
2
3
4
5
6
7public class App {
public static void main(String[] args) {
ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
BookDao bookDao = (BookDao) context.getBean("bookDao");
bookDao.save();
}
}
FactoryBean具体使用步骤:
- 创建一个BookDaoFactoryBean类,实现FactoryBean接口,并重写其中的方法
该接口是一个泛型接口,泛型参数为该工厂创建的Bean对象的类型1
2
3
4
5
6
7
8
9
10public class BookDaoFactoryBean implements FactoryBean<BookDao> {
public BookDao getObject() throws Exception {
return new BookDaoImpl();
}
public Class<?> getObjectType() {
return BookDao.class;
}
} - 在Spring的配置文件修改bookDao的bean,修改class属性为工厂的全类名
1
<bean id="bookDao" class="com.blog.factory.BookDaoFactoryBean"></bean>
- App运行类不用做任何修改,直接运行,结果如下
book dao save …
- 查看源码会发现,FactoryBean接口提供了三个方法
1
2
3
4
5
6
7
8
9
10// 该方法用于创建Bean对象并返回
T getObject() throws Exception;
// 该方法用于返回Bean对象的类型
Class<?> getObjectType();
// 该方法用于设置Bean对象是否为单例,默认为单例
default boolean isSingleton() {
return true;
} - 其中方法一和方法二必须重写,方法三因为有默认值,所以可以不重写
Bean的实例化小结
- Bean如何被创建
- Spring通过反射机制调用构造方法,因此即使构造方法为私有的,Spring依旧能正常创建Bean
- Spring的Ioc实例化对象的三种方式:
- 无参构造方法(常用)
- 静态工厂(了解)
- 实例工厂(了解)
- FactoryBean(实用)
Bean的生命周期
对于Bean的生命周期,主要围绕Bean生命周期控制来学习
- 什么是Bean的生命周期?
- Bean对象从创建到销毁的整个过程
- Bean生命周期控制是什么?
- Bean创建后到销毁前做的事情
- 我们研究的是如何在Bean创建之后和销毁之前将我们需要执行的操作添加进去
生命周期设置
具体的两个控制阶段:
- Bean创建之后,如果想要添加内容,比如用来初始化需要用到的资源
- Bean销毁之前,如果想要添加内容,比如用来释放资源
创建初始化和销毁方法
1
2
3
4
5
6
7
8
9
10
11
12
13public class BookDaoImpl implements BookDao {
public void save() {
System.out.println("book dao save ...");
}
// 在类中添加两个方法,方法名不唯一
public void init() {
System.out.println("init ... ");
}
public void destroy() {
System.out.println("destroy ... ");
}
}在配置文件中添加初始化和销毁方法
1
<bean id="bookDao" class="com.blog.dao.impl.BookDaoImpl" init-method="init" destroy-method="destroy"></bean>
运行程序
init …
book dao save …
知道出现问题的原因,该如何解决这个问题呢?接着往下看
Close关闭容器
- ApplicationContext中没有close方法,他的子类提供了close方法,所以需要将ApplicationContext更换为ClassPathXmlApplicationContext,然后调用
close()方法关闭容器1
2
3
4
5
6
7
8
9public class App {
public static void main(String[] args) {
//ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
BookDao bookDao = (BookDao) context.getBean("bookDao");
bookDao.save();
context.close();
}
} - 此时运行程序就能看到destroy正常输出
init …
book dao save …
destroy …
注册钩子关闭容器
- 在容器未关闭之前,提前设置好回调函数,让JVM在退出之前回调此函数来关闭容器
- 调用context的
registerShutdownHook()方法1
2
3
4
5
6
7
8public class App {
public static void main(String[] args) {
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
BookDao bookDao = (BookDao) context.getBean("bookDao");
bookDao.save();
context.registerShutdownHook();
}
} 运行后,查询打印结果
init …
book dao save …
destroy …close和registerShutdownHook该选哪个?
- 相同点: 这两种都能用来关闭容器
- 不同点: close()是在调用的时候关闭容器,registerShutdownHook()是在JVM退出前调用关闭容器。
- 那么registerShutdownHook()方法可以在任意位置调用,下面的代码中将其放在了第二行,仍能正常输出,但要是将其换成close()方法,则会报错BeanFactory not initialized or already closedclosed
1
2
3
4
5
6
7
8public class App {
public static void main(String[] args) {
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
context.registerShutdownHook();
BookDao bookDao = (BookDao) context.getBean("bookDao");
bookDao.save();
}
}
- 开发中用哪个?
- 答案是两个都不用
- 该方式添加初始化和销毁方法即需要更改配置文件还需要编码,实现步骤较复杂,不建议使用
- Spring提供了两个接口来实现生命周期的控制,好处是不需要再配置
init-method和destroy-method
Spring提供的接口
修改BookServiceImpl类,添加两个接口InitializingBean,DisposableBean并实现接口中的两个方法afterPropertiesSet和destroy
1 | public class BookServiceImpl implements BookService, InitializingBean, DisposableBean { |
1 | <bean id="bookService" class="com.blog.service.impl.BookServiceImpl"> |
- init 是BookDao的初始化方法输出的
- service init 是BookServiceImpl的初始化方法输出的
- book dao save …是BookDao的保存方法输出的
- service destroy 是BookServiceImpl的销毁方法输出的
- destroy 是BookDao的销毁方法输出的
init …
service init …
book dao save …
service destroy …
destroy …
- 小细节
- 对于InitializingBean接口中的afterPropertiesSet方法,翻译过来为属性设置之后。
- 对于BookServiceImpl来说,bookDao是它的一个属性,所以会先注入bookDao属性再初始化BookServiceImpl
IOC相关内容小结
- Bean的基础配置
<id>标签和<class>标签必须添加且不能为空,<id>标签的值不能重复,<class>标签的值可以重复<name>标签可以用来取别名,多个别名时使用逗号,分号或空格进行分隔<scope>标签用来设置Bean的作用范围,默认值为单例,即获取同一个<id>的Bean对象,获取的对象都是同一个(即使类路径相同只有id不同,这两个也是独立的Bean对象)
- Bean的实例化
- Bean对象是Spring通过暴力反射为我们创建的,我们如果重写了构造方法,一定要把无参构造方法也写出来。
- Spring的Ioc实例化对象的三种方式:
- 无参构造方法(常用)
- 静态工厂(了解)
- 实例工厂(了解)
- FactoryBean(实用)
- Bean的生命周期
- Spring为Bean的生命周期控制提供了两种方式:
- 配置文件中添加
init-method和destroy-method属性 - 类实现
InitializingBean和DisposableBean接口
- 配置文件中添加
- Bean的生命周期过程:
- 初始化容器
- 创建对象
- 执行构造方法
- 执行属性注入(set …)
- 执行Bean的初始化方法(service init …)
- 使用Bean
- 执行Bean的业务方法(book dao save …)
- 销毁容器
- 执行Bean的销毁方法(service destroy …)
- 初始化容器
- 关闭容器的两种方法:
- 通过配置文件添加初始化和销毁方法需要设置关闭容器,否则
destroy()方法不会正常执行 - ConfigurableApplicationContext是ApplicationContext的子类,子类才有下面两种方法:
close()registerShutdownHook()
- 通过配置文件添加初始化和销毁方法需要设置关闭容器,否则
- Spring为Bean的生命周期控制提供了两种方式:
DI相关内容
DI(依赖注入)通常用来为对象添加属性
- 向类中传递数据的方式有哪些?
- 构造方法
- Set方法
- Spring为我们提供了两种属性注入方式:
构造器注入- 简单类型
- 引用类型
Setter方法注入- 简单类型
- 引用类型
Set注入
- 前面就用到了set注入,快速回顾一下
- 在Bean中定义引用类型的属性,并提供set方法
1
2
3
4
5
6public class BookServiceImpl implements BookService {
private BookDao bookDao;
public void setBookDao(BookDao bookDao) {
this.bookDao = bookDao;
}
} - 在配置文件中使用property标签为属性赋值
1
2
3<bean id="bookService" class="com.blog.service.impl.BookServiceImpl">
<property name="bookDao" ref="bookDao"></property>
</bean>
注入引用数据类型
1 | public interface BookDao { |
1 | public class BookDaoImpl implements BookDao { |
1 | public interface BookService { |
1 | public class BookServiceImpl implements BookService { |
- 配置文件如下:
1
2
3
4
5
6
7
8
9
10
11
<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="bookDao" class="com.blog.dao.impl.BookDaoImpl"></bean>
<bean id="bookService" class="com.blog.service.impl.BookServiceImpl">
<property name="bookDao" ref="bookDao"></property>
</bean>
</beans> - 运行App类,加载Ioc容器并获取service对象
1
2
3
4
5
6
7public class App {
public static void main(String[] args) {
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
BookService bookService = (BookService) context.getBean("bookService");
bookService.save();
}
}注入简单数据类型
思考: - 引用类型使用的是
<property name="" ref=""/>,简单数据类型还是使用ref吗? ref是指向Spring的IOC容器中的另一个bean对象的,对于简单数据类型,没有对应的bean对象,该如何配置呢?
- 使用value来配置
<property name="" value=""/>
- 使用value来配置
步骤一:定义属性并提供set方法1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16public class BookDaoImpl implements BookDao {
private String dataBaseName;
private int connectionCount;
public void setDataBaseName(String dataBaseName) {
this.dataBaseName = dataBaseName;
}
public void setConnectionCount(int connectionCount) {
this.connectionCount = connectionCount;
}
public void save() {
System.out.println("book dao save ..." + dataBaseName + "," + connectionCount);
}
}步骤二:在配置文件中注入配置1
2
3
4
5
6
7
8
9
10
<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="bookDao" class="com.blog.dao.impl.BookDaoImpl">
<property name="dataBaseName" value="mysql"></property>
<property name="connectionCount" value="100"></property>
</bean>
</beans>- 步骤三:运行程序,查看结果
book dao save …mysql,100
构造器注入
环境准备
修改BookDao、BookDaoImpl、UserDao、UserDaoImpl、BookService和BookServiceImpl类
1
2
3public interface BookDao {
public void save();
}1
2
3
4
5
6
7
8
9public class BookDaoImpl implements BookDao {
private String databaseName;
private int connectionNum;
public void save() {
System.out.println("book dao save ...");
}
}1
2
3public interface UserDao {
public void save();
}1
2
3
4
5public class UserDaoImpl implements UserDao {
public void save() {
System.out.println("user dao save ...");
}
}1
2
3public interface BookService {
public void save();
}1
2
3
4
5
6
7
8
9
10
11
12public class BookServiceImpl implements BookService{
private BookDao bookDao;
public void setBookDao(BookDao bookDao) {
this.bookDao = bookDao;
}
public void save() {
System.out.println("book service save ...");
bookDao.save();
}
}配置文件:
1
2
3
4
5
6
7
8
9
10
<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="bookDao" class="com.blog.dao.impl.BookDaoImpl"/>
<bean id="bookService" class="com.blog.service.impl.BookServiceImpl">
<property name="bookDao" ref="bookDao"/>
</bean>
</beans>- 运行类
1
2
3
4
5
6
7public class App {
public static void main( String[] args ) {
ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
BookService bookService = (BookService) ctx.getBean("bookService");
bookService.save();
}
}
构造器注入引用类型
- 步骤一:删除setter方法并提供构造方法
1
2
3
4
5
6
7
8
9
10
11
12public class BookServiceImpl implements BookService{
private BookDao bookDao;
public BookServiceImpl(BookDao bookDao) {
this.bookDao = bookDao;
}
public void save() {
System.out.println("book service save ...");
bookDao.save();
}
} - 步骤二:配置文件中进行配置构造方式注入
1
2
3
4
5
6
7
8
9
10
<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="bookDao" class="com.blog.dao.impl.BookDaoImpl"/>
<bean id="bookService" class="com.blog.service.impl.BookServiceImpl">
<constructor-arg name="bookDao" ref="bookDao"/>
</bean>
</beans> - 步骤三:运行程序,查看结果
运行结果:book service save …
book dao save …
构造器注入多个参数
- 步骤一:添加多个简单属性并提供构造方法
1
2
3
4
5
6
7
8
9
10
11
12
13
14public class BookDaoImpl implements BookDao {
private String databaseName;
private int connectionNum;
public BookDaoImpl(String databaseName, int connectionNum) {
this.databaseName = databaseName;
this.connectionNum = connectionNum;
}
public void save() {
System.out.println("book dao save ..." + databaseName + "," + connectionNum);
}
} - 步骤二:修改该配置文件
1
2
3
4
5
6
7
8
9
10
<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="bookDao" class="com.blog.dao.impl.BookDaoImpl">
<constructor-arg name="databaseName" value="mysql"></constructor-arg>
<constructor-arg name="connectionNum" value="100"></constructor-arg>
</bean>
</beans> - 步骤三:运行程序:
book service save …
book dao save …mysql,100
存在的问题
<constructor-arg>标签内的name,必须与构造函数中的参数名一致,这两块存在紧耦合。- 解决该问题,需要提前说明一点,即这个参数名发生变化的情况并不多,所以上面的还是比较主流的配置方式,下面介绍的配置方式了解为主。
- 方式一:删除name属性,添加type属性,根据类型注入
- 这种方式可以解决构造函数形参名发生变化带来的耦合问题
- 但是如果构造方法参数中有类型相同的参数,这种方式就不太好实现了
1
2
3
4<bean id="bookDao" class="com.blog.dao.impl.BookDaoImpl">
<constructor-arg type="java.lang.String" value="mysql"></constructor-arg>
<constructor-arg type="int" value="9421"></constructor-arg>
</bean>
- 方式二:删除type属性,添加index属性,根据索引下标注入,下标从0开始
- 该方式能解决参数类型重复和参数名称问题
- 但这又要求参数的顺序不能变,带来了另外的耦合问题
1
2
3
4<bean id="bookDao" class="com.blog.dao.impl.BookDaoImpl">
<constructor-arg index="0" value="mysql"></constructor-arg>
<constructor-arg index="1" value="9421"></constructor-arg>
</bean>
如何选择使用哪种注入方式?
- 强制依赖时,使用构造器注入,使用set注入有概率不进行注入导致null对象出现
- 强制依赖指对象创建时必须要注入参数
- 可选依赖使用set注入,灵活性强
- 可选依赖指对象创建时,不一定需要注入参数
- Spring倡导使用构造器注入,第三方框架也大多采用构造器注入,因为这种方法较严谨
- 如果有必要,可以同时使用两种注入方法,强制依赖时使用构造器注入,可选依赖时使用set注入
- 实际开发需要根据实际情况来,如果是第三方程序没有提供set方法则只能使用构造器注入
- 自己开发模块时推荐使用set注入
构造器注入小结
- Set注入
- 简单类型
1
2
3<bean ...>
<property name="" value=""/>
</bean> - 引用类型
1
2
3<bean ...>
<property name="" ref=""/>
</bean>
- 简单类型
- 构造器注入
- 简单类型
1
2
3<bean ...>
<constructor-arg name="" index="" type="" value=""/>
</bean> - 引用类型
1
2
3<bean ...>
<constructor-arg name="" index="" type="" ref=""/>
</bean> - 根据类型注入
1
2
3
4<bean id="bookDao" class="com.blog.dao.impl.BookDaoImpl">
<constructor-arg type="java.lang.String" value="mysql"></constructor-arg>
<constructor-arg type="int" value="9421"></constructor-arg>
</bean> - 根据索引下标注入
1
2
3
4<bean id="bookDao" class="com.blog.dao.impl.BookDaoImpl">
<constructor-arg index="0" value="mysql"></constructor-arg>
<constructor-arg index="1" value="9421"></constructor-arg>
</bean>
- 简单类型
- 依赖注入的方式选择
- 自己开发模块时推荐使用set注入
- 第三方技术根据实际情况选择
自动配置
- 无论构造器注入还是set注入,都需要修改配置文件,相对还是很麻烦,对此Spring为我们提供了
自动配置功能 - Ioc容器根据Bean所依赖的资源在容器中自动查找并注入到Bean的过程称为
自动装配 - 自动装配的方式:
- 按类型
- 按名称
- 按构造方法
- 不启用自动装配(忽略)
环境准备
修改BookDao、BookDaoImpl、BookService和BookServiceImpl类
1 | public interface BookDao { |
1 | public class BookDaoImpl implements BookDao { |
1 | public interface BookService { |
1 | public class BookServiceImpl implements BookService{ |
配置文件:
1 |
|
运行类:
1 | public class App { |
启用自动装配
- 启用自动装配仅仅需要修改配置文件即可
- 将手动装配的
<property>标签删除 - 在
<bean>标签中添加autowire属性 - 通过更改autowire属性为byName或者byType来控制根据名称装配还是根据类型装配
1
2
3
4
5
6
7
8
9
10
<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="bookDao" class="com.blog.dao.impl.BookDaoImpl"/>-->
<!-- 既然是按类型注入了,那么id写不写都无所谓了-->
<bean class="com.blog.dao.impl.BookDaoImpl"/>
<bean id="bookService" class="com.blog.service.impl.BookServiceImpl" autowire="byType"/>
</beans>
- 将手动装配的
- 再次运行程序,结果如下,说明自动装配成功
book service save …
book dao save …
自动装配小结
- 如果按照名称去找对应的bean对象,找不到则注入Null
- 当某一个类型在IOC容器中有多个对象,按照名称注入只找其指定名称对应的bean对象,不会报错
- 两种方式介绍完后,以后用的更多的是
根据类型注入。 - 对于依赖注入,需要注意一些其他的配置特征:
- 常见的集合类型
- 数组
- List
- Set
- Map
- Properties
环境准备
- 修改BookDaoImpl类
1
2
3public interface BookDao {
public void save();
}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
46public class BookDaoImpl implements BookDao {
private int[] array;
private List<String> list;
private Set<String> set;
private Map<String,String> map;
private Properties properties;
public void save() {
System.out.println("book dao save ...");
System.out.println("遍历数组:" + Arrays.toString(array));
System.out.println("遍历List" + list);
System.out.println("遍历Set" + set);
System.out.println("遍历Map" + map);
System.out.println("遍历Properties" + properties);
}
public void setArray(int[] array) {
this.array = array;
}
public void setList(List<String> list) {
this.list = list;
}
public void setSet(Set<String> set) {
this.set = set;
}
public void setMap(Map<String, String> map) {
this.map = map;
}
public void setProperties(Properties properties) {
this.properties = properties;
}
} - 修改配置文件
1
2
3
4
5
6
7
<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="bookDao" class="com.blog.dao.impl.BookDaoImpl"/>
</beans> - 修改App运行类
1
2
3
4
5
6
7public class App {
public static void main( String[] args ) {
ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
BookDao bookDao = (BookDao) ctx.getBean("bookDao");
bookDao.save();
}
} - 接下来学习集合注入均在上面环境基础上完成,都是在bookDao的
<bean>标签上使用<property进行注入
注入数组类型
1 | <property name="array"> |
注入List类型
1 | <property name="list"> |
注入Set类型
1 | <property name="set"> |
注入Map类型
1 | <property name="map"> |
注入Properties类型
1 | <property name="properties"> |
DI小结
- 配置文件注入
- 构造器注入(
<constructor-arg>)- 根据类型注入
- 根据名称注入
- 根据下标注入
- setter注入(
property)- 基础数据类型:
<value> - 引用数据类型:
<ref>
- 基础数据类型:
- 构造器注入(
- 注入方式选择
- 自己开发模块时推荐使用set注入
- 第三方技术根据实际情况选择
- 自动装配
- 在配置文件中添加autowire属性,值为byType或byName
- byType:根据类型自动装配
- byName:根据名称自动装配
- 在配置文件中添加autowire属性,值为byType或byName
- 集合注入
- 数组:
<array> - List:
<list> - Set:
<set> - Map:
<map> - Properties:
<props>
- 数组:
Ioc/DI管理第三方Bean
- 前面的内容都是管理自己写的类,现在学习如何管理第三方的类
案例:数据源对象管理
环境准备
- 创建Maven项目
- 添加需要的依赖
1
2
3
4
5
6
7<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.2.10.RELEASE</version>
</dependency>
</dependencies> - resources目录下新建spring的配置文件
1
2
3
4
5
6
<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> 编写运行类
1
2
3
4
5public class App {
public static void main(String[] args) {
ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
}
}实现Druid管理
导入
Druid依赖1
2
3
4
5<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.1.16</version>
</dependency>配置第三方bean
此时第三方bean使用set注入还是构造器注入便需要根据第三方类为我们提供的内容来判断
通过查看源码发现DruidDataSource只提供了两个构造器,显然不能使用构造方法注入
1 | public DruidDataSource() |
确定使用set注入后,在配置文件中添加DruidDataSource的配置
1 |
|
- 从Ioc容器中获取bean对象
1
2
3
4
5
6
7public class App {
public static void main(String[] args) {
ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
DruidDataSource dataSource = context.getBean(DruidDataSource.class);
System.out.println(dataSource);
}
} - 运行程序
打印以下结果说明第三方Bean成功交给Ioc容器管理{
CreateTime:”2022-09-01 10:15:19”,
ActiveCount:0,
PoolingCount:0,
CreateCount:0,
DestroyCount:0,
CloseCount:0,
ConnectCount:0,
Connections:[
]
}
加载properties文件
- 我们完成Druid数据源配置后又发现一些问题:
- 数据源中用到一些固定的常量(如数据库连接四要素),而将这些值直接放到Spring的配置文件中不利于后期维护,因此选择将这些固定的常量提取到外部的properties配置文件中
- 提取到properties文件的数据如何在Spring框架中读取这是接下来要解决的问题
Druid属性优化
- 准备properties配置文件
新建jdbc.properties文件并添加对应的属性键值对1
2
3
4jdbc.driverClass=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:13306/spring_db
jdbc.username=root
jdbc.password=password - 开启
context命名空间
修改Spring配置文件,开启context命名空间1
2
3
4
5
6
7
8
9
10
11
<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
">
</beans> - 加载properties文件
在配置文件中使用以下标签来加载文件1
<context:property-placeholder location="jdbc.properties"/>
- 完成属性注入
使用${}占位符来引用properties文件中的值1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<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
">
<context:property-placeholder location="jdbc.properties"/>
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
<property name="driverClassName" value="${jdbc.driverClass}"/>
<property name="url" value="${jdbc.url}"/>
<property name="username" value="${jdbc.username}"/>
<property name="password" value="${jdbc.password}"/>
</bean>
</beans>
- 此时就已经将properties文件中的值成功注入到了DruidDataSource对象中
注意事项:
- 问题一:键值对的key为
username等系统内置属性引发问题- 在properties中配置键值对的时候,如果key设置为
username时,在运行后控制台打印该值发现打印的不是root,而是自己电脑的用户名 - 原因:
<context:property-placeholder/>标签会加载系统的环境变量,且环境变量会优先加载,下面的代码可以输出系统环境变量1
2
3
4public static void main(String[] args) throws Exception{
Map<String, String> env = System.getenv();
System.out.println(env);
} - 打印的结果是USERNAME=XXX[自己电脑用户名]
- 解决方案:将ystem-properties-mode设置为NEVER,表示不加载系统环境变量,这样就可以解决上面的问题了,当然还有一个解决方案就是给属性加上前缀,避免使用username作为属性的key。
1
<context:property-placeholder location="jdbc.properties" system-properties-mode="NEVER"/>
- 在properties中配置键值对的时候,如果key设置为
- 问题二:多个properties文件加载,该如何配置?
- 修改applicationContext.xml
1
2
3
4
5
6
7
8<!--方式一 -->
<context:property-placeholder location="jdbc.properties,jdbc2.properties" system-properties-mode="NEVER"/>
<!--方式二-->
<context:property-placeholder location="*.properties" system-properties-mode="NEVER"/>
<!--方式三 -->
<context:property-placeholder location="classpath:*.properties" system-properties-mode="NEVER"/>
<!--方式四-->
<context:property-placeholder location="classpath*:*.properties" system-properties-mode="NEVER"/>小结
- 修改applicationContext.xml
- 如何开启
context命名空间1
2
3
4
5
6
7
8
9
10
<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
">
如何加载properties配置文件
1
<context:property-placeholder location="" system-properties-mode="NEVER"/>
如何在applicationContext.xml引入properties配置文件中的值
- 使用占位符:
${key}
- 使用占位符:
核心容器
此处学习的核心容器,可以简单的理解为ApplicationContext,即前面App应用类创建的对象,接下来从结果问题来入手学习该容器的内容:
- 如何创建容器
- 创建好容器如何获得Bean对象
- 容器类的层次结构是什么
- BeanFactory是什么
环境准备
- 创建Maven项目
- 添加Spring的依赖
1
2
3
4
5
6
7<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.2.10.RELEASE</version>
</dependency>
</dependencies> - 新建Spring配置文件
1
2
3
4
5
6
7
8
9
<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="bookDao" class="com.blog.dao.impl.BookDaoImpl"/>
</beans> - 添加BookDao和BookDaoImpl类
1
2
3public interface BookDao {
public void save();
}1
2
3
4
5public class BookDaoImpl implements BookDao {
public void save() {
System.out.println("book dao save ..." );
}
} - 创建运行类
1
2
3
4
5
6
7public class App {
public static void main(String[] args) {
ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
BookDao bookDao = (BookDao) ctx.getBean("bookDao");
bookDao.save();
}
}容器
容器的创建方式
- 案例中创建
ApplicationContext方式如下 - 这种方式翻译为:类路径下的XML配置文件
1
ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
- Spring还提供了一种绝对路径的创建方式
1
ApplicationContext ctx = new FileSystemXmlApplicationContext("D:\xxx/xxx\applicationContext.xml");
- 这种方式能实现,但项目位置一旦发生变化代码就要跟着改,增加耦合度,所以不推荐使用
获取Bean的三种方式
- 方式一:获得bean后强转
- 该方式存在的问题在于每次获取Bean后需要进行强转操作
1
BookDao bookDao = (BookDao) ctx.getBean("bookDao");
- 方式二:指定获取Bean的类型
- 该方式在调用方法时需要指定Bean的类型,避免了强转操作
1
BookDao bookDao = ctx.getBean("bookDao", BookDao.class);
- 方式三:直接传Bean的类型
1
BookDao bookDao = ctx.getBean(BookDao.class);
- 该方式类似按类型注入。必须确保Ioc容器中该类型对应的Bean对象只能有一个
BeanFactory
容器的最上级父接口就是BeanFactory,使用BeanFactory也可以创建Ioc容器
1 | public class AppForBeanFactory { |
为了更好的看出BeanFactory和ApplicationContext之间的区别,在BookDaoImpl添加如下构造函数
1 | public class BookDaoImpl implements BookDao { |
如果不去获取bean对象,打印会发现:
- BeanFactory是延迟加载,只有在获取bean对象的时候才会去创建
- ApplicationContext是立即加载,容器加载的时候就会创建bean对象
- ApplicationContext要想成为延迟加载,只需要将lazy-init设为true
1
2
3
4
5
6
7
<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="bookDao" class="com.blog.dao.impl.BookDaoImpl" lazy-init="true"/>
</beans>
核心容器总结
容器相关
BeanFactory是IoC容器的顶层接口,初始化BeanFactory对象时,Bean对象不会被加载,只有获取Bean对象时才会创建ApplicationContext接口是Spring容器的核心接口,初始化时bean立即加载- ApplicationContext接口提供基础的bean操作相关方法,通过其他接口扩展其功能
- ApplicationContext接口常用初始化类
Ioc/DI注解开发
Spring的Ioc/DI对应的使用配置文件开发使用起来还是比较复杂的,所以Spring为我们提供了注解开发,Spring对注解支持的版本历程:
- 2.0版开始支持注解
- 2.5版注解功能趋于完善
- 3.0版支持纯注解开发
关于注解开发,主要学习两部分内容注解开发定义Bean和纯注解开发。
注解开发定义bean用的是2.5版提供的注解,纯注解开发用的是3.0版提供的注解。
环境准备
- 创建一个Maven项目
- 添加Spring的依赖
1
2
3
4
5
6
7<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.2.10.RELEASE</version>
</dependency>
</dependencies> - 新建Spring配置文件
1
2
3
4
5
6
7
<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="bookDao" class="com.blog.dao.impl.BookDaoImpl"/>
</beans> - 添加BookDao和BookDaoImpl类
1
2
3public interface BookDao {
public void save();
}1
2
3
4
5public class BookDaoImpl implements BookDao {
public void save() {
System.out.println("book dao save ..." );
}
} - 创建运行类
1
2
3
4
5
6
7public class App {
public static void main(String[] args) {
ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
BookDao bookDao = (BookDao) ctx.getBean("bookDao");
bookDao.save();
}
}注解开发定义Bean
- 删除原有的XML配置
将配置文件中的<bean>标签删除掉1
<bean id="bookDao" class="com.blog.dao.impl.BookDaoImpl"/>
- 在Dao上添加注解
在BookDaoImpl类上添加@Component注解1
2
3
4
5
6
public class BookDaoImpl implements BookDao {
public void save() {
System.out.println("book dao save ...");
}
} - 配置Spring的注解包扫描
仅仅在类上添加@Component注解是不够的,还需要配置Spring的注解包扫描,否则Spring容器不会扫描到该类,Spring没有扫描到自然不会添加到Ioc容器中管理。1
2
3
4
5
6
7
8
9
10
11
12
<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
">
<context:component-scan base-package="com.blog"/>
</beans> - 运行程序
book dao save …
- service上添加注解
在BookServiceImpl类上也添加@Component交给Spring框架管理1
2
3
4
5
6
public class BookServiceImpl implements BookService {
public void save() {
System.out.println("book service save ...");
}
} - 运行程序
在App类中从IOC容器中获取BookServiceImpl对应的bean对象执行结果:1
2
3
4
5
6
7
8
9
10
11public class App {
public static void main(String[] args) {
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
//按照名称获取bean
BookDao bookDao = (BookDao) context.getBean("bookDao");
//按照类型获取bean
BookService bookService = context.getBean(BookService.class);
bookDao.save();
bookService.save();
}
}book dao save …
book service save …纯注解开发
上面已经可以使用注解来配置Bean,但仍然需要使用配置文件并添加包扫描,Spring在3.0版本推出了纯注解开发,使用Java类来代替配置文件。
实现步骤
- 创建配置类
创建SpringConfig专门用作配置类1
2public class SpringConfig {
} - 标识该类为配置类
在配置类上添加@Configuration注解,标识为配置类,用来替代applicationContext.xml1
2
3
public class SpringConfig {
} - 使用包扫描注解
原本配置文件中就需要添加包扫描,现在使用注解@ComponentScan替换<context:component-scan base-package=""/>1
2
3
4
public class SpringConfig {
} - 创建运行类并执行
1
2
3
4
5
6
7
8
9public class AppForAnnotation {
public static void main(String[] args) {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(SpringConfig.class);
BookDao bookDao = (BookDao) context.getBean("bookDao");
bookDao.save();
BookService bookService = context.getBean(BookService.class);
bookService.save();
}
} - 执行结果
可以看到两个对象可以被成功获取book dao save …
book service save …
小结
纯注解开发的主要内容包括:
@Component注解可以用来定义bean@Configuration注解用于设定当前类为配置类@ComponentScan注解用于设定扫描路径,该注解只能添加一次,多个数据需要使用数组格式1
- 读取Spring核心配置文件初始化容器对象切换为Java配置类初始化容器对象
1
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(SpringConfig.class);
| 名称 | @Configuration |
|---|---|
| 类型 | 类注解 |
| 位置 | 类定义上方 |
| 作用 | 设置该类为spring配置类 |
| 属性 | value(默认):定义bean的id |
| 名称 | @ComponentScan |
|---|---|
| 类型 | 类注解 |
| 位置 | 类定义上方 |
| 作用 | 设置spring配置类扫描路径,用于加载使用注解格式定义的bean |
| 属性 | value(默认):扫描路径,此路径可以逐层向下扫描 |
注解开发Bean的作用范围和生命周期
- 使用注解能够实现Bean的管理,根据前面学习的内容,将
Bean作用范围(Scope)和Bean生命周期(init和destroy)也替换为注解实现
Bean的作用范围
- 前面提到Spring为我们创建的Bean默认是单例,使用注解创建也不例外,如果想创建多例Bean,只需要在类上添加
@scope注解即可知识点:1
2
3
4
5
6
7
public class BookDaoImpl implements BookDao {
public void save() {
System.out.println("book dao save ...");
}
}@scope
| 名称 | @Scope |
|---|---|
| 类型 | 类注解 |
| 位置 | 类定义上方 |
| 作用 | 设置该类创建对象的作用范围,可用于设置创建出的bean是否为单例对象 |
| 属性 | value(默认):定义bean作用范围,默认值singleton(单例),可选值prototype(非单例) |
Bean的生命周期
- 在BookDaoImpl中添加两个方法,init和destroy(方法名任意)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public class BookDaoImpl implements BookDao {
public BookDaoImpl() {
System.out.println("construct ... ");
}
public void save() {
System.out.println("book dao save ...");
}
public void init() {
System.out.println("init ... ");
}
public void destroy() {
System.out.println("destroy ... ");
}
} - 使用注解来标注初始化方法和销毁方法只需要在对应的方法上添加
@PostConstruct和@PreDestroy注解1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public class BookDaoImpl implements BookDao {
public BookDaoImpl() {
System.out.println("construct ... ");
}
public void save() {
System.out.println("book dao save ...");
}
// 在构造方法之后执行,替换 init-method
public void init() {
System.out.println("init ... ");
}
// 在销毁方法之前执行,替换 destroy-method
public void destroy() {
System.out.println("destroy ... ");
}
}
| 名称 | @PostConstruct |
|---|---|
| 类型 | 方法注解 |
| 位置 | 方法上 |
| 作用 | 设置该方法为初始化方法 |
| 属性 | 无 |
| 名称 | @PreDestroy |
|---|---|
| 类型 | 方法注解 |
| 位置 | 方法上 |
| 作用 | 设置该方法为销毁方法 |
| 属性 | 无 |
小结
注解开发依赖注入
Spring为了使用注解简化开发,并没有提供构造函数注入、setter注入对应的注解,只提供了自动装配的注解实现。
环境准备
- 创建Maven项目
- 添加Spring依赖
- 添加配置类
SpringConfig1
2
3
4
public class SpringConfig {
} - 添加BookDao、BookDaoImpl、BookService、BookServiceImpl类
1
2
3public interface BookDao {
public void save();
}1
2
3
4
5
6
public class BookDaoImpl implements BookDao {
public void save() {
System.out.println("book dao save ..." );
}
}1
2
3public interface BookService {
public void save();
}1
2
3
4
5
6
7
8
9
10
11
public class BookServiceImpl implements BookService {
private BookDao bookDao;
public void setBookDao(BookDao bookDao) {
this.bookDao = bookDao;
}
public void save() {
System.out.println("book service save ...");
bookDao.save();
}
} - 创建运行类App
1
2
3
4
5
6
7public class App {
public static void main(String[] args) {
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(SpringConfig.class);
BookService bookService = ctx.getBean(BookService.class);
bookService.save();
}
}
按类型注入(注解)
- 在BookServiceImpl类的bookDao属性上添加@Autowired注解
1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class BookServiceImpl implements BookService {
private BookDao bookDao;
// public void setBookDao(BookDao bookDao) {
// this.bookDao = bookDao;
// }
public void save() {
System.out.println("book service save ...");
bookDao.save();
}
} Autowired按照类型注入,当对应BookDao接口有多个实现类时,比如添加BookDaoImpl21
2
3
4
5
6
public class BookDaoImpl2 implements BookDao {
public void save() {
System.out.println("book dao save ...2");
}
}- 此时再次运行App,就会报错
NoUniqueBeanDefinitionException。 - 根据之前的学习,此时应当按照名称注入
- 先给两个Dao类分别起个名称
1
2
3
4
5
6
7
8
9
10
11
12
public class BookDaoImpl implements BookDao {
public void save() {
System.out.println("book dao save ..." );
}
}
public class BookDaoImpl2 implements BookDao {
public void save() {
System.out.println("book dao save ...2" );
}
} - 突然发现此时就能注入成功,这是为什么?
按名称注入(注解)
当根据类型在容器中找到多个bean,注入参数的属性名又和容器中bean的名称不一致,这个时候该如何解决,就需要使用到@Qualifier来指定注入哪个名称的bean对象。@Qualifier注解后的值就是需要注入的bean的名称。
1 |
|
简单类型注入
- Spring提供的自动装配只能对引用类型进行装配,不过Spring也为我们提供了简单类型装配的注解——
@Value。 使用
@Value注解为name属性赋值1
2
3
4
5
6
7
8
public class BookDaoImpl implements BookDao {
private String name;
public void save() {
System.out.println("book dao save ..." + name);
}
}看到这个注解的使用方式,可以会有疑问,都直接在属性上标了那为什么不直接赋值呢?因为可以在注解中使用占位符读取外部的properties配置文件
读取properties配置文件
@Value一般会被用在从properties配置文件中读取内容进行使用,具体如何实现?
步骤一:准备一个properties文件1
name=Stephen
步骤二:使用注解加载properties配置文件
在配置类上添加@PropertySource注解1
2
3
4
5
public class SpringConfig {
}步骤三:使用@Value读取配置文件中的内容1
2
3
4
5
6
7
8
public class BookDaoImpl implements BookDao {
private String name;
public void save() {
System.out.println("book dao save ..." + name);
}
}步骤四:运行程序
运行App类,查看运行结果,说明配置文件中的内容已经被加载
book service save …
book dao save …Stephen
名称 @Autowired 类型 属性注解 或 方法注解(了解) 或 方法形参注解(了解) 位置 属性定义上方 或 标准set方法上方 或 类set方法上方 或 方法形参前面 作用 为引用类型属性设置值 属性 required:true/false,定义该属性是否允许为null
名称 @Qualifier 类型 属性注解 或 方法注解(了解) 位置 属性定义上方 或 标准set方法上方 或 类set方法上方 作用 为引用类型属性指定注入的beanId 属性 value(默认):设置注入的beanId
名称 @Value 类型 属性注解 或 方法注解(了解) 位置 属性定义上方 或 标准set方法上方 或 类set方法上方 作用 为 基本数据类型 或 字符串类型 属性设置值 属性 value(默认):要注入的属性值
名称 @PropertySource 类型 类注解 位置 类定义上方 作用 加载properties文件中的属性值 属性 value(默认):设置加载的properties文件对应的文件名或文件名组成的数组
IOC/DI使用注解管理第三方Bean
- 自己开发的类可以通过添加注解快速管理,但第三方类我们无法在类中添加注解,此时该如何解决?此时Spring为我们提供了一个全新的注解——
@Bean环境准备
- 创建Maven项目
- 添加Spring依赖
1
2
3
4
5
6
7<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.2.10.RELEASE</version>
</dependency>
</dependencies> - 添加配置类
SpringConfig1
2
3
public class SpringConfig {
} - 添加BookDao、BookDaoImpl类
1
2
3public interface BookDao {
public void save();
}1
2
3
4
5
6
public class BookDaoImpl implements BookDao {
public void save() {
System.out.println("book dao save ..." );
}
} 创建运行类App
1
2
3
4
5public class App {
public static void main(String[] args) {
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(SpringConfig.class);
}
}管理第三方Bean
步骤一:导入对应的jar包1
2
3
4
5<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.1.16</version>
</dependency>
步骤二:在配置类中添加一个方法
注意该方法的返回值就是要创建的Bean对象类型1
2
3
4
5
6
7
8
9
10
11
public class SpringConfig {
public DataSource dataSource() {
DruidDataSource dataSource = new DruidDataSource();
dataSource.setDriverClassName("com.mysql.jdbc.Driver");
dataSource.setUrl("jdbc:mysql://localhost:13306/spring_db");
dataSource.setUsername("root");
dataSource.setPassword("PASSWORD");
return dataSource;
}
}步骤三:在方法上添加@Bean注解@Bean注解的作用是将方法的返回值作为一个Spring管理的bean对象1
2
3
4
5
6
7
8
9
10
11
12
public class SpringConfig {
public DataSource dataSource() {
DruidDataSource dataSource = new DruidDataSource();
dataSource.setDriverClassName("com.mysql.jdbc.Driver");
dataSource.setUrl("jdbc:mysql://localhost:13306/spring_db");
dataSource.setUsername("root");
dataSource.setPassword("PASSWORD");
return dataSource;
}
}步骤四:从IOC容器中获取对象并打印1
2
3
4
5
6
7public class App {
public static void main(String[] args) {
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(SpringConfig.class);
DataSource dataSource = ctx.getBean(DataSource.class);
System.out.println(dataSource);
}
}
输出如下
{
CreateTime:”2022-09-02 10:36:33”,
ActiveCount:0,
PoolingCount:0,
CreateCount:0,
DestroyCount:0,
CloseCount:0,
ConnectCount:0,
Connections:[
]
}
- 至此使用
@Bean来管理第三方bean的案例就已经完成。 - 如果有多个bean要被Spring管理,直接在配置类中多写几个方法,方法上添加@Bean注解即可。
引入外部配置类
如果把所有的第三方bean都配置到Spring的配置类SpringConfig中,虽然可以,但是不利于代码阅读和分类管理,所有我们就想能不能按照类别将这些bean配置到不同的配置类中?
那么对于数据源的bean,我们可以把它的配置单独放倒一个JdbcConfig类中
1 | public class JdbcConfig { |
那现在又有了一个新问题,这些配置类如何能被Spring配置类加载到,并创建DataSource对象在IOC容器中?
针对这个问题,有两个解决方案:
使用包扫描引入
步骤一:在Spring的配置类上添加包扫描
注意要将JdbcConfig类放在包扫描的地址下1
2
3
4
public class SpringConfig {
}步骤二:在JdbcConfig上添加@Configuration注解
JdbcConfig类要放入到com.blog.config包下,这样才能被Spring的配置类扫描到1
2
3
4
5
6
7
8
9
10
11
12
public class JdbcConfig {
public DataSource dataSource(){
DruidDataSource ds = new DruidDataSource();
ds.setDriverClassName("com.mysql.jdbc.Driver");
ds.setUrl("jdbc:mysql://localhost:3306/spring_db");
ds.setUsername("root");
ds.setPassword("root");
return ds;
}
}步骤三:运行程序
仍然可以获取到bean对象并输出到控制台{
CreateTime:”2022-09-02 10:52:50”,
ActiveCount:0,
PoolingCount:0,
CreateCount:0,
DestroyCount:0,
CloseCount:0,
ConnectCount:0,
Connections:[
]
}
这种方式虽然能够扫描到,但是不能很快的知晓都引入了哪些配置类(因为把包下的所有配置类都扫描了),所以这种方式不推荐使用。
使用@Import引入
方案一实现起来有点小复杂,Spring早就想到了这一点,于是又给我们提供了第二种方案。
这种方案可以不用加@Configuration注解,但是必须在Spring配置类上使用@Import注解手动引入需要加载的配置类
步骤一:去除JdbcConfig类上的注解1
2
3
4
5
6
7
8
9
10
11public class JdbcConfig {
public DataSource dataSource() {
DruidDataSource dataSource = new DruidDataSource();
dataSource.setDriverClassName("com.mysql.jdbc.Driver");
dataSource.setUrl("jdbc:mysql://localhost:13306/spring_db");
dataSource.setUsername("root");
dataSource.setPassword("PASSWORD");
return dataSource;
}
}步骤二:在Spring配置类中引入1
2
3
4
public class SpringConfig {
}
步骤三:运行程序
依然能获取到bean对象并打印控制台{
CreateTime:”2022-09-02 11:02:12”,
ActiveCount:0,
PoolingCount:0,
CreateCount:0,
DestroyCount:0,
CloseCount:0,
ConnectCount:0,
Connections:[
]
}
| 名称 | @Bean |
|---|---|
| 类型 | 方法注解 |
| 位置 | 方法定义上方 |
| 作用 | 设置该方法的返回值作为spring管理的bean |
| 属性 | value(默认):定义bean的id |
| 名称 | @Import |
|---|---|
| 类型 | 类注解 |
| 位置 | 类定义上方 |
| 作用 | 导入配置类 |
| 属性 | value(默认):定义导入的配置类类名, 当配置类有多个时使用数组格式一次性导入多个配置类 |
为第三方Bean注入资源
前面学习到使用@Value搭配${}来注入简单数据,以及使用@Autowired来注入引用数据,对于第三方bean,如何为它注入属性呢?
简单数据类型
- 注入简单数据类型的方法跟自己定义的bean一样,使用
@Value搭配${}来注入即可1
2
3
4jdbc.driver=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:13306/spring_db
jdbc.username=root
jdbc.password=PASSWORD.1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public class JdbcConfig {
private String driver;
private String url;
private String username;
private String password;
public DataSource dataSource() {
DruidDataSource dataSource = new DruidDataSource();
dataSource.setDriverClassName(driver);
dataSource.setUrl(url);
dataSource.setUsername(username);
dataSource.setPassword(password);
return dataSource;
}
}引用数据类型
- 对第三方注入引用数据类型的对象则更加方便,只需要为方法添加需要注入的Bean作为参数即可,容器会
根据类型自动装配1
2
3
4
5
6
7
8
9
10
public DataSource dataSource(BookDao bookDao) {
bookDao.save();
DruidDataSource dataSource = new DruidDataSource();
dataSource.setDriverClassName(driver);
dataSource.setUrl(url);
dataSource.setUsername(username);
dataSource.setPassword(password);
return dataSource;
}注解开发总结

Spring整合
Spring整合MyBatis
准备环境
步骤一:准备数据库表
MyBatis是用来操作数据库表的,所以先来创建库和表
1 | create database spring_db character set utf8; |
步骤二:创建项目导入依赖1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22<dependencies>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.46</version>
</dependency>
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.5.6</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.1.16</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.2.10.RELEASE</version>
</dependency>
</dependencies>步骤三:
根据表创建模型类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
47public class Account {
private Integer id;
private String name;
private Double money;
public Account() {
}
public Account(Integer id, String name, double money) {
this.id = id;
this.name = name;
this.money = 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 double getMoney() {
return money;
}
public void setMoney(double money) {
this.money = money;
}
public String toString() {
return "Account{" +
"id=" + id +
", name='" + name + '\'' +
", money=" + money +
'}';
}
}步骤四:创建Dao接口(在之前是Mapper接口,且要配置一个对应的xml文件,不过这里没涉及到复杂的sql语句,所以没配置xml文件,采用注解开发)
1 | public interface AccountDao { |
步骤五:创建Service接口和实现类AccountService
1 | public interface AccountService { |
1 |
|
步骤六:添加jdbc.properties文件1
2
3
4jdbc.driver=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:13306/spring_db
jdbc.username=root
jdbc.password=PASSWORD.步骤七:添加Mybatis核心配置文件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
<configuration>
<!--读取外部properties配置文件-->
<properties resource="jdbc.properties"></properties>
<!--别名扫描的包路径-->
<typeAliases>
<package name="com.blog.domain"/>
</typeAliases>
<!--数据源-->
<environments default="mysql">
<environment id="mysql">
<transactionManager type="JDBC"></transactionManager>
<dataSource type="POOLED">
<property name="driver" value="${jdbc.driver}"></property>
<property name="url" value="${jdbc.url}"></property>
<property name="username" value="${jdbc.username}"></property>
<property name="password" value="${jdbc.password}"></property>
</dataSource>
</environment>
</environments>
<!--映射文件扫描包路径-->
<mappers>
<package name="com.blog.dao"></package>
</mappers>
</configuration>步骤八:编写应用程序1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19public class App {
public static void main(String[] args) throws IOException {
// 1. 创建SqlSessionFactoryBuilder对象
SqlSessionFactoryBuilder sqlSessionFactoryBuilder = new SqlSessionFactoryBuilder();
// 2. 加载mybatis-config.xml配置文件
InputStream inputStream = Resources.getResourceAsStream("mybatis-config.xml");
// 3. 创建SqlSessionFactory对象
SqlSessionFactory factory = sqlSessionFactoryBuilder.build(inputStream);
// 4. 获取SqlSession
SqlSession sqlSession = factory.openSession();
// 5. 获取mapper
AccountDao mapper = sqlSession.getMapper(AccountDao.class);
//6. 执行方法进行查询
Account account = mapper.findById(2);
System.out.println(account);
//7. 释放资源
sqlSession.close();
}
}步骤九:运行程序,结果如下Account{id=2, name=’Jerry’, money=3000.0}
思路分析
Mybatis的基础环境我们已经准备好了,接下来就得分析在上述的内容中,哪些对象可以交给Spring来管理?
- Mybatis程序核心对象分析
从图中可以获取到,真正需要交给Spring管理的是SqlSessionFactory
- 整合Mybatis,就是将Mybatis用到的内容交给Spring管理,分析下配置文件

整合步骤
整合Spring与Mybatis大体需要做两件事,
- 第一件:Spring要管理MyBatis中的SqlSessionFactory
- 第二件:Spring要管理Mapper接口的扫描
那我们下面就开始来整合
步骤一:项目中导入整合需要的jar包1
2
3
4
5
6
7
8
9
10<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis-spring</artifactId>
<version>1.3.0</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>5.2.10.RELEASE</version>
</dependency>步骤二:创建Spring的主配置类
1 | //配置类注解 |
步骤三:
创建数据源的配置类
在配置类中完成数据源的创建
1 | public class JdbcConfig { |
步骤四:主配置类中读properties并引入数据源配置类
1 |
|
步骤五:创建Mybatis配置类并配置SqlSessionFactory
1 | public class MyBatisConfig { |
说明:
SqlSessionFactoryBean是前面我们讲解FactoryBean的一个子类,在该类中将SqlSessionFactory的创建进行了封装,简化对象的创建,我们只需要将其需要的内容设置即可。
方法中有一个参数为dataSource,当前Spring容器中已经创建了Druid数据源,类型刚好是DataSource类型,此时在初始化SqlSessionFactoryBean这个对象的时候,发现需要使用DataSource对象,而容器中刚好有这么一个对象,就自动加载了DruidDataSource对象。
sqlSessionFactory.setTypeAliasesPackage("com.blog.domain");,替换掉配置文件中的
1 | <typeAliases> |
sqlSessionFactory.setDataSource(dataSource);,替换掉配置文件中的
1 | <environments default="mysql"> |
这个MapperScannerConfigurer对象也是MyBatis提供的专用于整合的jar包中的类,用来处理原始配置文件中的mappers相关配置,加载数据层的Mapper接口类
MapperScannerConfigurer有一个核心属性basePackage,就是用来设置所扫描的包路径
步骤六:主配置类中引入Mybatis配置类
1 |
|
步骤七:编写运行类
在运行类中,从IOC容器中获取Service对象,调用方法获取结果
1 | public class App { |
步骤八:运行程序Account{id=1, name=’Tom’, money=2800.0}
至此,Spring与Mybatis的整合就已经完成了,其中主要用到的两个类分别是:
- SqlSessionFactoryBean
- MapperScannerConfigurer
Spring整合JUnit
步骤一:引入依赖
1 | <dependency> |
步骤二:编写测试类
1 | //设置类运行器 |
注意:
- 单元测试,如果测试的是注解配置类,则使用
@ContextConfiguration(classes = 配置类.class) - 单元测试,如果测试的是配置文件,则使用
@ContextConfiguration(locations={配置文件名,...}) - Junit运行后是基于Spring环境运行的,所以Spring提供了一个专用的类运行器,这个务必要设置,这个类运行器就在Spring的测试专用包中提供的,导入的坐标就是这个东西
SpringJUnit4ClassRunner - 上面两个配置都是固定格式,当需要测试哪个bean时,使用自动装配加载对应的对象,下面的工作就和以前做Junit单元测试完全一样了
知识点1:@RunWith
AOP简介
Spring有两个核心的概念,一个是IOC/DI,一个是AOP。
- 前面学习的
IOC\DI主要的内容是将对象的创建和管理交给了Spring容器,而AOP则是在不改变原有代码的基础上添加新的功能。什么是AOP?
AOP(Aspect Oriented Programming)面向切面编程,是一种编程范式,指导开发者如何组织程序结构AOP核心概念
- 前面编写的BookDaoImpl类,设置了最简单的几个执行业务逻辑的代码,没有其他功能,现在体验一下使用AOP对代码进行升级代码的内容很简单,就是测试一下万次执行的耗时
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
public class BookDaoImpl implements BookDao {
public void save() {
//记录程序当前执行执行(开始时间)
Long startTime = System.currentTimeMillis();
//业务执行万次
for (int i = 0;i<10000;i++) {
System.out.println("book dao save ...");
}
//记录程序当前执行时间(结束时间)
Long endTime = System.currentTimeMillis();
//计算时间差
Long totalTime = endTime-startTime;
//输出信息
System.out.println("执行万次消耗时间:" + totalTime + "ms");
}
public void update(){
System.out.println("book dao update ...");
}
public void delete(){
System.out.println("book dao delete ...");
}
public void select(){
System.out.println("book dao select ...");
}
}
当在App类中从容器中获取bookDao对象后,分别执行其save,delete,update和select方法后会有如下的打印结果这个案例中使用的AOP使得在不改动原代码的前提下,增强了原代码的功能,这就是AOPbook dao save …
book dao save …
book dao save …
book dao save …
book dao save …
book dao save …
执行万次消耗时间:79msbook dao delete …
book dao delete …
book dao delete …
book dao delete …
book dao delete …
book dao delete …
执行万次消耗时间:81msbook dao update …
book dao update …
book dao update …
book dao update …
book dao update …
book dao update …
执行万次消耗时间:63msbook dao select …
Spring是如何实现AOP的呢?
连接点(JoinPoint):程序执行过程中的任意位置,粒度为执行方法、抛出异常、设置变量等- 在SpringAOP中,理解为能够被增强的方法
切入点(Pointcut):真正要被增强的连接点- 在SpringAOP中,一个切入点可以描述一个具体方法,也可也匹配多个方法
- 一个具体的方法:如com.blog.dao包下的BookDao接口中的无形参无返回值的save方法
- 匹配多个方法:所有的save方法/所有的get开头的方法/所有以Dao结尾的接口中的任意方法/所有带有一个参数的方法
- 连接点范围比切入点范围大,是切入点的方法也一定是连接点,但是是连接点的方法不一定要被增强,所以可能不是切入点。
- 在SpringAOP中,一个切入点可以描述一个具体方法,也可也匹配多个方法
通知(Advice):在切入点处执行的操作,也就是要增加的共性功能- 在SpringAOP中,功能最终以方法的形式呈现
通知类:定义通知的类切面(Aspect):描述通知与切入点的对应关系。可以理解为切入点+通知
AOP入门案例
环境准备
- 创建Maven项目,并导入Spring依赖
1
2
3
4
5<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.2.10.RELEASE</version>
</dependency> 添加BookDao和BookDaoImpl类
1
2
3
4public interface BookDao {
public void save();
public void update();
}1
2
3
4
5
6
7
8
9
10
11
public class BookDaoImpl implements BookDao {
public void save() {
System.out.println(System.currentTimeMillis());
System.out.println("book dao save ...");
}
public void update() {
System.out.println("book dao update ...");
}
}创建Spring依赖类
1
2
3
4
public class SpringConfig {
}- 编写App运行类
1
2
3
4
5
6
7public class App {
public static void main(String[] args) {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(SpringConfig.class);
BookDao bookDao = context.getBean(BookDao.class);
bookDao.update();
}
}AOP实现步骤
步骤一:添加依赖spring-context中已经导入了spring-aop,所以不需要再单独导入spring-aop
导入AspectJ的jar包,AspectJ是AOP思想的一个具体实现,Spring有自己的AOP实现,但是相比于AspectJ来说比较麻烦,所以直接采用Spring整合ApsectJ的方式进行AOP开发。
1 | <dependency> |
步骤二:定义接口和实现类
准备环境的时候已经完成了步骤三:定义通知类和通知
通知就是将共性功能抽取出来后形成的方法,共性功能指的就是当前系统时间的打印。
类名和方法名没有要求,可以任意。
1 | public class MyAdvice { |
步骤四:定义切入点
BookDaoImpl中有两个方法,分别是update()和save(),我们要增强的是update方法,那么该如何定义呢?
1 | public class MyAdvice { |
步骤五:制作切面
切面是用来描述通知和切入点之间的关系,如何进行关系的绑定?
1 | public class MyAdvice { |
绑定切入点与通知关系,并指定通知添加到原始连接点的具体执行位置
说明:@Before翻译过来是之前,也就是说通知会在切入点方法执行之前执行,除此之前还有其他四种类型,后面会讲。
那这里就会在执行update()之前,来执行我们的method(),输出当前毫秒值
步骤六:将通知类配给容器并标识其为切面类
1 |
|
步骤七:开启注解格式AOP功能
使用@EnableAspectJAutoProxy注解
1 |
|
步骤八:运行程序
这次我们再来调用update()
1 | public class App { |
控制台成功输出了当前毫秒值
1662367945787
book dao update …
| 名称 | @EnableAspectJAutoProxy |
|---|---|
| 类型 | 配置类注解 |
| 位置 | 配置类定义上方 |
| 作用 | 开启注解格式AOP功能 |
| 名称 | @Aspect |
|---|---|
| 类型 | 类注解 |
| 位置 | 切面类定义上方 |
| 作用 | 设置当前类为AOP切面类 |
| 名称 | @Before |
|---|---|
| 类型 | 方法注解 |
| 位置 | 通知方法定义上方 |
| 作用 | 设置当前通知方法与切入点之间的绑定关系,当前通知方法在原始切入点方法前运行 |
| 名称 | @Pointcut |
|---|---|
| 类型 | 方法注解 |
| 位置 | 切入点方法定义上方 |
| 作用 | 设置切入点方法 |
| 属性 | value(默认):切入点表达式 |
AOP工作流程
这一节我们主要讲解两个知识点:AOP工作流程和AOP核心概念。其中核心概念是对前面核心概念的补充。
工作流程
AOP是基于Spring容器管理的bean做的增强,所以整个工作过程需要从Spring加载bean说起
流程一:Spring容器启动
- 容器启动就需要去加载bean,哪些类需要被加载呢?
- 需要被增强的类,如:BookServiceImpl
- 通知类,如:MyAdvice
- 注意此时bean对象还没有创建成功
流程二:读取所有切面配置中的切入点
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class MyAdvice {
private void ptx() {
}
private void pt() {
}
public void method() {
System.out.println(System.currentTimeMillis());
}
}上面这个例子中有两个切入点的配置,但是第一个
ptx()并没有被使用(该切入点没有被任何通知使用),所以不会被读取。流程三:初始化bean,判定bean对应的类中的方法是否匹配到任意切入点
- 注意第一步在容器启动的时候,bean对象还没有被创建成功。
- 要被实例化bean对象的类中的方法和切入点进行匹配

匹配失败,创建原始对象,如
UserDao- 匹配失败说明不需要增强,直接调用原始对象的方法即可。
匹配成功,创建原始对象(
目标对象)的代理对象,如:BookDao- 匹配成功说明需要对其进行增强
- 对哪个类做增强,这个类对应的对象就叫做
目标对象 - 因为要对目标对象进行功能增强,而采用的技术是
动态代理,所以会为其创建一个代理对象 - 最终运行的是代理对象的方法,在该方法中会对原始方法进行功能增强
流程四:获取bean执行方法
- 获取的bean是原始对象时,调用方法并执行,完成操作
- 获取的bean是代理对象时,根据代理对象的运行模式运行原始方法与增强的内容,完成操作
下面我们来验证一下容器中是否为代理对象
- 如果目标对象中的方法
会被增强,那么容器中将存入的是目标对象的代理对象 - 如果目标对象中的方法
不被增强,那么容器中将存入的是目标对象本身
- 如果目标对象中的方法
步骤一:修改App运行类,获取类的类型并输出1
2
3
4
5
6
7
8public class App {
public static void main(String[] args) {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(SpringConfig.class);
BookDao bookDao = context.getBean(BookDao.class);
System.out.println(bookDao);
System.out.println(bookDao.getClass());
}
}步骤二:修改MyAdvice类,改为不增强
将定义的切入点改为updatexxx,而BookDaoImpl类中不存在该方法,所以BookDao中的update方法在执行的时候,就不会被增强
所以此时容器中的对象应该是目标对象本身。
1 |
|
步骤三:运行程序
输出结果如下,确实是目标对象本身,符合我们的预期com.blog.dao.impl.BookDaoImpl@bcec361
class com.blog.dao.impl.BookDaoImpl步骤四:修改MyAdvice类,改为增强
将定义的切入点改为update,那么BookDao中的update方法在执行的时候,就会被增强
所以容器中的对象应该是目标对象的代理对象
1 |
|
步骤五:运行程序
结果如下com.blog.dao.impl.BookDaoImpl@3d34d211
class com.sun.proxy.$Proxy19
AOP核心概念
在上面介绍AOP的工作流程中,我们提到了两个核心概念,分别是:
目标对象(Target):原始功能去掉共性功能对应的类产生的对象,这种对象是无法直接完成最终工作的代理(Proxy):目标对象无法直接完成工作,需要对其进行功能回填,通过原始对象的代理对象实现
简单来说,目标对象就是要增强的类如:BookServiceImpl类对应的对象,也叫原始对象,不能说它不能运行,只能说它在运行的过程中对于要增强的内容是缺失的。
SpringAOP是在不改变原有设计(代码)的前提下对其进行增强的,它的底层采用的是代理模式实现的,所以要对原始对象进行增强,就需要对原始对象创建代理对象,在代理对象中的方法把通知如:MyAdvice中的method方法内容加进去,就实现了增强,这就是我们所说的代理(Proxy)。
AOP配置管理
AOP切入点表达式
- 在入门案例就已经用过切入点表达式了,此处具体学习对于AOP中切入点表达式,我们总共会学习三个内容,分别是
1
语法格式、通配符和书写技巧。语法格式
对切入点描述有两种方式: - 描述方式一:执行BookDao接口的无参数update方法
1
execution(void com.blog.dao.BookDao.update())
- 描述方式二:执行BookDaoImpl类的无参数update方法
1
execution(void com.blog.dao.impl.BookDaoImpl.update())
对于切入点表达式的语法为:
切入点表达式标准格式:动作关键字(访问修饰符 返回值 包名.类/接口名.方法名(参数) 异常名)
对于这个格式,不需要硬记,通过一个例子去理解它:1
execution(public User com.blog.service.UserService.findById(int))
execution:动作关键字,描述切入点的行为动作,例如execution表示执行到指定切入点
- public:访问修饰符,还可以是public,private等,可以省略
- User:返回值,写返回值类型
- com.blog.service:包名,多级包使用点连接
- UserService:类/接口名称
- findById:方法名
- int:参数,直接写参数的类型,多个类型用逗号隔开
- 异常名:方法定义中抛出指定异常,可以省略
通配符
使用通配符的主要目的就是简化配置,例如:
*:单个独立的任意符号,可以独立出现,也可以作为前缀或者后缀的匹配符出现
匹配com.blog包下的任意包中的UserService类或接口中所有find开头的带有一个参数的方法1
execution(public * com.blog.*.UserService.find*(*))
..:多个连续的任意符号,可以独立出现,常用于简化包名与参数的书写
匹配com包下的任意包中的UserService类或接口中所有名称为findById的方法1
execution(public User com..UserService.findById(..))
+:专用于匹配子类类型
这个使用率较低,描述子类的,*Service+,表示所有以Service结尾的接口的子类
1 | execution(* *..*Service+.*(..)) |
下面来具体分析一下各种用法
- 匹配接口,能匹配到
1 | execution(void com.blog.dao.BookDao.update()) |
匹配实现类,能匹配到
1
execution(void com.blog.dao.impl.BookDaoImpl.update())
返回值任意,能匹配到
1 | execution(* com.blog.dao.impl.BookDaoImpl.update()) |
- 返回值任意,但是update方法必须要有一个参数,无法匹配,要想匹配需要在update接口和实现类添加参数
1 | execution(* com.blog.dao.impl.BookDaoImpl.update(*)) |
- 返回值为void,com包下的任意包三层包下的任意类的update方法,匹配到的是实现类,能匹配
1 | execution(void com.*.*.*.*.update()) |
- 返回值为void,com包下的任意两层包下的任意类的update方法,匹配到的是接口,能匹配
1 | execution(void com.*.*.*.update()) |
- 返回值为void,方法名是update的任意包下的任意类,能匹配
1 | execution(void *..update()) |
- 匹配项目中任意类的任意方法,能匹配,但是不建议使用这种方式,影响范围广
1 | execution(* *..*(..)) |
- 匹配项目中任意包任意类下只要以u开头的方法,update方法能满足,能匹配
1 | execution(* *..u*(..)) |
- 匹配项目中任意包任意类下只要以e结尾的方法,update和save方法能满足,能匹配
1 | execution(* *..*e(..)) |
- 返回值为void,com包下的任意包任意类任意方法,能匹配,*代表的是方法
1 | execution(void com..*()) |
- 将项目中所有业务层方法的以find开头的方法匹配
1 | execution(* com.blog.*.*Service.find*(..)) |
- 将项目中所有业务层方法的以save开头的方法匹配
1 | execution(* com.blog.*.*Service.save*(..)) |
书写技巧
对于切入点表达式的编写其实是很灵活的,那么在编写的时候,有没有什么好的技巧让我们用用:
- 所有代码按照标准规范开发,否则以下技巧全部失效
- 描述切入点通常
描述接口,而不描述实现类,如果描述到实现类,就出现紧耦合了 - 访问控制修饰符针对接口开发均采用public描述(
可省略访问控制修饰符描述) - 返回值类型对于增删改类使用精准类型加速匹配,对于查询类使用
*通配快速描述 包名书写尽量不使用..匹配,效率过低,常用*做单个包描述匹配,或精准匹配接口名/类名书写名称与模块相关的采用*匹配,例如UserService书写成*Service,绑定业务层接口名- 方法名书写以
动词进行精准匹配,名词采用*匹配,例如getById书写成getBy*,selectAll书写成selectAll - 参数规则较为复杂,根据业务方法灵活调整
- 通常
不使用异常作为匹配规则




