Spring Framework

Java开发要用的Spring家族的基础就是SpringFramework、SpringBoot、SpringCloud。这里笔记记录的就是SpringFramework技术,学习视频是黑马的SSM快速入门Spring-01-初识Spring_哔哩哔哩_bilibili

要注意的是,本内容仅是对Spring框架应用的学习以及架构的认识,更加深入的内容要在Spring源码的部分进一步学习。

Spring系统架构

image-20250614224253416

IoC(Inversion of Control)控制翻转

使用对象时,由主动new产生对象转换为由外部提供对象,此过程中对象的创建控制权由程序转移到外部,这个思想就是控制翻转

IoC、Bean和DI:

  • Spring提供了一个容器,称为IoC容器,用来充当Ioc思想中的“外部”

  • IoC容器负责对象的创建、初始化的一系列工作,被创建或管理的对象在IoC容器中统称为Bean

  • DI(依赖注入)在容器中建立起bean与bean之间的关系

我们这样做的目的是充分解耦,具体做法可以概括如下:

  • 使用IoC容器管理bean(IoC)
  • 在IoC容器中将有依赖关系的bean进行关系绑定(DI)

bean管理

bean作用范围

bean的作用范围对应着scope属性,主要就是控制其是否为单例。

为什么bean默认为单例?

  • IoC容器就适合管理单例的对象,这样才能有效地提高效率。

适合交给容器管理的bean

  • 表现层对象
  • 业务层对象
  • 数据层对象
  • 工具对象

不适合交给容器管理的bean

  • 封装实体的域对象、

bean的实例化

  1. 构造方法(常用)

提供可访问的构造方法

配置

  • 无参构造器如果不存在,会抛出BeanCreationException
  1. 静态工厂(了解)
  2. 实例工厂(了解)
  3. 使用FactoryBean实例化,其中有三个方法,用来得到Bean实例、设定Bean类型、设定是否为单例(实用)

bean的生命周期

  • bean生命周期:bean从创建到销毁的整体过程
  • bean生命周期控制:在bean创建后到销毁前做的一些事情

生命周期控制

  • 配置的方式
  • 接口控制

bean的生命周期

初始化容器

  1. 创建对象(内存分配)
  2. 执行构造方法
  3. 执行属性注入(set操作)
  4. 执行bean初始化方法

使用bean

  1. 执行业务操作

关闭/销毁容器

  1. 执行bean销毁方法

bean销毁

容器关闭前触发bean的销毁

关闭容器方式:

  • 手动关闭容器,ConfigurableApplicationContext接口close()操作
  • 注册关闭钩子,在虚拟机退出前先关闭容器再退出虚拟机,ConfigurableApplicationContext接口registerShutdownHook()操作

总结

bean相关标签属性

1
2
3
4
5
6
7
8
9
10
11
12
<bean
id="bookDao"
name="dao bookDaoImpl daoImpl"
class="com.itheima.dao.impl.BookDaoImpl"
scope="singleton"
init-method="init"
destroy-method="destory"
autowire="byType"
factory-method="getInstance"
factory-bean="com.itheima.factory.BookDaoFactory"
lazy-init="true"
/>
  • id: bean的Id
  • name: bean别名
  • class: bean类型,静态工厂类,FactoryBean类
  • scope: 控制bean的实例数量
  • init-method: 生命周期初始化方法
  • destroy-method: 生命周期销毁方法
  • autowire: 自动装配类型,实际开发一般用注解,该属性一般不用
  • factory-method: bean工厂方法,应用于静态工厂或实例工厂
  • factory-bean: 实例工厂bean
  • lazy-init: 控制bean延迟加载

依赖注入

手动装配

  • setter注入

引用类型使用ref配置

简单类型:使用property标签value属性进行配置

1
2
<property name="bookDao" ref="bookDao"/>
<property name="msg" value="WARN"/>
  • 构造器注入

使用constructor-arg标签ref属性注入引用类型对象

依赖注入方式选择

如果使用第三方bean,如果其同时提供了构造器注入和setter注入,使用setter注入。没有setter注入就只能使用构造器注入。自己开发推荐使用setter注入。

自动装配

