Spring
# Spring概念
Spring是分层的 Java SE/EE应用 full-stack 轻量级开源框架,以 IoC(Inverse Of Control:反转控制)和
AOP(Aspect Oriented Programming:面向切面编程)为内核。

# Spring Framework学习路线

# 核心概念
IOC(Inversion of Control)控制反转
使用对象时,由主动new产生对象转换为由外部提供对象,此过程中对象创建控制权由程序转移到外部,此思想称为控制反转。通俗的讲就是“将new对象的权利交给Spring,我们从Spring中获取对象使用即可”Spring技术对IoC思想进行了实现
- Spring提供了一个容器,称为IOC容器,用来充当IoC思想中的“外部”
- IOC容器负责对象的创建、初始化等一系列工作,被创建或被管理的对象在IoC容器中统称为Bean
DI(Dependency Injection)依赖注入
- 在容器中建立bean与bean之间的依赖关系的整个过程,称为依赖注入。

简单说:将IOC容器中的Dao对象赋值给BookServiceImpl中的BookDao就是依赖注入
- 目标:充分解耦
- 使用IoC容器管理bean(IOC)
- 在IoC容器内将有依赖关系的bean进行关系绑定(DI)
- 最终效果
- 使用对象时不仅可以直接从IoC容器中获取,并且获取到的bean已经绑定了所有的依赖关系
# Spring核心容器
容器类层次结构
- BeanFactory是IoC容器的顶层接口,初始化BeanFactory对象时,加载的bean延迟加载
- ApplicationContext接口是Spring容器的核心接口,初始化时bean立即加载
- ApplicationContext接口提供基础的bean操作相关方法,通过其他接口扩展其功能
- ApplicationContext接口常用初始化类
- ClassPathXmlApplicationContext(常用)
- FileSystemXmlApplicationContext
- AnnotationConfigApplicationContext
bean相关
依赖注入相关
# 核心容器的三种创建方式
- 方式一:类路径加载配置文件
ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
- 方式二:文件路径(绝对路径)加载配置文件
ApplicationContext ctx = new FileSystemXmlApplicationContext("D:\\applicationContext.xml");
- 加载多个配置文件
ApplicationContext ctx = new ClassPathXmlApplicationContext("bean1.xml", "bean2.xml");
# IOC
IOC(Inversion of Control)控制反转
使用对象时,由主动new产生对象转换为由外部提供对象,此过程中对象创建控制权由程序转移到外部,此思想称为控制反转。通俗的讲就是“将new对象的权利交给Spring,我们从Spring中获取对象使用即可”Spring技术对IoC思想进行了实现** **
- Spring提供了一个容器,称为IOC容器,用来充当IoC思想中的“外部”
- IOC容器负责对象的创建、初始化等一系列工作,被创建或被管理的对象在IoC容器中统称为Bean
Bean属性
| 属性 | 说明 |
|---|---|
| id | 容器中唯一的标识 |
| name | 还可以有多个名字,使用逗号,空格,分号隔开都可以 |
| class | 指定类全名,指定的是实现类,不是接口 |
| scope | 指定bean在容器中作用范围 singleton:默认值,表示这个是单例对象,整个容器中只会创建一个对象 prototype:这是多例对象,每次获取一个新的对象 |
| init-method | 创建对象时,执行的初始化的方法 |
| destroy-method | 销毁对象时,执行的方法。只用于单例对象 |
# IOC开发步骤【XML】
【第一步】导入Spring坐标
<dependencies>
<!--导入spring的坐标spring-context,对应版本是5.2.10.RELEASE-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.2.10.RELEASE</version>
</dependency>
<!-- 导入junit的测试包 -->
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter</artifactId>
<version>5.7.0</version>
<scope>test</scope>
</dependency>
</dependencies>
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
【第二步】定义Spring管理的类(接口)
package com.itheima.dao;
/**
* 书籍的DAO接口
*/
public interface BookDao {
/**
添加书籍
*/
void save();
}
2
3
4
5
6
7
8
9
10
11
package com.itheima.dao.impl;
import com.itheima.dao.BookDao;
/**
* 书籍DAO的实现类
*/
public class BookDaoImpl implements BookDao {
public void save() {
System.out.println("DAO:添加书籍到数据库");
}
}
2
3
4
5
6
7
8
9
10
11
12
package com.itheima.service;
public interface BookService {
/**
添加书籍
*/
void save();
}
2
3
4
5
6
7
8
package com.itheima.service.impl;
import com.itheima.dao.BookDao;
import com.itheima.dao.impl.BookDaoImpl;
import com.itheima.service.BookService;
public class BookServiceImpl implements BookService {
//创建成员对象
private BookDao bookDao = new BookDaoImpl();
//实现业务方法
@Override
public void save() {
System.out.println("业务层:调用添加书籍的方法");
bookDao.save();
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
【第三步】创建Spring配置文件,配置对应类作为Spring管理的bean对象
- 定义applicationContext.xml配置文件并配置BookServiceImpl
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<!--
bean标签:表示配置bean
id属性:表示给bean起名字
class属性:表示给bean定义类型
-->
<bean id="bookService" class="com.itheima.service.impl.BookServiceImpl"></bean>
</beans>
2
3
4
5
6
7
8
9
10
11
12
13
**注意:**id属性bean对象在容器中的唯一标识,不能重复,因为Spring容器的底层是Map集合,键不能重复
【第四步】初始化IOC容器(Spring核心容器/Spring容器),通过容器获取Bean对象
public class App {
public static void main(String[] args) {
//1.创建IoC容器对象,加载spring核心配置文件
ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
//2 从IOC容器中获取Bean对象(BookService对象)
BookService bookService= (BookService)ctx.getBean("bookService");
// 指定对象类型
BookService bookService= ctx.getBean("bookService",BookService.class);
// 获取指定类型的Bean对象,前提容器中只有一个对象是这个类型,否则会报错
BookService bookService= ctx.getBean(BookService.class);
//3 调用Bean对象(BookService对象)的方法
bookService.save();
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# DI
# DI开发步骤【XML】
【第一步】删除使用new的形式创建对象的代码
【第二步】提供依赖对象对应的setter方法
package com.itheima.service.impl;
import com.itheima.dao.BookDao;
import com.itheima.service.BookService;
public class BookServiceImpl implements BookService {
//【第一步】删除使用new的形式创建对象的代码,解除对象之间的耦合度
private BookDao bookDao;
//【第二步】提供依赖对象对应的setter方法
public void setBookDao(BookDao bookDao) {
this.bookDao = bookDao;
}
//实现业务方法
@Override
public void save() {
System.out.println("业务层:调用添加书籍的方法");
bookDao.save();
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
【第三步】配置service与dao之间的关系
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<!--
bean标签:表示配置bean
id属性:表示给bean起名字
class属性:表示给bean定义类型
-->
<bean id="bookDao" class="com.itheima.dao.impl.BookDaoImpl"/>
<bean id="bookService" class="com.itheima.service.impl.BookServiceImpl">
<!--配置server与dao的关系
property标签:表示配置当前bean的属性
name属性:表示配置哪一个具体的属性
ref属性:表示参照哪一个bean
-->
<property name="bookDao" ref="bookDao"/>
</bean>
</beans>
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
在applicationContext.xml中配置
图解
# 依赖注入方式
| <property>的属性 | 描述 |
|---|---|
| name | 属性名 |
| value | 简单类型的值 |
| ref | 引用类型的值 |
# 依赖注入的两种方式
- setter注入
简单类型
引用类型(常用) - 构造器注入
简单类型
引用类型
简单类型:八种基本数据类型+String
# Setter方式注入
引用类型
简单类型
# 构造方法方式注入
引用类型
简单类型
参数适配
依赖注入方式选择
- 强制依赖使用构造器进行,使用setter注入有概率不进行注入导致null对象出现
- 可选依赖使用setter注入进行,灵活性强
- Spring框架倡导使用构造器,第三方框架内部大多数采用构造器注入的形式进行数据初始化,相对严谨
- 如果有必要可以两者同时使用,使用构造器注入完成强制依赖的注入,使用setter注入完成可选依赖的注入
- 实际开发过程中还要根据实际情况分析,如果受控对象没有提供setter方法就必须使用构造器注入
- 自己开发的模块推荐使用setter注入
# 依赖自动装配
# 自动装配概念
- IoC容器根据bean所依赖的资源在容器中自动查找并注入到bean中的过程称为自动装配
- 自动装配方式
- 按类型(常用)byType
- 按名称 byName
- 按构造方法 constructor
- 不启用自动装配 no
类型
# 依赖自动装配
<bean id="bookDao" class="com.itheima.dao.impl.BookDaoImpl"/>
<bean id="bookService" class="com.itheima.service.impl.BookServiceImpl" autowire="byType"/>
2
配置中使用bean标签autowire属性设置自动装配的类型
# 依赖自动装配特征
- 自动装配用于引用类型依赖注入,不能对简单类型进行操作
- 使用按类型装配时(byType)必须保障容器中相同类型的bean唯一,推荐使用
- 使用按名称装配时(byName)必须保障容器中具有指定名称的bean,不推荐使用
- 自动装配优先级低于setter注入与构造器注入,同时出现时自动装配配置失效
<bean id="bookDao1" class="com.itheima.dao.impl.BookDaoImpl">
<constructor-arg type="int" value="10"/>
<constructor-arg type="java.lang.String" value="mysql"/>
</bean>
<bean id="bookDao" class="com.itheima.dao.impl.BookDaoImpl">
<constructor-arg type="int" value="20"/>
<constructor-arg type="java.lang.String" value="oracle"/>
</bean>
<bean id="bookService" class="com.itheima.service.impl.BookServiceImpl" autowire="byName">
</bean>
2
3
4
5
6
7
8
9
10
11
12
# 集合注入
# 注入数组类型数据
<property name="array">
<array>
<value>100</value>
<value>200</value>
<value>300</value>
</array>
</property>
2
3
4
5
6
7
# 注入List类型数据
<property name="list">
<list>
<value>itcast</value>
<value>itheima</value>
<value>boxuegu</value>
<value>chuanzhihui</value>
</list>
</property>
2
3
4
5
6
7
8
# 注入Set类型数据
<property name="set">
<set>
<value>itcast</value>
<value>itheima</value>
<value>boxuegu</value>
<value>boxuegu</value>
</set>
</property>
2
3
4
5
6
7
8
# 注入Map类型数据
<property name="map">
<map>
<entry key="country" value="china"/>
<entry key="province" value="henan"/>
<entry key="city" value="kaifeng"/>
</map>
</property>
2
3
4
5
6
7
# 注入Properties类型数据
<property name="properties">
<props>
<prop key="country">china</prop>
<prop key="province">henan</prop>
<prop key="city">kaifeng</prop>
</props>
</property>
2
3
4
5
6
7
说明:property标签表示setter方式注入,构造方式注入constructor-arg标签内部也可以写<array>、<list>、<set>、<map>、<props>标签,并且<array>、<list>、<set>、<map>、<props>标签只是语义上的区别
# AOP
概念
AOP(Aspect Oriented Programming)面向切面编程,一种编程范式,指导开发者如何组织程序结构
OOP(Object Oriented Programming)面向对象编程
作用:简单的说就是在不改变方法源代码的基础上对方法进行功能增强。
Spring理念:无入侵式/无侵入式
特点:
- 将切面代码与主业务代码分开编写
- 在程序执行过程中动态将切面代码与主业务代码织入到一起
- 底层使用的是动态代理模式
# AOP开发思想
AOP真正目的是:你写代码的时候,只需考虑主流程,而不用考虑那些不重要的,但又必须要写的其它相同的代码,这些其它的相同代码所在的类就是切面类。
编写代码时只关注业务功能
在代码运行时将共性功能与非共性功能织入到一起生成代理对象,相当于将业务功能传入动态代理对象中,由代理对象对共性的功能进行补全。
# 应用场景
银行系统会有一个取款流程,还会有一个查询余额流程,两者有一个相同的验证流程。
验证用户的功能是相同的,但又在不同地方出现,我们可以把它提取出来,做成切面类在程序执行的时候动态的添加到业务程序中去,这个切面类可以在不同的地方重用
•有了AOP,写代码时不需要把这个验证用户步骤写进去,即完全不考虑验证用户。只写取款和显示余额的业务代码。
•而在另一个地方,写好验证用户的代码。这个验证用户的代码就是切面代码,以后在执行取款和显示余额的时候,利用代理模式。将验证用户的功能在执行取款和显示余额前调用。
主要应用场景:
1.事务处理
2.日志记录
3.用户权限
# AOP中的核心概念

- 连接点(JoinPoint):【业务类中所有的方法】在程序执行过程中的某个阶段点,就是指主业务方法的调用,它是客观存在的。指的接口或实现类中所有的方法,例如:save()、update()、delete()、select()等都是连接点。
- 切入点(Pointcut):【业务类中选中的部分方法】满足某一规则的类或方法都是切入点,通过切入点表达式来制定规则。是连接点中的部分方法。即进行功能增强了的方法,例如:save()、update()、delete()方法,select()方法没有被增强所以不是切入点,但是是连接点。
- 在SpringAOP中,一个切入点可以只描述一个具体方法,也可以匹配多个方法
- 一个具体方法:com.itheima.dao包下的BookDao接口中的无形参无返回值的save方法
- 匹配多个方法:所有的save方法,所有的get开头的方法,所有以Dao结尾的接口中的任意方法,所有带有一个参数的方法
- 在SpringAOP中,一个切入点可以只描述一个具体方法,也可以匹配多个方法
- 通知(Advice):【共性的方法,要织入的方法】切入点处所要执行的程序代码,即要执行的公共方法。通知的类型有: 前置通知、后置通知、异常通知、最终通知、环绕通知。在切入点前后执行的操作,也就是增强的共性功能
- 在SpringAOP中,功能最终以方法的形式呈现
- 切面类(Aspect):【通知方法和切入点规则所在的类】切面指的是切入点(规则)和通知(织入方法)的类。描述通知与切入点的对应关系,也就是哪些通知方法对应哪些切入点方法。切面类 = 切入点规则+通知方法。
- 目标对象(Target):【要增强的业务类对象】需要被代理增强的对象,即主业务类。
- 织入(Weaving):【把通知方法加入到目标对象中的过程】织入指的是把新增的功能用于目标对象,创建代理对象的过程。
- 代理(Proxy):【增强的对象】一个类被AOP织入增强后产生的结果类,即代理类。
# AOP注解开发
相关注解
| 注解 | 说明 |
|---|---|
| @EnableAspectJAutoProxy | 开启注解开发AOP功能 |
| @Aspect | 放在类上表示这是一个切面类 |
| @Before | 放在方法上:前置通知 |
| @AfterReturning | 后置通知 |
| @AfterThrowing | 异常通知 |
| @After | 最终通知,在后置通知或异常通知 |
| @Around | 环绕通知 |
| @Pointcut | 定义切入点表达式 |
xml开启AOP注解自动代理:<**aop:aspectj-autoproxy**/>
开发步骤
- 依赖坐标
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.9.4</version>
</dependency>
2
3
4
5
- 定义dao接口与实现类
public interface BookDao {
void save();
void update();
}
2
3
4
@Repository
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 ...");
}
}
2
3
4
5
6
7
8
9
10
11
- 定义通知类,制作通知方法
//通知类必须配置成Spring管理的bean
@Component
public class MyAdvice {
public void method(){
System.out.println(System.currentTimeMillis());
}
}
2
3
4
5
6
7
- 定义切入点表达式、配置切面(绑定切入点与通知关系)
//通知类必须配置成Spring管理的bean
@Component
//设置当前类为切面类类
@Aspect
public class MyAdvice {
//设置切入点,@Pointcut注解要求配置在方法上方
@Pointcut("execution(void com.itheima.dao.BookDao.update())")
private void pt(){}
//设置在切入点pt()的前面运行当前操作(前置通知)
@Before("pt()")
public void method(){
System.out.println(System.currentTimeMillis());
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
- 在配置文件中开启Spring注解包扫描和开启AOP功能
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/aop https://www.springframework.org/schema/aop/spring-aop.xsd">
<!--注解包扫描-->
<context:component-scan base-package="cn.kk"/>
<!--开启AOP注解自动代理-->
<aop:aspectj-autoproxy/>
</beans>
2
3
4
5
6
7
8
9
10
11
# AOP切点表达式
| 切入点函数 | 作用 |
|---|---|
| execution | 细粒度函数,精确到方法 |
| within | 粗粒度,只能精确到类 |
| bean | 粗粒度,精确到类,从容器中通过id获取对象 |
**标准格式:**切入点函数(访问修饰符 返回值 包名.类/接口名.方法名(参数)异常名)execution(public int com.itheima.service.UserService.DeleteById(int))
注意:表达式中的返回值只能识别简单数据类型
通配符
参数个数通配符写法:
()没有参数(*)1个或多个参数(..)0个或多个参数
类全名的包通配符写法:
..表示当前包和子包
*单个独立的任意符号,可以独立出现,也可以作为前缀或者后缀的匹配符出现
execution(public * com.itheima.*.UserService.find*(*))
匹配com.itheima包下的任意包中的UserService类或接口中所有find开头的带有一个参数的方法
..多个连续的任意符号,可以独立出现,常用于简化包名与参数的书写
execution(public User com..UserService.findById(..))
匹配com包下的任意包中的UserService类或接口中所有名称为findById的方法
+专用于匹配子类类型
execution(* *..*Service+.*(..))
扩展
execution(public void com.itheima.service.impl.AccountServiceImpl.save())
最精确的写法
execution(* com.itheima.service..*.*(String))
service的包和子包下面所有的类和方法,方法参数是String类型
execution(* *(..))
匹配所有的类和方法
execution(* save(..)) || execution(* update(..))
匹配方法名是save或update的方法
也可以使用&&符号,虽然语法是正确的,但没有意义
!execution(* save(..))
除了方法名是save的所有方法
within(com.itheima..*)
匹配包和子包中所有的类(不能是接口)
bean(accountService)
从容器中获取一个id为accountService的类中所有方法
bean(*Service)
从容器中获取所有Service的方法
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
# AOP工作流程
- Spring容器启动
- 读取所有切面配置中的切入点
- 初始化bean,判定bean对应的类中的方法是否匹配到任意切入点
- 匹配失败,创建原始对象
- 匹配成功,创建原始对象(目标对象)的代理对象
- 获取bean执行方法
- 获取的bean是原始对象时,调用方法并执行,完成操作
- 获取的bean是代理对象时,根据代理对象的运行模式运行原始方法与增强的内容,完成操作
AOP核心概念
- 目标对象(Target):被代理的对象,也叫原始对象,该对象中的方法没有任何功能增强。
- 代理对象(Proxy):代理后生成的对象,由Spring帮我们创建代理对象。
# AOP通知类型
- 前置通知:原始方法执行前执行,如果通知中抛出异常,阻止原始方法运行
- 后置通知:原始方法正常执行完毕并返回结果后执行,如果原始方法中抛出异常,无法执行
- 最终通知:原始方法执行后执行,无论原始方法中是否出现异常,都将执行通知
- 异常通知:原始方法抛出异常后执行,如果原始方法没有抛出异常,无法执行
- 环绕通知:在原始方法执行前后均有对应执行,还可以阻止原始方法的执行 | 注解 | 说明 | | --- | --- | | @Pointcut | 定义切入点表达式,给其他通知重用 | | @Before | 放在方法上:前置通知 | | @AfterReturning | 后置通知 | | @AfterThrowing | 异常通知 | | @After | 最终通知,在后置通知后或异常通知后 | | @Around | 环绕通知 |
前置通知
- 名称:@Before
- 类型:方法注解
- 位置:通知方法定义上方
- 作用:设置当前通知方法与切入点之间的绑定关系,当前通知方法在原始切入点方法前运行
- 范例:
@Before("pt()")
public void before() {
System.out.println("before advice ...");
}
2
3
4
最终通知
- 名称:@After
- 类型:方法注解
- 位置:通知方法定义上方
- 作用:设置当前通知方法与切入点之间的绑定关系,当前通知方法在原始切入点方法后运行
- 范例:
@After("pt()")
public void after() {
System.out.println("after advice ...");
}
2
3
4
后置通知
- 名称:@AfterReturning(了解)
- 类型:方法注解
- 位置:通知方法定义上方
- 作用:设置当前通知方法与切入点之间的绑定关系,当前通知方法在原始切入点方法正常执行完毕后运行
- 范例:
@AfterReturning("pt()")
public void afterReturning() {
System.out.println("afterReturning advice ...");
}
2
3
4
异常通知
- 名称:@AfterThrowing(了解)
- 类型:方法注解
- 位置:通知方法定义上方
- 作用:设置当前通知方法与切入点之间的绑定关系,当前通知方法在原始切入点方法运行抛出异常后执行
- 范例:
@AfterThrowing("pt()")
public void afterThrowing() {
System.out.println("afterThrowing advice ...");
}
2
3
4
环绕通知
- 名称:@Around(重点,常用)
- 类型:方法注解
- 位置:通知方法定义上方
- 作用:设置当前通知方法与切入点之间的绑定关系,当前通知方法在原始切入点方法前后运行
- 环绕方法可以获取目标方法的返回值
- 可以获取目标方法的参数
- 可以阻止目标方法的执行
- 可以修改目标方法的参数值和返回值
- 范例:
@Around("pt()")
public Object around(ProceedingJoinPoint pjp) throws Throwable {
try{
System.out.println("前置通知");
// 调用目标方法获取返回值
Object ret = pjp.proceed();
// 获取目标方法的签名
Signature signature = pjp.getSignature();
// 获取目标方法名
String name = signature.getName();
// 获取目标方法所在类全名
Class declaringType = signature.getDeclaringType();
// 获取目标参数
Object[] args = pjp.getArgs();
args[0] = "123";
// 使用有参的proceed()调用目标方法
Object ret = pjp.proceed(args);
System.out.println("后置通知");
}catch{
System.out.println("异常通知");
}finally{
System.out.println("最终通知");
}
return ret;
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
环绕通知注意事项
- 环绕通知方法形参必须是ProceedingJoinPoint,表示正在执行的连接点,使用该对象的proceed()方法表示对原始对象方法进行调用,返回值为原始对象方法的返回值。
- 环绕通知方法的返回值建议写成Object类型,用于将原始对象方法的返回值进行返回,哪里使用代理对象就返回到哪里。
# AOP切入点数据获取
获取参数
- JoinPoint对象描述了连接点方法的运行状态,可以获取到原始方法的调用参数
@Before("pt()")
public void before(JoinPoint jp) {
Object[] args = jp.getArgs(); //获取连接点方法的参数们
System.out.println(Arrays.toString(args));
}
2
3
4
5
- ProccedJointPoint是JoinPoint的子类
@Around("pt()")
public Object around(ProceedingJoinPoint pjp) throws Throwable {
Object[] args = pjp.getArgs(); //获取连接点方法的参数们
System.out.println(Arrays.toString(args));
Object ret = pjp.proceed();
return ret;
}
2
3
4
5
6
7
说明:所有通知都可以获取到连接点方法的参数
获取返回值
- 后置通知可以获取切入点方法中的返回值,使用形参可以接收对应的返回值对象
@AfterReturning(value = "pt()",returning = "ret")
public void afterReturning(Object ret) { //变量名要和returning="ret"的属性值一致
System.out.println("afterReturning advice ..."+ret);
}
2
3
4
- 环绕通知中可以手工书写对原始方法的调用,得到的结果即为原始方法的返回值
@Around("pt()")
public Object around(ProceedingJoinPoint pjp) throws Throwable {
// 手动调用连接点方法,返回值就是连接点方法的返回值
Object ret = pjp.proceed();
return ret;
}
2
3
4
5
6
说明:在后置通知和环绕通知中都可以获取到连接点方法的返回值
获取异常
- 异常通知可以获取切入点方法中出现的异常信息,使用形参可以接收对应的异常对象
@AfterThrowing(value = "pt()",throwing = "t")
public void afterThrowing(Throwable t) {//变量名要和throwing = "t"的属性值一致
System.out.println("afterThrowing advice ..."+ t);
}
2
3
4
- 异常通知可以获取切入点方法运行的异常信息,使用形参可以接收运行时抛出的异常对象
@Around("pt()")
public Object around(ProceedingJoinPoint pjp) {
Object ret = null;
//此处需要try...catch处理,catch中捕获到的异常就是连接点方法中抛出的异常
try {
ret = pjp.proceed();
} catch (Throwable t) {
t.printStackTrace();
}
return ret;
}
2
3
4
5
6
7
8
9
10
11
说明:在异常通知和环绕通知中都可以获取到连接点方法中出现的异常
# Spring的两种动态代理
原理区别:
java动态代理是利用反射机制生成一个实现代理接口的匿名类,在调用具体方法前调用InvokeHandler来处理。
而cglib动态代理是利用asm开源包,对代理对象类的class文件加载进来,通过修改其字节码生成子类来处理。
- 如果目标对象实现了接口,默认情况下会采用JDK的动态代理实现AOP
- 如果目标对象实现了接口,可以强制使用CGLIB实现AOP
- 如果目标对象没有实现了接口,必须采用CGLIB库,spring会自动在JDK动态代理和CGLIB之间转换
如何强制使用CGLIB实现AOP?
(1)添加CGLIB库,SPRING_HOME/cglib/*.jar
(2)在spring配置文件中加入<aop:aspectj-autoproxy proxy-target-class="true"/>
JDK动态代理和CGLIB字节码生成的区别?
(1)JDK动态代理只能对实现了接口的类生成代理,而不能针对类
(2)CGLIB是针对类实现代理,主要是对指定的类生成一个子类,覆盖其中的方法
因为是继承,所以该类或方法最好不要声明成final
# Spring事务
- 事务作用:在数据层保障一系列的数据库操作同成功同失败
- Spring事务作用:在数据层或业务层保障一系列的数据库操作同成功同失败

也就是需要在Spring容器中添加事务管理器对象
# 开发步骤
【第一步】在业务层接口上添加Spring事务管理
package com.itheima.service;
import org.springframework.transaction.annotation.Transactional;
/**
* 业务接口
*/
public interface AccountService {
/**
* 转账操作
* @param out 传出方
* @param in 转入方
* @param money 金额
*/
@Transactional
void transfer(String out,String in ,Double money) ;
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
在转账的方法上添加@Transactional注解
注意事项
- Spring注解式事务通常添加在业务层接口中而不会添加到业务层实现类中,降低耦合
- 注解式事务可以添加到业务方法上表示当前方法开启事务,也可以添加到接口上表示当前接口所有方法开启事务
【第二步】设置事务管理器(将事务管理器添加到IOC容器中)
//配置事务管理器,mybatis使用的是jdbc事务
@Bean
public PlatformTransactionManager transactionManager(DataSource dataSource){
DataSourceTransactionManager transactionManager = new DataSourceTransactionManager();
transactionManager.setDataSource(dataSource);
return transactionManager;
}
2
3
4
5
6
7
说明:可以在JdbcConfig中配置事务管理器
<bean class="org.springframework.jdbc.datasource.DataSourceTransactionManager" id="manager">
<property name="dataSource" ref="dataSource"/>
</bean>
2
3
注意事项
- 事务管理器要根据实现技术进行选择
- MyBatis框架使用的是JDBC事务
【第三步】开启注解式事务驱动
@EnableTransactionManagement
@Configuration
@ComponentScan("com.itheima")
@PropertySource("classpath:jdbc.properties")
@Import({JdbcConfig.class,MybatisConfig.class})
//开启注解式事务驱动
@EnableTransactionManagement
public class SpringConfig {
}
2
3
4
5
6
7
8
xml通过如下配置开启注解式事务驱动
<tx:annotation-driven** **transaction-manager="manager" proxy-target-class="true"/>
# Spring事务角色
- 事务管理员:发起事务方,在Spring中通常指代业务层开启事务的方法
- 事务协调员:加入事务方,在Spring中通常指数据层方法,也可以是业务层方法

# @Transactional注解中与事务相关配置
| 属性 | 作用 | 示例 |
|---|---|---|
| readOnly | 设置是否为只读事务 | readOnly=true 只读事务 |
| timeout | 设置事务超时时间 | timeout = -1(永不超时) |
| rollbackFor | 设置事务回滚异常(class) | rollbackFor= {NullPointException.class} |
| rollbackForClassName | 设置事务回滚异常(String) | 同上格式为字符串 |
| noRollbackFor | 设置事务不回滚异常(class) | noRollbackFor= {NullPointException.class} |
| noRollbackForClassName | 设置事务不回滚异常(String) | 同上格式为字符串 |
| propagation | 设置事务传播行为 | …… |
**说明:**对于RuntimeException类型异常或者Error错误,Spring事务能够进行回滚操作。但是对于非运行时异常,Spring事务是不进行回滚的,所以需要使用rollbackFor来设置要回滚的异常。
# 事务的传播行为
**事务传播行为:**事务协调员对事务管理员所携带事务的处理态度或者方法间对事务的处理行为
如:方法1(事务管理员)开启了事务后,方法2(事务协调员)会进行的行为
传播属性行为类型
| 事务传播行为类型 | 说明 |
|---|---|
| PROPAGATION_REQUIRED | 如果当前没有事务,就新建一个事务,如果已经存在一个事务中,加入到这个事务中。这是最常见的选择。 |
| PROPAGATION_REQUIRES_NEW | 新建事务,如果当前存在事务,把当前事务挂起。 |
| PROPAGATION_SUPPORTS | 支持当前事务,如果当前没有事务,就以非事务方式执行。 |
| PROPAGATION_NOT_SUPPORTED | 以非事务方式执行操作,如果当前存在事务,就把当前事务挂起。 |
| PROPAGATION_MANDATORY | 使用当前的事务,如果当前没有事务,就抛出异常。 |
| PROPAGATION_NEVER | 以非事务方式执行,如果当前存在事务,则抛出异常。 |
| PROPAGATION_NESTED | 如果当前存在事务,则在嵌套事务内执行。如果当前没有事务,则执行与PROPAGATION_REQUIRED类似的操作。 |
行为
示例:
@Transactional(propagation = Propagation.REQUIRES_NEW,
rollbackFor = {NullPointerException.class})
2
# Bean
# Bean的基础配置
# 配置说明
| 类别 | 描述 |
|---|---|
| 名称 | bean |
| 类型 | 标签 |
| 所属 | beans标签 |
| 功能 | 定义Spring核心容器管理的对象 |
| 格式 | _ _ |
| 属性 列表 | id:bean的id,使用容器可以通过id值获取对应的bean,在一个容器中id值唯一 class:bean的类型,即配置的bean的全路径类名 |
| 范例 |
# Bean别名配置
| 类别 | 描述 |
|---|---|
| 名称 | name |
| 类型 | 属性 |
| 所属 | bean标签 |
| 功能 | 定义bean的别名,可定义多个,使用逗号(,)分号(;)空格( )分隔 |
| 范例 |
【注意】 获取bean无论是通过id还是name获取,如果无法获取到,将抛出异常NoSuchBeanDefinitionException NoSuchBeanDefinitionException: No bean named 'bookServiceImpl' available
# 作用范围配置
| 类别 | 描述 |
|---|---|
| 名称 | scope |
| 类型 | 属性 |
| 所属 | bean标签 |
| 功能 | 定义bean的作用范围,可选范围如下 singleton:单例(默认) prototype:非单例 |
| 范例 |
扩展:scope的取值不仅仅只有singleton和prototype,还有request、session、global-session,表示创建出的对象放置在web容器(tomcat)对应的位置。比如:request表示保存到request域中。
为什么bean默认为单例?
- 为了提高性能
- 少创建实例
- 减少垃圾回收
- 缓存快速获取
单例有啥劣势?
如果是有状态的话在并发环境下线程不安全。
什么是有状态对象?什么是无状态对象?
有状态对象:有实例变量可以标志其对象所处的状态。(有实例变量的对象,有存储数据能力)- 白话:有属性的对象
无状态对象:无实例变量可以标志其对象所处的状态。(无实例变量的对象,无存储数据能力)- 白话:无属性的对象
适合交给容器进行管理的bean
- 控制器对象 (以前用的Servlet做为控制器)
- 业务层对象 (如:BookService)
- 数据层对象 (如:BookDao或BookMapper)
- 工具对象 (如:CartUtils对象)
以上对象都只需要创建一个就够了
不适合交给容器进行管理的bean
- 封装实体的域对象(实体类对象 Book 包名:pojo,entity, domain)
# 实例化Bean的三种方式
# 构造方法方式
由Spring容器来负责创建对象
- BookDaoImpl实现类
public class BookDaoImpl implements BookDao {
public BookDaoImpl() {
System.out.println("book dao constructor is running ....");
}
public void save() {
System.out.println("book dao save ...");
}
}
2
3
4
5
6
7
8
- applicationContext.xml配置
<!--方式一:构造方法实例化bean-->
<bean id="bookDao" class="com.itheima.dao.impl.BookDaoImpl"/>
2
- AppForInstanceBook测试类
public class AppForInstanceBook {
public static void main(String[] args) {
ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
BookDao bookDao = (BookDao) ctx.getBean("bookDao");
bookDao.save();
}
}
2
3
4
5
6
7
8
9
- 运行结果

【注意】无参构造方法如果不存在,将抛出异常BeanCreationException
# 静态工厂方式
不由Spring容器来创建对象,由其他的对象工厂来创建对象
- OrderDao接口和OrderDaoImpl实现类
public interface OrderDao {
public void save();
}
public class OrderDaoImpl implements OrderDao {
public void save() {
System.out.println("order dao save ...");
}
}
2
3
4
5
6
7
8
- OrderDaoFatory工厂类
//静态工厂创建对象
public class OrderDaoFactory {
public static OrderDao getOrderDao(){
System.out.println("factory setup....");
return new OrderDaoImpl();
}
}
2
3
4
5
6
7
- applicationContext.xml配置
<!--方式二:使用静态工厂实例化bean-->
<bean id="orderDao" class="com.itheima.factory.OrderDaoFactory" factory-method="getOrderDao"/>
2

- AppForInstanceOrder测试类
public class AppForInstanceOrder {
public static void main(String[] args) {
ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
OrderDao orderDao = (OrderDao) ctx.getBean("orderDao");
orderDao.save();
}
}
2
3
4
5
6
7
8
9
- 运行结果

# 实例工厂方式
- UserDao接口和UserDaoImpl实现类
public interface UserDao {
public void save();
}
public class UserDaoImpl implements UserDao {
public void save() {
System.out.println("user dao save ...");
}
}
2
3
4
5
6
7
8
- UserDaoFactory工厂类
//实例工厂创建对象
public class UserDaoFactory {
public UserDao getUserDao(){
return new UserDaoImpl();
}
}
2
3
4
5
6
- applicationContext.xml配置
<!--方式三:使用实例工厂实例化bean-->
<bean id="userFactory" class="com.itheima.factory.UserDaoFactory"/>
<bean id="userDao" factory-method="getUserDao" factory-bean="userFactory"/>
2
3
4

- AppForInstanceUser测试类
public class AppForInstanceUser {
public static void main(String[] args) {
// //创建实例工厂对象
// UserDaoFactory userDaoFactory = new UserDaoFactory();
// //通过实例工厂对象创建对象
// UserDao userDao = userDaoFactory.getUserDao();
// userDao.save();
ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
UserDao userDao = (UserDao) ctx.getBean("userDao");
userDao.save();
}
}
2
3
4
5
6
7
8
9
10
11
12
- 运行结果

# Bean的生命周期
- 生命周期:从创建到消亡的完整过程
- bean生命周期:bean从创建到销毁的整体过程
- bean生命周期控制:在bean创建后到销毁前做一些事情
过程
- 初始化容器
- 创建对象(内存分配)
- 执行构造方法
- 执行属性注入(set操作)
- 执行bean初始化方法
- 使用bean
- 执行业务操作
- 关闭/销毁容器
- 执行bean销毁方法
# Bean生命周期控制
- 提供生命周期控制方法
public class BookDaoImpl implements BookDao {
public void save() {
System.out.println("book dao save ...");
}
//表示bean初始化对应的操作
public void init(){
System.out.println("init...");
}
//表示bean销毁前对应的操作
public void destory(){
System.out.println("destory...");
}
}
2
3
4
5
6
7
8
9
10
11
12
13
- applicationContext.xml配置
<!--init-method:设置bean初始化生命周期回调函数,此处填写init方法名-->
<!--destroy-method:设置bean销毁生命周期回调函数,仅适用于单例对象,此处填写destory方法名-->
<bean id="bookDao" class="com.itheima.dao.impl.BookDaoImpl" init-method="init" destroy-method="destory"/>
2
3
- 测试类
public class AppForLifeCycle {
public static void main( String[] args ) {
//此处需要使用实现类类型,接口类型没有close方法
ClassPathXmlApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
BookDao bookDao = (BookDao) ctx.getBean("bookDao");
bookDao.save();
//关闭容器,执行销毁的方法
ctx.close();
}
}
2
3
4
5
6
7
8
9
10
销毁的方法只对单例对象有效,多例对象由GC回收,不由Spring管理
扩展:还可以通过实现InitializingBean, DisposableBean接口定义生命周期控制方法
public class BookServiceImpl implements BookService, InitializingBean, DisposableBean {
private BookDao bookDao;
public void setBookDao(BookDao bookDao) {
System.out.println("set .....");
this.bookDao = bookDao;
}
public void save() {
System.out.println("book service save ...");
bookDao.save();
}
public void destroy() throws Exception {
System.out.println("service destroy");
}
public void afterPropertiesSet() throws Exception {
System.out.println("service init");
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# Bean销毁时机
- 容器关闭前触发bean的销毁
- 关闭容器方式:
- 手工关闭容器
调用容器的close()操作 - 注册关闭钩子,在虚拟机退出前先关闭容器再退出虚拟机
调用容器的registerShutdownHook()操作
- 手工关闭容器
public class AppForLifeCycle {
public static void main( String[] args ) {
//此处需要使用实现类类型,接口类型没有close方法
ClassPathXmlApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
BookDao bookDao = (BookDao) ctx.getBean("bookDao");
bookDao.save();
//注册关闭钩子函数,在虚拟机退出之前回调此函数,关闭容器
ctx.registerShutdownHook();
//关闭容器
//ctx.close();
}
}
2
3
4
5
6
7
8
9
10
11
12
13
# Bean依赖注入时机
- Spring 容器启动初始化的时候(所有单例非懒加载的bean)
- 懒加载(lazy-init)的bean 第一次进行getBean的时候
ApplicationContext context = new ClassPathXmlApplicationContext("spring-beans.xml");
public ClassPathXmlApplicationContext(String[] configLocations, boolean refresh, ApplicationContext parent)throws BeansException {
super(parent);
setConfigLocations(configLocations);
if (refresh) {
// 容器初始化入口
refresh();
}
}
public void refresh() throws BeansException, IllegalStateException {
synchronized (this.startupShutdownMonitor) {
prepareRefresh();
// Prepare the bean factory for use in this context.
prepareBeanFactory(beanFactory);
// Allows post-processing of the bean factory in context subclasses.
postProcessBeanFactory(beanFactory);
// Invoke factory processors registered as beans in the context.
invokeBeanFactoryPostProcessors(beanFactory);
// Register bean processors that intercept bean creation.
registerBeanPostProcessors(beanFactory);
// Instantiate all remaining (non-lazy-init) singletons.
// 初始化所有非 懒加载的bean!!!!
finishBeanFactoryInitialization(beanFactory);
// Last step: publish corresponding event.
finishRefresh();
}
}
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
protected void finishBeanFactoryInitialization(ConfigurableListableBeanFactory beanFactory) {
// Stop using the temporary ClassLoader for type matching.
beanFactory.setTempClassLoader(null);
// 此处省略多行与本次无关代码
// Instantiate all remaining (non-lazy-init) singletons.
beanFactory.preInstantiateSingletons();
}
public void preInstantiateSingletons() throws BeansException {
// 所有beanDefinition集合
List<String> beanNames = new ArrayList<String>(this.beanDefinitionNames);
// 触发所有非懒加载单例bean的初始化
for (String beanName : beanNames) {
RootBeanDefinition bd = getMergedLocalBeanDefinition(beanName);
// 判断是否是懒加载单例bean,如果是单例的并且不是懒加载的则在Spring 容器
if (!bd.isAbstract() && bd.isSingleton() && !bd.isLazyInit()) {
// 判断是否是FactoryBean
if (isFactoryBean(beanName)) {
// 对FactoryBean的处理
}else {
// 如果是普通bean则进行初始化依赖注入,此 getBean(beanName)接下来触发的逻辑跟
// context.getBean("beanName") 所触发的逻辑是一样的
getBean(beanName);
}
}
}
}
@Override
public Object getBean(String name) throws BeansException {
return doGetBean(name, null, null, false);
}
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
**懒加载的使用:**在类上加上@Lazy注解
懒加载机制只对单例bean有作用,对于多例bean设置懒加载没有意义,因为多例bean本来就是在使用时才创建的。
# Spring配置第三方资源管理
配置DruidDataSource连接池Bean对象
Druid
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
<property name="driverClassName" value="com.mysql.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://localhost:3306/spring_db"/>
<property name="username" value="root"/>
<property name="password" value="root"/>
</bean>
2
3
4
5
6
c3p0
<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
<property name="driverClass" value="com.mysql.jdbc.Driver"/>
<property name="jdbcUrl" value="jdbc:mysql://localhost:3306/spring_db"/>
<property name="user" value="root"/>
<property name="password" value="root"/>
</bean>
2
3
4
5
6
通过加载配置文件的方式创建
创建jdbc配置文件
// 名字尽量起长一点,防止冲突
jdbc.driver=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://127.0.0.1:3306/spring_db
jdbc.username=root
jdbc.password=root
2
3
4
5
在applicationContext.xml中开启开启context命名空间,加载jdbc.properties属性文件
// classpath表示类路径,即resource目录
<context:property-placeholder location="classpath:jdbc.properties"/>
2
在配置连接池Bean的地方使用EL表达式获取jdbc.properties属性文件中的值
<bean class="com.alibaba.druid.pool.DruidDataSource" id="dataSource">
<property name="driverClassName" value="${jdbc.driver}"/>
<property name="url" value="${jdbc.url}"/>
<property name="username" value="${jdbc.username}"/>
<property name="password" value="${jdbc.password}"/>
</bean>
2
3
4
5
6
# 配置不加载系统属性
如果属性文件中配置的不是jdbc.username=root,而是username=root,那么使用${username}获取到的不是root666,而是JVM的系统属性,即计算机的名称。
原因
系统属性的优先级比我们属性文件中的高,替换了我们的username=root。
解决
解决1:换一个名称,例如不叫username,叫jdbc.username。
解决2:使用system-properties-mode="NEVER"属性表示不使用系统属性。
<context:property-placeholder location="classpath:jdbc.properties" system-properties-mode="NEVER"/>
<bean class="com.alibaba.druid.pool.DruidDataSource" id="dataSource">
<property name="driverClassName" value="${driver}"/>
<property name="url" value="${url}"/>
<property name="username" value="${username}"/>
<property name="password" value="${password}"/>
</bean>
2
3
4
5
6
7
8
# 加载properties文件写法
- 不加载系统属性,读取当前配置文件同级目录下的属性文件
<context:property-placeholder location="jdbc.properties" system-properties-mode="NEVER"/>
- 加载多个properties文件,使用逗号分隔
<context:property-placeholder location="jdbc.properties,msg.properties"/>
- 加载所有properties文件,使用通配符
<context:property-placeholder location="*.properties"/>
- 加载properties文件,在类路径下加载
<context:property-placeholder location="classpath:*.properties"/>
- 加载properties文件标准格式,加载其它依赖的jar包中的属性文件
<context:property-placeholder location="classpath*:*.properties"/>
# Spring注解开发
# 注解开发定义Bean对象
使用步骤
- 在applicationContext.xml中开启Spring注解包扫描
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:context="http://www.springframework.org/schema/context"
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
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">
<!--扫描com.itheima包及其子包下的类中注解-->
<context:component-scan base-package="com.itheima"/>
</beans>
2
3
4
5
6
7
8
9
10
- 在类上使用@Component注解定义Bean
@Component("bookDao")
public class BookDaoImpl implements BookDao {
public void save() {
System.out.println("book dao save ...");
}
}
2
3
4
5
6
补充:如果@Component注解没有使用参数指定Bean的名称,那么类名首字母小写就是Bean在IOC容器中的默认名称。例如:BookDaoImpl对象在IOC容器中的名称是bookDaoImpl。
@Component三个衍生注解
**@Controller**:用于表现层bean定义**@Service**:用于业务层bean定义@Repository:用于数据层bean定义
三者只有语义上的区别
# 纯注解开发模式
- Spring3.0开启了纯注解开发模式,使用Java类替代配置文件,开启了Spring快速开发赛道
- Java类代替Spring核心配置文件

- @Configuration注解用于设定当前类为配置类
- @ComponentScan注解用于设定扫描路径,此注解只能添加一次,多个数据请用数组格式
@ComponentScan({com.itheima.service","com.itheima.dao"})
- 读取Spring核心配置文件初始化容器对象切换为读取Java配置类初始化容器对象
//加载配置文件初始化容器
ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
//加载配置类初始化容器
ApplicationContext ctx = new AnnotationConfigApplicationContext(SpringConfig.class);
2
3
4
5
相关注解
| 注解 | 作用 |
|---|---|
| @Configuration | 声明这是一个配置类 |
| @ComponentScan | 指定要扫描的基包 |
| @Import | 导入其它的配置类 |
| @PropertySource | 读取属性配置文件 |
| @Value | 将配置文件中属性值注入给成员变量 |
| @Bean | 1.把方法的返回值放到容器中 2.可以指定名字,如果没有指定名字,方法名就是id 3.如果方法有参数,通过类型匹配的方式从容器中去找 |
示例
// 声明当前类为Spring配置类
@Configuration
// Spring注解扫描,相当于<context:component-scan base-package="com.itheima"/>
@ComponentScan(value = "com.itheima")
// 设置bean扫描路径,多个路径书写为字符串数组格式
// @ComponentScan({"com.itheima.service","com.itheima.dao"})
// @PropertySource加载properties配置文件
@PropertySource({"classpath:jdbc.properties"}) //{}可以省略不写
public class SpringConfig {
}
2
3
4
5
6
7
8
9
10
11
12
13
@Test
public void testPureAnnotation() {
// AnnotationConfigApplicationContext加载Spring配置类初始化Spring容器
ApplicationContext ctx = new AnnotationConfigApplicationContext(SpringConfig.class);
BookDao bookDao1 = (BookDao) ctx.getBean("bookDao");
System.out.println(bookDao1);
// 按类型获取bean
BookDao bookDao2 = ctx.getBean(BookDao.class);
System.out.println(bookDao2);
}
2
3
4
5
6
7
8
9
10
11
12
# 注解开发Bean作用范围和生命周期管理
| 对象范围与生命周期 | 说明 |
|---|---|
| @Scope | 对象类型,单例或多例 |
| @PostConstruct | 初始化方法 |
| @PreDestroy | 初始化方法 |
# bean作用范围注解配置
- 使用@Scope定义bean作用范围
@Component
@Scope("singleton")
public class BookUtils {
}
2
3
4
# bean生命周期注解配置
- 使用@PostConstruct、@PreDestroy定义bean生命周期
@Component
@Scope("singleton")
public class BookUtils {
public BookUtils() {
System.out.println("book constructor ...");
}
@PostConstruct
public void init(){
System.out.println("book init ...");
}
@PreDestroy
public void destroy(){
System.out.println("book destory ...");
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
注意:@PostConstruct和@PreDestroy注解是jdk中提供的注解,从jdk9开始,jdk中的javax.annotation包被移除了,也就是说这两个注解就用不了了,可以额外导入一下依赖解决这个问题。
<dependency>
<groupId>javax.annotation</groupId>
<artifactId>javax.annotation-api</artifactId>
<version>1.3.2</version>
</dependency>
2
3
4
5
# 注解开发依赖注入
| 依赖注入的注解 | 说明 |
|---|---|
| @Autowired | 1. 按类型匹配的方式 2.如果有多个按名字 3.如果找不到抛出异常 参数:required 表示是否要忽略当前要注入的bean true:不忽略,必须注入,没有则会报错(默认) false:忽略,如果有则注入,没有则跳过,不会报错 |
| @Qualifier | 按名字匹配,必须与@Autowired一起使用 |
@Autowired注解开启自动装配模式(按类型)
示例
@Service
public class BookServiceImpl implements BookService {
//@Autowired:注入引用类型,自动装配模式,默认按类型装配
@Autowired
private BookDao bookDao;
public void save() {
System.out.println("book service save ...");
bookDao.save();
}
}
2
3
4
5
6
7
8
9
10
11
说明:不管是使用配置文件还是配置类,都必须进行对应的Spring注解包扫描才可以使用。@Autowired默认按照类型自动装配,如果IOC容器中同类的Bean有多个,那么默认按照变量名和Bean的名称匹配,建议使用@Qualifier注解指定要装配的bean名称
注意:自动装配基于反射设计创建对象并暴力反射对应属性为私有属性初始化数据,因此无需提供setter方法。
使用@Qualifier注解指定要装配的bean名称
示例
@Service
public class BookServiceImpl implements BookService {
//@Autowired:注入引用类型,自动装配模式,默认按类型装配
@Autowired
//@Qualifier:自动装配bean时按bean名称装配
@Qualifier("bookDao")
private BookDao bookDao;
public void save() {
System.out.println("book service save ...");
bookDao.save();
}
}
2
3
4
5
6
7
8
9
10
11
12
13
注意:@Qualifier注解无法单独使用,必须配合@Autowired注解使用
使用@Value实现简单类型注入
示例
@Repository("bookDao")
public class BookDaoImpl implements BookDao {
//@Value:注入简单类型(无需提供set方法)
@Value("${name}")
private String name;
public void save() {
System.out.println("book dao save ..." + name);
}
}
2
3
4
5
6
7
8
9
10
以上@Value注解中使用${name}从属性文件中读取name值,那么就需要在配置类或者配置文件中加载属性文件。
@Configuration
@ComponentScan("com.itheima")
//@PropertySource加载properties配置文件
@PropertySource({"classpath:jdbc.properties"}) //{}可以省略不写
public class SpringConfig {
}
2
3
4
5
6
注意:@PropertySource中加载多文件请使用数组格式配置,不允许使用通配符
*
# 注解开发管理第三方Bean
使用步骤
- 单独定义配置类
public class JdbcConfig {
//@Bean:表示当前方法的返回值是一个bean对象,添加到IOC容器中
@Bean
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;
}
}
2
3
4
5
6
7
8
9
10
11
12
- 将独立的配置类加入核心配置
- @import注解导入式
@Configuration
@ComponentScan("com.itheima")
//@Import:导入配置信息
@Import({JdbcConfig.class})
public class SpringConfig {
}
2
3
4
5
6
- @ComponentScan扫描式
@Configuration
// 只要com.itheima.config包扫到了就行,三个包可以合并写成com.itheima
@ComponentScan({"com.itheima.config","com.itheima.service","com.itheima.dao"})
public class SpringConfig {
}
2
3
4
5
通过依赖注入注入资源
简单类型依赖注入
public class JdbcConfig {
//1.定义一个方法获得要管理的对象
@Value("com.mysql.jdbc.Driver")
private String driver;
@Value("jdbc:mysql://localhost:3306/spring_db")
private String url;
@Value("root")
private String userName;
@Value("root")
private String password;
//2.@Bean:表示当前方法的返回值是一个bean对象,添加到IOC容器中
@Bean
public DataSource dataSource(){
DruidDataSource ds = new DruidDataSource();
ds.setDriverClassName(driver);
ds.setUrl(url);
ds.setUsername(userName);
ds.setPassword(password);
return ds;
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
说明:如果@Value()中使用了EL表达式读取properties属性文件中的内容,那么就需要加载properties属性文件。
引用类型依赖注入
//Spring会自动从IOC容器中找到BookDao对象赋值给参数bookDao变量,如果没有就会报错。
@Bean
public DataSource dataSource(BookDao bookDao){
System.out.println(bookDao);
DruidDataSource ds = new DruidDataSource();
ds.setDriverClassName(driver);
ds.setUrl(url);
ds.setUsername(userName);
ds.setPassword(password);
return ds;
}
2
3
4
5
6
7
8
9
10
11
# Spring整合Mybatis
MyBatis程序核心对象分析
整合Mybatis
使用XML配置
<!--加载第三方配置文件-->
<context:property-placeholder location="classpath:jdbc.properties"/>
<!--开启注解包扫描-->
<context:component-scan base-package="com.example"/>
<!--开启AOP注解自动代理-->
<aop:aspectj-autoproxy/>
<!--配置JDBC驱动,初始化DataSource-->
<bean class="com.alibaba.druid.pool.DruidDataSource" id="dataSource">
<property name="username" value="${jdbc.username}"/>
<property name="password" value="${jdbc.password}"/>
<property name="url" value="${jdbc.url}"/>
<property name="driverClassName" value="${jdbc.driver}"/>
</bean>
<!--mybatis实体类包扫描,初始化类别名-->
<bean class="org.mybatis.spring.SqlSessionFactoryBean" id="sessionFactoryBean">
<property name="dataSource" ref="dataSource"/>
<property name="typeAliasesPackage" value="com.example.pojo"/>
<property name="configuration">
<bean class="org.apache.ibatis.session.Configuration">
<!--驼峰映射-->
<property name="mapUnderscoreToCamelCase" value="true"/>
<!--打印日志-->
<property name="logImpl" value="org.apache.ibatis.logging.stdout.StdOutImpl"/>
</bean>
</property>
</bean>
<!--mybatis映射接口包扫描,初始化映射配置-->
<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer" id="mapperScannerConfigurer">
<property name="basePackage" value="com.example.mapper"/>
</bean>
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
简单说这样就相当于配置了mybatis的核心配置文件,并且使用会话工厂生成了mapper代理对象并放入了Spring容器之中,使用时只需从容器中获取mapper代理对象调用方法即可
使用注解配置

示例
import org.mybatis.spring.SqlSessionFactoryBean;
import org.mybatis.spring.mapper.MapperScannerConfigurer;
import org.springframework.context.annotation.Bean;
import javax.sql.DataSource;
// 声明这是一个配置类
@Configuration
public class MybatisConfig {
//定义bean,SqlSessionFactoryBean,用于产生SqlSessionFactory对象
@Bean
public SqlSessionFactoryBean sqlSessionFactory(DataSource dataSource){
SqlSessionFactoryBean factoryBean = new SqlSessionFactoryBean();
//指定实体类的基包,定义别名
factoryBean.setTypeAliasesPackage("com.itheima.domain");
//配置数据源
factoryBean.setDataSource(dataSource);
return factoryBean;
}
//定义bean,返回MapperScannerConfigurer对象
@Bean
public MapperScannerConfigurer mapperScannerConfigurer(){
MapperScannerConfigurer mapperScanner = new MapperScannerConfigurer();
//配置dao接口所在的包
mapperScanner.setBasePackage("com.itheima.dao");
return mapperScanner;
}
}
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
依赖jar包
<properties>
<!-- 定义spring的版本号 -->
<spring.version>5.2.10.RELEASE</spring.version>
</properties>
<dependencies>
<!-- 导入阿里巴巴druid连接池(数据源) -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.1.23</version>
</dependency>
<!-- 导入spring的包 -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>${spring.version}</version>
</dependency>
<!-- 数据库mysql驱动 -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.46</version>
<scope>runtime</scope>
</dependency>
<!-- junit4 -->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.13</version>
<scope>test</scope>
</dependency>
<!-- mybatis的包 -->
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.5.0</version>
</dependency>
<!-- mybatis整合spring -->
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis-spring</artifactId>
<version>2.0.4</version>
</dependency>
<!-- spring-jdbc包 -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>${spring.version}</version>
</dependency>
</dependencies>
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
57
58
59
# Spring整合Junit单元测试
只有使用Spring容器创建的Bean对象,才能做到使用@Autowired自动依赖注入,因为只有当我们创建容器时,容器才会扫描容器中所有的Bean对象进行依赖注入,也就是初始化。而由我们主动new的对象,是不会识别@Autowired注解进行依赖注入的,自然也就没有初始化,所以在我们主动new的对象中使用被@Autowired标识的属性会出现空指针异常。并且一个在容器中的类不能在容器中获取自己,会报错。
// 将Demo放入容器
@Component
public class Demo {
@Autowired
private static UserService userService;
@PostConstruct
public void init(){
System.out.println("Demo被初始化...");
}
/*
* 此时初始化方法被执行,说明Demo类已经在容器之中
* 但是为什么userService获取到的是null?
* 因为此时打印的userService是this的userService
* 此时的this是普通创建的Demo对象,相当于new出来的
* 而不是从容器中获取的由Spring容器创建的Demo Bean对象
* 所以这里的userService是还未被初始化的
* */
public static void main(String[] args) {
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
System.out.println(userService);
// Demo demo = context.getBean("Demo", Demo.class); NoSuchBeanDefinitionException
}
}
---------------------------------
运行结果:
Demo被初始化...
null
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
Spring整合了Junit单元测试,Junit提供了一个第三方运行器的接口,而Spring提供了自己的运行器,使得让整个类交给Spring来管理,即使用Spring来运行该类,也就是此时容器已经创建,那么这个类中的属性依赖,自然够从Spring容器中获取了。
示例
@ExtendWith(SpringExtension.class) //junit5指定第三方的运行器
//@Runwith(SpringJUnit4ClassRunner.class) //junit4的写法
@ContextConfiguration(classes = SpringConfig.class) //读取类配置文件
//@ContextConfiguration("classpath:applicationContext.xml") //读取xml配置文件
public class MybatisSpringTest {
@Autowired
private AccountService accountService;
@Test
public void testFindById() {
//调用接口中方法
Account account = accountService.findById(1);
System.out.println(account);
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
依赖jar包
<!-- junit4 -->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.13</version>
<scope>test</scope>
</dependency>
<!-- spring整合junit5 -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>5.2.10.RELEASE</version>
<scope>test</scope>
</dependency>
2
3
4
5
6
7
8
9
10
11
12
13
14
15