自动装配方式:

  1. 按类型
  2. 按名称
  3. 构造器
  4. 不使用自动装配

通过属性autowire进行自动装配的配置

  • 自动装配用于引用类型依赖注入,不能对简单类型进行操作
  • 使用按类型装配时(byType)必须保障容器中相同类型的bean唯一
  • 使用按名称装配时(byName)必须保障容器中具有指定名称的bean,因变量名与配置耦合,不推荐使用
  • 自动装配优先级低于手动注入

集合注入

数组、list、set、map、properties

格式

1
2
3
4
<list>
<value>itcast</value>
<ref bean="dataSource"/>
</list>

容器

创建容器

方式1,使用ClassPathXmlApplicationContext

方式2,使用FileSystemXmlApplicationContext

获取bean

方式1,按名称

方式2,按类型

方式3,按名称并按类型

容器类层次结构

image-20250618151923608

总结

  • BeanFactory是IoC容器的顶层接口,初始化BeanFactory对象时,加载的bean延迟加载
  • ApplicationContext接口是Spring容器的核心接口,初始化时bean立即加载
  • ApplicationContext接口提供基础的bean操作相关方法,通过其他接口扩展其功能
  • ApplicationContext接口初始化类
    • ClassPathXmlApplicationContext
    • FileSystemXmlApplicationContext

加载propertis文件

开启命名空间的方式

1.开context命名空间

2.使用context空间加载propertie文件

3.使用属性占位符${}读取properties文件中的属性

加载propertis文件几种方式

  • 不加载系统属性

    1
    <context:property-placeholder location="classpath*:*.properties" system-properties-mode="NEVER"></pre>
  • 加载多个properties文件

  • 加载所有properties文件

  • 加载properties文件标准格式

1
<context:property-placeholder location="classpath:*.properties"
  • 从类路径或jar包中搜索并加载properties文件
1
<context:property-placeholder location="classpathE*:*.properties"

前三种格式都不规范,推荐使用后两种

注解开发

注解开发定义bean

使用注解的形式代替原来在配置文件中写的<bean>标签。具体方法是:在相应bean的类那里使用@Component注解。

为了能让Spring容器找到这个组件,还要在配置中添加扫描标签:

1
<context:component-scan base-package="com.itheima.dao.impl"/>

同时@Component还有三个衍生注解@Controller、@Service、@Repository

纯注解开发

Spring3.0升级了纯注解开发模式

创建一个配置类,添加@Configuration、@ComponentScan注解,就可以完全代替配置文件

相对应的,应用程序的使用也要改变,需要使用AnnotationConfigApplicationContext的实现类创建容器,其相应参数传入我们的配置类。

总之,核心思想就是通过一个类及其注解代替配置文件

bean管理

bean作用范围

@Scope

bean生命周期

@PostConstruct

@PreDestroy

依赖注入

引用类型

自动装配@Autowired按照类型自动装配。

  • 注意:自动装配基于反射设计创建对象并暴力反射对应属性为私有属性初始化数据,因此无需提供setter方法
  • 注意:自动装配建议使用无参构造方法(默认),如果不提供对应构造方法,请提供唯一的构造方法

关于有多个同类型的类的情况下如何解决,在SpringBoot的依赖注入部分我已经总结过SpringBoot入门 | KuoZ’s blog

简单类型

使用@Value()进行值的注入,这样直接注入是静态的,实际上没什么意义,关键是如何动态注入。

如何动态进行注入?

方法:

  1. 在properties配置文件中写相应的属性名及其值

  2. 在配置类中添加@PropertySource(“配置文件名”)注解

  3. 然后在基本类型的@Value注解中用${属性名}进行替换

注意:要添加多个配置文件,要用数组的形式进行配置,@PropertySource({“配置文件名1”,“配置文件名2”,…})

第三方bean管理

1
2
3
4
5
6
7
8
9
@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;
}

这里有一个问题,这个bean实在配置类中写的,可能会有很多第三方bean,如果都写在SpringConfig配置类中就会很臃肿,所以要写新的配置类。接下来的问题是如何将这个新的配置类加载起来,方法是在SpringConfig配置类中使用@Import()注解,同样的,有多个配置类时,要用数组的形式写如@Import的属性。

第三方bean依赖注入

  • 引用类型:方法形参
  • 简单类型:成员变量,使用@Value进行注入即可

总结

XML和注解配置的对比

功能 XML配置 注解
定义bean <bean>标签 @Component
- id属性 - @Controller
- class属性 - @Service
- @Repository
@ComponentScan
设置依赖注入 setter注入(set方法) @Autowired
- 引用/简单类型 - @Qualifier
构造器注入(构造方法) @Value
- 引用/简单类型
自动装配
配置第三方bean <bean>标签 @Bean
静态工厂、实例工厂、FactoryBean
作用范围 scope属性 @Scope
生命周期 标准接口 @PostConstruct
- init-method @PreDestroy
- destroy-method

整合

Spring整合MyBatis

如何整合MyBatis?

  • 通过MyBatis的实现代码可以看出,MyBatis的核心实现类是SqlSessionFactory,所以整合的核心也是sqlSessionFactory对象

简略地说,只有两个bean需要配置

  • SqlSessionFactoryBean
  • MapperScannerConfigurer

Spring整合JUnit

在pom中导入JUnit依赖和Spring整合测试依赖

在test包下创建测试类,为其添加注解

  • @RunWith(SpringJUnit4ClassRunner.class)
  • @ContextConfiguration(classes = SpringConfig.class)

AOP

在介绍AOP之前,我想先来介绍一下代理模式,这样可以利于理解AOP

代理模式

动态代理

什么是动态代理?

就是一种无侵入式地修改代码、添加功能

这个功能的实现就依赖于代理对象。

什么是代理对象?

代理对象就相当于是一个中间对象,它介于目标对象和调用者之间,通过反射的方式间接调用目标对象的方法,同时还可以添加增强的功能。

下一个问题,如何创建代理对象?

jdk中为我们提供了一个类:java.lang.reflect.Proxy类,提供了为对象产生代理对象的方法:newProxyInstance()

该方法的参数

1
2
3
4
5
6
public static Object new ProxyInstance(ClassLoader loader,Class<?>[] interfaces,InvocationHandler h);
/**
*参数1:用于指定用哪个类加载器去加载生成的代理类,一般固定
*参数2:指定接口,指明有哪些方法
*参数3:生成的代理对象要干什么事(一般是匿名实现类的匿名对象,不懂的可以去看面向对象高级里的接口)
*/

AOP

  • AOP(Aspect Oriented Programming)面向切面编程,一种编程范式,指导开发者如何组织程序结构

  • 作用:在不惊动原始设计的基础上为其进行功能增强

AOP的重要概念

连接点(JoinPoint):程序执行过程中的任意位置,粒度为执行方法

  • 在SpringAOP中,理解为方法的执行

切入点(Pointcut):匹配连接点的式子

  • 在SpringAOP中,一个切入点可以只描述一个具体方法,也可以匹配多个方法

    • 一个具体方法:

    • 匹配多个方法:

通知(Advice):在切入点处执行的操作,也就是共性功能

  • 在SpringAOP中,功能最终以方法的形式呈现

通知类:定义通知的类

切面(Aspect):描述通知与切入点的对应关系

AOP开发

比如在服务层的若干连接点,在这些方法前执行一个共性方法

  1. 导入aspect依赖、spring-aop依赖(依赖于Context依赖,导入Context后会自动导入)
  2. 定义dao接口和实现类
  3. 定义通知类,为这个类添加@Component、@Aspect
  4. 定义切入点,写一个私有空方法(比如private void pt(){}),在上边添加@Pointcut(“execution()”)绑定切入点,参数是切入点的返回类型+包名.类名.方法名
  5. 绑定切入点与通知关系,@Before(“pt()”)
  6. 为配置类添加@EnableAspectJAutoProxy

AOP工作流程

工作流程:

  1. Spring容器启动
  2. 读取所有切面配置的切入点(注意一定是使用了的切入点才会被读取)
  3. 初始化bean,判定bean对应的类中的方法是否匹配到了任意切入点
    • 匹配失败,创建对象
    • 匹配成功,创建目标对象的代理对象
  4. 获取bean的执行方法
    • 获取bean,调用方法并执行,完成操作
    • 根据代理对象的运行模式运行原始方法与增强的内容,完成操作

AOP的本质就是代理模式

AOP切入点表达式

切入点表达式和切入点是完全不同的概念

语法格式:

没什么好讲的,大概给个标准格式吧,稍微看一点就会了

动作关键字(权限修饰符(可省略) 返回值类型 包名.类/接口名.方法名(形参列表)异常名)

通配符:

通配符 说明
* 可以独立出现,也可以作为前缀或者后缀(如find*)(用在方法参数中一个只表示一个任意参数)
多个连续的任意符号,可以独立出现,常用于简化包名与参数的书写
+ 专用于匹配子类类型

下面给几个例子,来体验一下切入点表达式的书写

书写技巧:

  1. 按照编程的命名规范编程,否则技巧全部无用
  2. 通常描述切入点时描述到接口而不写实现类
  3. 一般省略修饰符
  4. 对于返回值,增删改类使用精准类型加速匹配,查询类使用*通配符快速描述
  5. 包名尽量不用…匹配,效率太低,常用*作为单个包匹配,或者精准匹配
  6. 接口名采用*匹配,如:*Service,绑定业务层接口名
  7. 方法名动词精准匹配,名词用*,如getBy*
  8. 参数规则较复杂,据业务方法灵活调整
  9. 不使用异常方法做匹配规则

AOP通知类型

五种类型:

  • 前置通知@Before
  • 后置通知@After
  • 环绕通知@Around(重点)
  • @AfterReturning(不常用)
  • @AfterThrowing(不常用)

环绕通知的使用:

1
2
3
4
5
6
7
@Around(pt)
public Object around(ProceedingJoinPoint pjp) throws Throwable{
System.out.println("切入点前执行...");
Object ret = pjp.proceed;
System.out.println("切入点后执行...");
return ret;
}

关于@Around的通知类型的注意:

  • 通知中未使用ProceedingJoinPoint对原始方法进行调用,则会跳过原始方法
  • 对原始方法的调用可以不接收返回值,通知方法返回值类型设为void即可,如果接收,则必须设定为Object类型
  • 原始方法的返回值如果是void类型,通知方法的返回值类型可以设定为void,也可以设定成Object
  • 由于无法预知原始方法是否会抛出异常,所以通知方法要抛出异常

AOP通知获取数据

获取参数

环绕类型直接使用ProceedingJoinPoint参数获取参数

其他类型使用JoinPoint参数获取参数

获取返回值

使用returning注解属性获取返回值

获取异常

使用throwing注解属性获取返回值

Spring事务属性

Spring事务,在数据层、业务层保障一系列数据库操作同成功同失败

Spring事务的添加

  1. 在业务层接口上加@Transactional注解
  2. 在配置中添加事务管理器
  3. 开启事务控制@EnableTransactionManagement

Spring事务角色

Spring事务会让数据层接口的事务加入Spring事务,从而保证一致性

有两个角色

事务管理员:发起事务方,在Spring中代指业务层开启事务的方法

事务协调员:加入事务方,在Spring中代指数据层方法也可以是业务层方法

Spring事务属性

事务相关配置

在@Transactionnal注解属性中可以更改readonly、timeout等属性,最重要的还是rollbackFor属性

为什么需要rollbackFor属性?

因为默认情况下只有遇到Error、运行时异常,事务才会回滚。如果想在其他异常时也同样回滚,就需要设置rollbackFor属性。

例如@Transactional(rollbackFor=IOException.class)

日志模块

日志模块要求无论转账成功与否,都要在数据库中添加记录

这就涉及到事务的传播行为,来设定事务的处理态度

通过@Transactional注解的Propagation设定

总共有如下取值

Propagation 事务管理员 事务协调员
REQUIRED 开启T 加入T
新建T2
REQUIRES_NEW 开启T 新建T2
新建T2
SUPPORTS 开启T 加入T
NOT_SUPPORTED 开启T
MANDATORY 开启T 加入T
ERROR
NEVER 开启T ERROR

还有一个是NESTED,设置savePoint,一旦事务回滚,事务将回滚到savaPoint,交由客户响应提交/回滚