文章目录
- Spring IoC / DI
- Spring IoC / DI 核心概念
- Spring 组件管理概念
- Spring IoC / DI 概念
- Spring Ioc 容器具体接口和实现类
- Spring Ioc 的管理方式
- 基于 XML 方式管理 Bean
- Spring IoC/ / DI 实现步骤
- 第一步:导入依赖配置元数据
- 第二步:实例化 IoC 容器
- 第三步:获取 Bean(组件)
- 配置 IoC
- 基于无参构造函数配置 IoC
- 基于静态工厂方法实例配置 IoC
- 基于实例工厂方法配置 IoC
- IoC 容器中的 BeanName 问题
- 依赖注入 DI
- 基于构造函数的依赖注入(单个构造参数)
- 基于构造函数的依赖注入(多个构造参数)
- 基于 Setter 方法依赖注入
- 组件(bean)作用域和周期方法配置
- 组件(bean)周期方法配置
- 组件(Bean)作用域配置(单例 or 多例)
- FactoryBean 特性和使用
- 特性
- 使用
- 注意点
- 基于注解方式管理 Bean
- Bean 注解标记和扫描(IoC)
- 概述
- 使用
- 第一步:导入依赖准备组件
- 第二步:配置元数据确定扫描范围
- 组件 BeanName 问题
- 依赖注入:引用类型自动装配 (DI)
- 自动装配实现
- 第一步:准备 Bean 组件
- 第二步:配置元数据扫描包
- 第三步:加 @Autowired 注解注入
- @Autowired 注解标记位置
- 所需类型匹配 Bean 不止一个的解决办法
- 装配 Bean 工作流程
- @Qualifier 注解
- @ JSR-250 @Resource 注解
- 依赖注入:基本类型属性赋值(DI)
- 第一步:编写外部配置文件
- 第二步:xml 引入外部配置文件
- 第三步:@Value 注解注入
- 基于配置类方式管理 Bean
- 完全注解开发理解
- 配置类方式 IoC
- 基本使用
- 第一步:准备组件
- 第二步:声明配置类扫描组件
- 第三步:获取 Bean
- 依赖注入:基本类型属性赋值(DI)
- 第一步:准备配置类和组件
- 第二步:使用 @PropertySource 注解
- 第三方 jar 无法使用注解加入 IoC容器 解决方案
- 使用 @Bean 定义组件
- @Bean 细节
- @Bean 的 BeanName问题
- @Bean 初始化和销毁方法指定
- @Bean 作用域(单例还是多例)
- @Bean 互相注入
- @Import 注解
- Spring 三种配置方式总结
- XML 方式配置总结
- XML + 注解方式配置总结
- 完全注解方式配置总结
- 整合 Spring5-Test5 搭建测试环境
- 优点
- 使用第一步:导入依赖
- 使用第二步:整合测试注解使用
- Spring AoP 面向切面编程
- 动态代理技术
- Spring AoP
- AoP 概述
- AOP 核心概念
- AoP 通知类型
- AoP 通知顺序
- AoP 切点表达式 @execution
- AoP 切点表达式重用
- 第一种:创建存储切点的类维护
- 第二种:当前类中提取表达式
- AoP 基本使用
- 底层技术组成
- 默认代理方式演示(JDK原生)
- 第一步:导入依赖
- 第二步:准备接口和实现类
- 第三步:声明切面类 @Aspect
- 第四步:用配置类开启 aspectj 注解支持 @EnableAspectJAutoProxy
- 环绕通知
- JoinPoint 详解
- CGLib 动态代理生效情况
- Spring AoP 对获取 Bean 的影响
- JDK 原生代理
- CGLib 代理
- Spring TX 声明式事务
- 声明式事务概念
- 声明式事务基本使用
- 第一步:加入依赖
- 第二步:在配置类上加上 @EnableTransactionManagement 注解开启事务管理
- 第三步:在配置类里配置事务管理器
- 第四步:给需要事务的地方加上 @Transactional 注解
- 事务的属性
- 事务属性:只读
- 事务属性:超时
- 事务属性:回滚
- 事务属性:事务隔离级别
- 事务属性:事务传播行为
Spring IoC / DI
Spring IoC / DI 核心概念
Spring 组件管理概念
Spring 框架可管理组件,替代程序员的 new 对象及属性赋值操作,其管理动作包括组件对象实例化、属性赋值、对象间引用及存活周期管理等。我们通过编写元数据(配置文件)指明需管理的类组件及其关系。组件是应用中可重用的 Java 对象,组件必是对象,对象未必是组件。总之,Spring 作为组件容器,创建、管理和存储组件,减轻编码压力,使我们专注业务编写。
Spring IoC / DI 概念
IoC容器
Spring IoC 容器作为核心组件管理容器,承担着实例化、配置以及组装 bean(组件)的职责。它依据所读取的配置元数据,获取对组件进行实例化、配置和组装的具体指令,从而实现对组件的有效管理和协调,以满足应用程序的运行需求。简单来说就是需要 DI 注入 的类就放进容器里面IoC(Inversion of Control)控制反转
IoC 主要针对对象创建与调用的控制权,当应用程序需用某对象时,不再自行创建,改由 IoC 容器负责,从而使控制权从应用程序转至 IoC 容器,实现 “反转”。其多通过依赖查找实现,即 IoC 容器负责维护并创建应用程序所需对象。DI (Dependency Injection) 依赖注入
DI 是在组件间传递依赖时,于容器内处理依赖关系,避免应用代码硬编码依赖,实现解耦。Spring 中,DI 可通过 XML 配置或注解达成,有构造函数、Setter 方法和接口三种注入形式。比如说:注解方式。直接给个注解它就实例化了对象。
Spring Ioc 容器具体接口和实现类
Spring Ioc 容器接口
BeanFactory
是 Spring IoC 容器标准化超接口,提供高级配置机制管理对象。能够管理任何类型的对象。ApplicationContext
是其子接口,扩展了与 AOP 集成、消息资源处理及特定应用(如 WebApplicationContext)相关功能。总之,BeanFactory
有基础配置和功能,ApplicationContext
增添企业级功能,是完整超集。
ApplicationContext 容器实现类
类型名 | 简介 |
---|---|
ClassPathXmlApplicationContext | 通过读取类路径下的 XML 格式的配置文件创建 IOC 容器对象 |
FileSystemXmlApplicationContext | 通过文件系统路径读取 XML 格式的配置文件创建 IOC 容器对象 |
AnnotationConfigApplicationContext | 通过读取Java配置类创建 IOC 容器对象 |
WebApplicationContext | 专门为 Web 应用准备,基于 Web 环境创建 IOC 容器对象,并将对象引入存入 ServletContext 域中。 |
Spring Ioc 的管理方式
Spring框架提供了多种配置方式:XML配置方式,注解方式,Java配置类方式
XML配置方式
:是Spring框架最早的配置方式之一,通过在XML文件中定义Bean及其依赖关系、Bean的作用域等信息,让Spring IoC容器来管理Bean之间的依赖关系。注解方式
:*从Spring 2.5版本开始提供支持,可以通过在Bean类上使用注解来代替XML配置文件中的配置信息。通过在Bean类上加上相应的注解(如@Component, @Service, @Autowired等),将Bean注册到Spring IoC容器中,这样Spring IoC容器就可以管理这些Bean之间的依赖关系。Java配置类方式
:*从Spring 3.0版本开始提供支持,通过Java类来定义Bean、Bean之间的依赖关系和配置信息,从而代替XML配置文件的方式。Java配置类是一种使用Java编写配置信息的方式,通过@Configuration、@Bean等注解来实现Bean和依赖关系的配置。
当前开发以配置类 + 注解方式
为主
基于 XML 方式管理 Bean
Spring IoC/ / DI 实现步骤
第一步:导入依赖配置元数据
配置元数据,既是编写交给Spring IoC容器管理组件的信息。
<dependencies>
<!--spring context依赖-->
<!--当你引入SpringContext依赖之后,表示将Spring的基础依赖引入了-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>6.0.6</version>
</dependency>
</dependencies>
<?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
https://www.springframework.org/schema/beans/spring-beans.xsd">
<!-- 定义 bean 实例 -->
<bean id="beanId1" class="fully.qualified.ClassName1">
<!-- 在此处配置该 bean 的协作对象及其他相关设置 -->
</bean>
<bean id="beanId2" class="fully.qualified.ClassName2">
<!-- 同样在此配置其协作与设置信息 -->
</bean>
<!-- 可继续添加更多 bean 定义 -->
</beans>
Spring IoC 容器管理一个或多个组件。这些 组件是使用你提供给容器的配置元数据(例如,以 XML
<bean/>
定义的形式)创建的。
<bean />
标签就相当于对组件信息进行声明。id
属性是标识单个 Bean 定义的字符串。也就是别名class
属性定义 Bean 的类型并使用完全限定的类名。
第二步:实例化 IoC 容器
提供给 ApplicationContext 构造函数的位置路径是资源字符串地址,允许容器从各种外部资源(如本地文件系统、Java CLASSPATH 等)加载配置元数据。
我们应该选择一个合适的容器实现类,进行IoC容器的实例化工作:
注意:如果是没合并状态需要手动 refresh
第一种方式
//实例化ioc容器,读取外部配置文件,最终会在容器内进行ioc和di动作 这一步就已经把 IoC 容器中的类全部创建出来了
ApplicationContext context =
new ClassPathXmlApplicationContext("services.xml", "daos.xml");
第二种方式
第一种相当于这两步合并了: 如果使用这种方式需要手动 refresh 创建 bean 对象
// 使用ClassPathXmlApplicationContext创建Spring的IOC容器,它能管理bean。
ClassPathXmlApplicationContext applicationContext =
new ClassPathXmlApplicationContext();
// 设置配置文件位置,告诉容器要加载哪个XML配置文件来创建bean等信息。
applicationContext.setConfigLocation("services.xml,daos.xml");
// 触发容器进行IOC和DI操作,让容器按配置文件初始化并准备好可用的bean对象。
applicationContext.refresh();
第三步:获取 Bean(组件)
ApplicationContext 是一个高级工厂的接口,能够维护不同 bean 及其依赖项的注册表。通过使用方法 T getBean(String name, Class requiredType) ,您可以检索 bean 的实例。
注意:获取Bean组件默认都是获取同一个,也就是单例的
第一种方式
直接根据 beanId 获取即可 返回值类型是Object 需要强转不推荐
Object happyConponent = applicationContext.getBean("happyComponent");
第二种方式
根据 beanId 同时定制 bean 的类型 Class
HappyComponent happyComponent1 = applicationContext.getBean("happyComponent", HappyComponent.class);
第三种方式
直接根据类型获取
.
条件1
:同一个类型, 在 ioc 容器中只能有一个 bean!,如果 ioC 容器中存在多个同类型的 Bean 会出现: NoUniqueBeanFinitionException
条件2
:ioc的配置一定是实现类, 但是可以通过接口类型获取触发多态
.
原理:
根据类型来获取bean时,在满足bean唯一性的前提下,其实只是看:『对象 instanceof 指定的类型』的返回结果,只要返回的是true就可以认定为和类型匹配,能够获取到。
//A 是接口 HappyComponent 是实现类
A bean = applicationContext.getBean(A.class);
bean.doWork();
图解
配置 IoC
基于无参构造函数配置 IoC
通过构造函数方法创建一个 bean(组件对象) 时,所有普通类都可以由 Spring 使用并与之兼容。也就是说,正在开发的类不需要实现任何特定的接口或以特定的方式进行编码。只需指定 Bean 类信息就足够了。但是,默认情况下,我们需要一个默认(空)构造函数。简单来说:就是一个类有个无参构造就行
- 第一步:准备 Bean 组件类 NonConstructorComponent
public class NonConstructorComponent {
public NonConstructorComponent() {
System.out.println("NonConstructorComponent work !!!");
}
}
- 第二步:配置元数据 spring-bean-01 [ 把 NonConstructorComponent 放入 IoC 容器 ]
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<!--无参构造方法实例化-->
<bean id="nonConstructorComponent" class="com.mangfu.NonConstructorComponent"/>
</beans>
bean标签
:通过配置bean标签告诉IOC容器需要创建对象的组件信息id属性
:bean的唯一标识,方便后期获取Bean!class属性
:组件类的全限定符!注意
:要求当前组件类必须包含无参数构造函数!
- 第三步:测试数据
//1. 创建 ioc 容器对象
// 使用ClassPathXmlApplicationContext创建Spring的IOC容器,它能管理bean。
ClassPathXmlApplicationContext applicationContext =
new ClassPathXmlApplicationContext("spring-bean-01.xml");
//2. 读取 ioc 容器组件
NonConstructorComponent nonConstructorComponent = applicationContext.getBean(NonConstructorComponent.class);
//3. 输出 NonConstructorComponent Work !!!
// com.mangfu.NonConstructorComponent@14ec4505
System.out.println(nonConstructorComponent);
基于静态工厂方法实例配置 IoC
可以简单理解为静态方法返回对象
- 第一步:创建 Bean 组件类 UserComponent
public class UserComponent {
}
- 第二步:创建静态工厂类 StaticGetUserComponent
public class StaticGetUserComponent {
public static UserComponent getUserComponent() {
System.out.println("UserComponent work !!!");
return new UserComponent();
}
}
- 第三步:配置元数据 spring-bean-01 [ 把 UserComponent 加入 IoC 容器 ]
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<!--无参构造方法实例化-->
<bean id="nonConstructorComponent" class="com.mangfu.NonConstructorComponent"/>
<!--静态工厂方法实例化: IOC 静态工厂模式可以理解为通过静态方法返回 bean 对象,-->
<bean id="staticGetUserComponent" class="com.mangfu.StaticGetUserComponent" rfactory-method="getUserComponent"/>
</beans>
class属性
:指定工厂类的全限定符!factory-method
: 指定静态工厂方法,注意,该方法必须是static方法。
- 第四步:测试数据
public class ComponentTest {
public static void main(String[] args) {
//1. 创建 ioc 容器对象
// 使用ClassPathXmlApplicationContext创建Spring的IOC容器,它能管理bean。
ClassPathXmlApplicationContext applicationContext =
new ClassPathXmlApplicationContext("spring-bean-01.xml");
//2. 读取 ioc 容器组件
UserComponent userComponent = applicationContext.getBean(UserComponent.class);
System.out.println(userComponent);
}
}
基于实例工厂方法配置 IoC
可以简单理解为实例方法返回对象
- 第一步:创建 Bean 组件类 AnimalComponent
public class AnimalComponent {
}
- 第二步:创建实例工厂类 NonStaticGetAnimalComponetn
public class NonStaticGetAnimalComponetn {
public AnimalComponent getAnimalComponent() {
System.out.println("AnimalComponent work !!!");
return new AnimalComponent();
}
}
- 第三步:配置元数据 spring-bean-01 [ 把 AnimalComponent 加入 IoC 容器 ]
<!--实例工厂方法实例化: IOC 实例工厂模式可以理解为通过实例方法返回 bean 对象,-->
<!--将工厂类进行 ioc 配置-->
<bean id="getAnimalComponent" class="com.mangfu.NonStaticGetAnimalComponetn"/>
<!--根据工厂对象的实例工厂方法进行实例化组件对象-->
<bean id="animalComponent" factory-bean="getAnimalComponent" factory-method="getAnimalComponent"/>
<bean id="happyComponent" class="com.mangfu.HappyComponent"/>
factory-bean属性
:指定当前容器中工厂Bean 的名称.factory-method
: 指定实例工厂方法名。注意,实例方法必须是非static的!
.
这里必须把工厂类加入 IoC 容器而 静态工厂不用。是因为静态工厂可以直接通过类名调用。而实例工厂必须先 new 出来才能用
- 第四步:测试数据
public class ComponentTest {
public static void main(String[] args) {
//1. 创建 ioc 容器对象
// 使用ClassPathXmlApplicationContext创建Spring的IOC容器,它能管理bean。
ClassPathXmlApplicationContext applicationContext =
new ClassPathXmlApplicationContext("spring-bean-01.xml");
AnimalComponent animalComponent = applicationContext.getBean(AnimalComponent.class);
System.out.println(animalComponent);
AnimalComponent animalComponent2 = applicationContext.getBean(AnimalComponent.class);
System.out.println(animalComponent2);
}
IoC 容器中的 BeanName 问题
优先查找 id
:如果定义了 id 属性,则首先使用 id 来查找Bean。其次查找 name
:如果没有定义 id 或者 id 查找失败,则会尝试使用 name 属性来查找Bean。没有 id 和 name
:按照首字母小写的类名
<bean id="myBean" class="com.example.MyClass"/>
<!-- 或者 -->
<bean name="myBean" class="com.example.MyClass"/>
<!-- 或者同时定义 id 和 name -->
<bean id="myBean" name="anotherName" class="com.example.MyClass"/>
依赖注入 DI
ref
:注入对象引用value
: 注入普通类型
基于构造函数的依赖注入(单个构造参数)
- 第一步:创建 Bean 组件 UserDao, UserService
//UserDao Bean
public class UserDao {
}
//UserService Bean 。要把 UserDao 注入 UserService
public class UserService {
private UserDao userDao;
public UserService(UserDao userDao) {
System.out.println("注入");
this.userDao = userDao;
}
public UserDao getUserDao() {
return userDao;
}
}
}
- 第二步:编写元数据 spring-bean-02
<?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">
<!--被引用的UserDao bean-->
<bean id="userDao" class="com.mangfu2.UserDao"/>
<!--需要引用 bean 的 bean-->
<bean id="userService" class="com.mangfu2.UserService">
<!--引用userDao bean-->
<constructor-arg ref="userDao"/>
</bean>
</beans>
constructor-arg标签
:可以引用构造参数 ref 引用其他bean的标识。和 value 注入普通属性
- 第三步:测试
@Test
public void test3() {
ClassPathXmlApplicationContext applicationContext =
new ClassPathXmlApplicationContext("spring-bean-02.xml");
//获取 UserService Bean
UserService userService = applicationContext.getBean(UserService.class);
//get方法获取 UserDao
UserDao userDao = userService.getUserDao();
//输出查看是否注入成功
System.out.println(userDao);
}
基于构造函数的依赖注入(多个构造参数)
- 第一步:创建 Bean 组件 UserDao UserService
public class UserDao {
}
@Data
public class UserService {
private UserDao userDao;
private int age;
private String name;
public UserService(UserDao userDao, int age, String name) {
this.userDao = userDao;
this.age = age;
this.name = name;
}
public UserService(String s) {
}
}
- 第二步: 编写元数据 spring-bean-03
方案1
构造参数的顺序填写值 value 直接赋值 ref 引用其他的 beanId。要考虑顺序
<beans>
<bean id="userDao" class="com.mangfu2.UserDao"/>
<bean id="userService" class="com.mangfu2.UserService">
<constructor-arg ref="userDao"/>
<constructor-arg value="18"/>
<constructor-arg value="mangfu"/>
</bean>
</beans>
方案2
通过构造参数的名称填写,不用考虑顺序 name = 构造参数的名词 [推荐]
<beans>
<bean id="userDao" class="com.mangfu2.UserDao"/>
<bean id="userService" class="com.mangfu2.UserService">
<constructor-arg name="userDao" ref="userDao"/>
<constructor-arg name="age" value="18"/>
<constructor-arg name="name" value="mangfu"/>
</bean>
</beans>
方案3
构造参数的参数的下标指定填写,不用考虑顺序 index = 构造参数的下标 从左到右从0开始 [推荐]
<beans>
<bean id="userDao" class="com.mangfu2.UserDao"/>
<bean id="userService" class="com.mangfu2.UserService">
<constructor-arg index = "0" ref="userDao"/>
<constructor-arg index ="1" value="18"/>
<constructor-arg index ="2" value="mangfu"/>
</bean>
</beans>
constructor-arg
标签:指定构造参数和对应的值constructor-arg
标签:name
属性指定参数名、index
属性指定参数角标、value
属性指定普通属性值,ref
属性引用其他的 beanId
- 第三步:测试数据
@Test
public void test4() {
ClassPathXmlApplicationContext applicationContext =
new ClassPathXmlApplicationContext("spring-bean-03.xml");
UserService userService = applicationContext.getBean(UserService.class);
System.out.println(userService.getUserDao());
System.out.println(userService.getAge());
System.out.println(userService.getName());
}
基于 Setter 方法依赖注入
- 第一步:创建 Bean 组件 UserDao UserService
public class MovieFinder {
}
public class SimpleMovieLister {
private MovieFinder movieFinder;
private String movieName;
public void setMovieFinder(MovieFinder movieFinder) {
this.movieFinder = movieFinder;
}
public void setMovieName(String movieName) {
this.movieName = movieName;
}
}
- 第二步:编写元数据 spring-bean-04
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="movieFinder" class="com.mangfu3.MovieFinder"/>
<bean id="simpleMovieLister" class="com.mangfu3.SimpleMovieLister">
<property name="movieFinder" ref="movieFinder"/>
<property name="movieName" value="整蛊专家"/>
</bean>
</beans>
property标签:
可以给setter方法对应的属性赋值property 标签
: name属性代表set方法标识、ref代表引用bean的标识id、value属性代表基本属性值
第三步:编写测试数据
@Test
public void test5() {
ClassPathXmlApplicationContext applicationContext =
new ClassPathXmlApplicationContext("spring-bean-04.xml");
SimpleMovieLister simpleMovieLister = applicationContext.getBean(SimpleMovieLister.class);
System.out.println(simpleMovieLister.getMovieFinder());
System.out.println(simpleMovieLister.getMovieName());
}
注意
- 依赖注入(DI)包含引用类型和基本数据类型,同时注入的方式也有多种!主流的注入方式为setter方法注入和构造函数注入,两种注入语法都需要掌握!
- 需要特别注意:引用其他bean,使用ref属性。直接注入基本类型值,使用value属性。
组件(bean)作用域和周期方法配置
组件(bean)周期方法配置
概念
我们可以在组件类中定义方法,然后当IoC容器实例化和销毁组件对象的时候进行调用!这两个方法我们称为生命周期方法!类似于
Servlet
的init
/destroy
方法,我们可以在周期方法完成初始化和释放资源等工作。
init-method
= “初始化方法名”destroy-method
= “销毁方法名”
- 第一步:准备 Bean 组件类 JavaBean
public class JavaBean {
public void init() {
System.out.println("init");
}
public void clear() {
System.out.println("destroy");
}
}
- 第二步:配置元数据 spring-bean-05
<?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">
<!--
init-method = "初始化方法名"
destroy-method = "销毁方法名"
spring ioc 容器就会在对应的时间时间节点回调对应的方法,我们可以在其中写对应多的业务就可以了
-->
<bean id="javaBean" class="com.mangfu4.JavaBean" init-method="init" destroy-method="clear"/>
</beans>
- 第三步:测试数据
@Test
public void test6() {
//执输出 init
ClassPathXmlApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring-bean-05.xml");
//关闭 ClassPathXmlApplicationContext 输出 destroy
applicationContext.close();
}
组件(Bean)作用域配置(单例 or 多例)
概念
<bean>标签
声明Bean
,只是将Bean
的信息配置给SpringIoC容器
!在IoC容器
中,这些<bean>标签
对应的信息转成Spring内部 BeanDefinition 对象
,BeanDefinition 对象
内,包含定义的信息(id,class,属性等等) ! 这意味着,BeanDefinition
与类概念一样,SpringIoC容器
可以可以根据BeanDefinition对象
反射创建多个Bean对象实例
。具体创建多少个Bean的实例对象
,由Bean
的作用域Scope属性
指定!
作用域可选值
取值 | 含义 | 创建对象的时机 | 默认值 |
---|---|---|---|
singleton | 在 IOC 容器中,这个 bean 的对象始终为单实例 | IOC 容器初始化时 | 是 |
prototype | 这个 bean 在 IOC 容器中有多个实例 | 获取 bean 时 | 否 |
- 第一步:准备 Bean 组件类 JavaBean,JavaBean2
public class JavaBean {
}
public class JavaBean2 {
}
- 第二步:编写配置文件spring-bean-06
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<!--这里默认就是单例, 也可以不写-->
<bean id="javaBean2" class="com.mangfu5.JavaBean2" scope="singleton"/>
<!--多例-->
<bean id="javaBean" class="com.mangfu5.JavaBean" scope="prototype"/>
</beans>
- 第三步:测试数据
@Test
public void test7() {
ClassPathXmlApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring-bean-06.xml");
//获取 单例的 Bean
JavaBean2 javaBean2_bean = applicationContext.getBean(JavaBean2.class);
JavaBean2 javaBean2_bean2 = applicationContext.getBean(JavaBean2.class);
System.out.println(javaBean2_bean == javaBean2_bean2); //true
//获取 多例的 Bean
JavaBean javaBean_bean = applicationContext.getBean(JavaBean.class);
JavaBean javaBean_bean2 = applicationContext.getBean(JavaBean.class);
System.out.println(javaBean_bean == javaBean_bean2); //false
applicationContext.close();
}
FactoryBean 特性和使用
特性
FactoryBean
接口是Spring IoC容器实例化逻辑的可插拔性点。用于配置复杂的Bean对象,可以将创建过程存储在FactoryBean
的getObject方法!
.
FactoryBean
使用场景
- 代理类的创建
- 第三方框架整合
- 复杂对象实例化等
.
FactoryBean
和BeanFactory
区别
FactoryBean
:Spring 里的特殊 bean,启动时创建,能在getObject()方法里按自定义逻辑产出其他 bean,初始化逻辑可按需定制,整合第三方框架常用它。BeanFactory
:Spring 基础接口,定义容器管理 bean 生命周期、加载解析配置、装配注入等基本行>为,用getBean()获取 bean 实例,可从多种源获取 bean 定义并转化为实例,还有ApplicationContext等子类拓展功能。二者区别
:FactoryBean 专注创建 bean,有灵活定制初始化功能;BeanFactory 重在管理 bean,提供基础容器和生命周期管理功能。
使用
FactoryBean<T>接口
有三个关键方法
T getObject()
:用于创建对象实例,创建的对象会被放入 IoC 容器中。boolean isSingleton()
:判断工厂创建的对象是否为单例,默认返回 true,但使用 Lombok 插件可能有影响。Class<?> getObjectType()
:返回getObject()所创建对象的类型,若事先不知则返回 null 。
第一步:准备 Bean 组件类 MyJavaBean
public class MyJavaBean {
private String name;
public void setName(String name) {
this.name = name;
}
public String getName() {
return name;
}
}
- 第二步:准备工厂类 JavaBeanFactoryBean 实现 FactoryBean 接口
ublic class JavaBeanFactoryBean implements FactoryBean<MyJavaBean> {
private String value;
public void setValue(String value) {
this.value = value;
}
public String getValue() {
return value;
}
//自定义 Bean
@Override
public MyJavaBean getObject() throws Exception {
MyJavaBean myJavaBean = new MyJavaBean();
myJavaBean.setName(value);
return myJavaBean;
}
//配置 Bean 返回类型
@Override
public Class<?> getObjectType() {
return MyJavaBean.class;
}
//true 为 单例 false 为多例
@Override
public boolean isSingleton() {
return true;
}
}
- 第三步:编写元数据 spring-bean-07
<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 就是 getObject 方法返回的对象的标识
Class 就是 factoryBean 标准化工厂类
-->
<bean id="myJavaBean" class="com.mangfu6.JavaBeanFactoryBean">、
<!--此位置的属性是注入 JavaBean 工厂类-->
<property name="value" value="莽夫"/>
</bean>
</beans>
- 第四步:测试数据
@Test
public void test8() {
ClassPathXmlApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring-bean-07.xml");
MyJavaBean myJavaBean = applicationContext.getBean(MyJavaBean.class);
System.out.println(myJavaBean.getName());
//factoryBean 工厂也会加入到 IoC 容器中名字是 &id
JavaBeanFactoryBean javaBeanFactoryBean = applicationContext.getBean(JavaBeanFactoryBean.class);
System.out.println(javaBeanFactoryBean.getValue());
applicationContext.close();
}
注意点
- 元数据中:
id
是getObject
方法返回的对象的标识Class
是JavaBean 工厂类
- JavaBean 工厂类也会加入到
IoC
容器中。名字是&id
(就是元数据中的 id 加个 &)
基于注解方式管理 Bean
Bean 注解标记和扫描(IoC)
概述
- 注解本质与作用:注解如同 XML 配置文件,自身不具备执行能力,仅作标记。其具体功能是在框架检测到标记位置后,依标记功能执行操作。本质上,所有操作皆由 Java 代码完成,XML 和注解只是指示框架中的 Java 代码执行方式。
- Spring 扫描机制:Spring 为确定程序员标记的注解位置,需通过扫描来检测,进而依据注解开展后续操作。
使用
Spring 提供多种注解用于定义 Bean:
@Component
:通用注解,标记类为 Spring Bean,可用于各层,如 Service、Dao 等,直接标注在类上即可。@Repository
:专用于 Dao 层类,功能同 @Component。@Service
:用于 Service 层类,功能同 @Component。@Controller
:用于控制层类(如 SpringMVC 的 Controller),功能同 @Component。
源码显示,@Controller、@Service、@Repository
基于@Component
起名,对Spring IOC
管理无实质区别,仅为方便开发者区分组件作用。虽本质相同,但为保证代码可读性与结构严谨性,不可随意标注。
第一步:导入依赖准备组件
<dependencies>
<!--spring context依赖-->
<!--当你引入Spring Context依赖之后,表示将Spring的基础依赖引入了-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>6.0.6</version>
</dependency>
</dependencies>
//相当于<bean id="commonComponent" class="com.atguigu.ioc_01.CommonComponent">
@Component
public class CommonComponent {}
@Controller
public class XxxController {}
@Repository
public class XxxDao {}
@Service
public class XxxService {}
第二步:配置元数据确定扫描范围
- 第一种方式:基本扫描配置
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd">
<!-- 配置自动扫描的包 -->
<!-- 1.包要精准,提高性能!
2.会扫描指定的包和子包内容
3.多个包可以使用,分割 例如: com.mangfu.controller, com.mangfu.service等
-->
<context:component-scan base-package="com.mangfu7"/>
</beans>
- 第二种方式:指定排除组件
<!-- 指定不扫描的组件 -->
<context:component-scan base-package="com.mangfu7">
<!-- context:exclude-filter标签:指定排除规则 -->
<!-- type属性:指定根据什么来进行排除,annotation取值表示根据注解来排除 -->
<!-- expression属性:指定排除规则的表达式,对于注解来说指定注解的全类名即可比如下面是排除 Controller 注解 -->
<context:exclude-filter type="annotation" expression="org.springframework.stereotype.Controller"/>
</context:component-scan>
- 第三种方式:仅扫描指定的组件
<!-- 仅扫描指定的组件 -->
<!-- 仅扫描 = 关闭默认规则 + 追加规则 -->
<!-- use-default-filters属性:取值false表示关闭默认扫描规则 -->
<context:component-scan base-package="com.mangfu7" use-default-filters="false">
<!-- context:include-filter标签:指定在原有扫描规则的基础上追加的规则。比如下面是只扫描 Controller -->
<context:include-filter type="annotation" expression="org.springframework.stereotype.Controller"/>
</context:component-scan>
组件 BeanName 问题
使用 XML 方式管理 bean 时,每个 bean 靠 id 属性值作为唯一标识,方便在别处引用。而采用注解管理组件后,组件同样需要唯一标识。默认情况下,组件对应的 bean 的 id 是其
类名首字母小写
形式,比如SoldierController
类对应的 bean 的 id 就是soldierController
如果指定了value
那别名就是value
指定的值。
//因为注解源码里面只有 value 一个属性所以 value 可以省略
@Controller(value = "tianDog")
public class SoldierController {
}
依赖注入:引用类型自动装配 (DI)
自动装配实现
前提条件
:参与自动装配的组件,无论是要被装配的还是提供装配的,都必须在 IoC 容器里,并且 IoC 容器的实现方式(XML 配置或者注解方式)对此没有影响。@Autowired 注解使用方法
:可以直接在成员变量上标记 @Autowired 注解 来自动注入对象,在实际项目中我们通常采用这种方式。甚至第三方类放入容器后,也能使用此注解来实现自动装配。原理
:1. 在ioc 容器中查找符合类型的组件对象, 2. 设置给当前属性(di)
第一步:准备 Bean 组件
@Repository
public class UserDao {
}
@Service
public class UserService {
private UserDao userDao;
public UserDao getUserDao() {
return userDao;
}
}
第二步:配置元数据扫描包
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd">
<!--
扫描包
UserService 和 UserDao 都加入 IoC 容器了
-->
<context:component-scan base-package="com.mangfu8"/>
</beans>
第三步:加 @Autowired 注解注入
@Service
public class UserService {
@Autowired
private UserDao userDao;
public UserDao getUserDao() {
return userDao;
}
}
@Autowired 注解标记位置
注意:注入和被注入的都要在 IoC 容器中
- 成员变量
@Repository
//这里 UserDao 也必须在 IoC 容器中
public class UserDao {
@Autowired
private UserService userService;
}
- 构造器
@Repository
public class UserDao {
private UserDao userDao;
@Autowired
public UserDao(UserDao userDao) {
this.userDao = userDao;
}
}
- Set 方法
@Repository
public class UserDao {
private UserDao userDao;
@Autowired
public void setUserDao(UserDao userDao) {
this.userDao = userDao;
}
}
所需类型匹配 Bean 不止一个的解决办法
装配 Bean 工作流程
在进行装配操作时,先依据所需组件类型到 IOC 容器中查找:
- 若能找到唯一的 bean,则 @Autowired 直接执行装配。
- 要是完全找不到匹配该类型的 bean,装配就会失败。
.
而当和所需类型匹配的 bean 不止一个时:
- 若没有 @Qualifier 注解,会以 @Autowired 标记位置成员变量的变量名作为 bean 的 id 来匹配,匹配成功就执行装配,匹配不到则装配失败。
- 若使用了 @Qualifier 注解,便按照 @Qualifier 注解中指定的名称作为 bean 的 id 进行匹配,能找到就执行装配,找不到则装配失败。
- 此外,还可以使用 @Resource 注解来进行相关资源的装配,其装配规则与上述情况类似,也会按照指定的名称等规则去 IOC 容器中查找匹配的 bean 来完成装配操作。
@Qualifier 注解
根据 @Qualifier 注解中指定的名称作为 bean 的id进行匹配
@Repository
public class UserDao {
private UserDao userDao;
//匹配 BeanId 为 userService 进行装配
//value 可以省略
@Qualifier(value = "userService")
public void setUserDao(UserDao userDao) {
this.userDao = userDao;
}
}
@ JSR-250 @Resource 注解
JSR 系列注解背景:
JSR(Java Specification Requests)
是 Java 平台标准化进程中的技术规范,其中的 JSR 注解按分类及语义不同分为多个系列。JSR-250
:用于在 Java EE 5 中定义支持注解,像@Resource用于标识需要注入的资源,是实现 Java EE 组件间依赖关系的方式,还有@PostConstruct(标识初始化方法)、@PreDestroy(标识销毁方法)等注解也在此规范中定义。需注意,JSR 只是规定注解及含义,具体实现由第三方框架(如 Spring)等完成。
@Resource 与 @Autowired 的区别:
所属不同:
@Resource注解
属于 JDK 扩展包,是 JSR-250 标准制定的注解类型,更具通用性。@Autowired注解
是 Spring 框架自身的
.装配方式区别
@Resource注解
默认按 Bean 名称装配,未指定 name 时,用属性名作为 name,通过 name 找不到会自动按类型装配。@Autowired注解
默认按类型装配,若要按名称装配,需配合@Qualifier注解使用。.
使用位置不同:
@Resource注解
用在 属性上、setter 方法上。@Autowired注解
用在 属性上、setter 方法上、构造方法上、构造方法参数上。
使用
@Resource注解
属于 JDK 扩展包,不在 JDK 当中,高于 JDK11 或低于 JDK8 时需要引入以下依赖:
- 如果没有指定
name
,先根据属性名查找IoC 中组件 xxxService
- 如果没有指定
name
,并且属性名没有对应的组件,会根据属性类型查找- 可以指定
name
名称查找!@Resource(name='test') == @Autowired + @Qualifier(value='test')
<dependency>
<groupId>jakarta.annotation</groupId>
<artifactId>jakarta.annotation-api</artifactId>
<version>2.1.1</version>
</dependency>
@Repository
public class UserDao {
private UserDao userDao;
/**
* 1. 如果没有指定name,先根据属性名查找IoC中组件xxxService
* 2. 如果没有指定name,并且属性名没有对应的组件,会根据属性类型查找
* 3. 可以指定name名称查找! @Resource(name='test') == @Autowired + @Qualifier(value='test')
*/
//也可以不指定 name
@Resource(name = "userDao")
public void setUserDao(UserDao userDao) {
this.userDao = userDao;
}
}
依赖注入:基本类型属性赋值(DI)
第一步:编写外部配置文件
username=root
第二步:xml 引入外部配置文件
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd">
<!-- 扫描包 -->
<context:component-scan base-package="com.mangfu8"/>
<context:property-placeholder location="application.properties"/>
</beans>
第三步:@Value 注解注入
情况1
: ${key} 取外部配置key对应的值!
情况2
: ${key:defaultValue} 没有key,可以给与默认值
@Repository
public class UserDao {
/**
* 情况1: ${key} 取外部配置key对应的值!
* 情况2: ${key:defaultValue} 没有key,可以给与默认值
*/
@Value("${userName}")
private String userName;
public String getUserName() {
return userName;
}
}
基于配置类方式管理 Bean
完全注解开发理解
Spring 完全注解配置,即使用 Java 配置类代码与注解来搭建 Spring 应用程序,取代传统的 XML 配置文件。这种方式类型更安全,可读性也更佳
配置类方式 IoC
基本使用
第一步:准备组件
注解方式加入 IoC 容器
@Component
public class MyComponent {
private String name;
public void setName(String name) {
this.name = name;
}
public String getName() {
return name;
}
}
第二步:声明配置类扫描组件
@ComponentScan
:扫描包中注解,以便加入 IoC 容器
@Configuration
:声明为配置类,它本身也会加入 IoC 容器
//包扫描: 相当于 XML 中的 <context:component-scan base-package="com.mangfu11"/>
@ComponentScan(basePackages = "com.mangfu11")
//声明为配置类
@Configuration
public class MyConfiguration {
}
第三步:获取 Bean
- 第一种方式
@Test
public void test10() {
//1. 创建 ioc 容器对象: 参数是配置类
AnnotationConfigApplicationContext annotationConfigApplicationContext =
new AnnotationConfigApplicationContext(MyJavaBean.class);
//2. 获取 bean
MyJavaBean bean = annotationConfigApplicationContext.getBean(MyJavaBean.class);
bean.setName("莽夫");
System.out.println(bean.getName());
}
- 第二种方式
@Test
public void test11() {
//第二种创建 ioc 容器的方式
AnnotationConfigApplicationContext applicationContext1 = new AnnotationConfigApplicationContext();
//外部设置配置类
applicationContext1.register(MyConfiguration.class);
//刷新后方可生效
applicationContext1.refresh(); //ioc di
MyComponent bean = applicationContext1.getBean(MyComponent.class);
bean.setName("莽夫");
System.out.println(bean.getName());
}
依赖注入:基本类型属性赋值(DI)
注入引用来行用注解方式就行,基本类型属性赋值可以改成这种
第一步:准备配置类和组件
- 配置类
//包扫描: 相当于 XML 中的 <context:component-scan base-package="com.mangfu11"/>
@ComponentScan(basePackages = "com.mangfu11")
//声明为配置类
@Configuration
public class MyConfiguration {
}
- 组件
@Component
@Data
public class MyComponent {
private String name;
private String age;
private String gender;
}
第二步:使用 @PropertySource 注解
@PropertySource
: 用于将外部的属性文件(如.properties文件)加载到 Spring 的环境中。这样,在 Spring 容器管理的组件中,就可以使用这些属性文件中的配置信息。@Value
:将外部配置文件(已由@PropertySource引入)里的具体值,注入到被 Spring 管理的组件(像带@Component的类)的字段中。比如看到@Value(“${某个属性名}”),Spring 启动时就会拿配置文件里对应属性的值,填到这个字段,实现配置与代码分离,改配置不用动代码。
@Component
//导入 properties配置文件
@PropertySource("classpath:application.properties")
@Data
public class MyComponent {
//注入
@Value("${username}")
private String name;
@Value("${age}")
private String age;
@Value("${gender}")
private String gender;
}
第三方 jar 无法使用注解加入 IoC容器 解决方案
使用 @Bean 定义组件
@Bean
:方法返回值作为 Bean 类型:在使用@Bean注解的方法中,方法的返回值类型决定了 Bean 的类型。例如,如果方法返回一个DataSource类型的对象,那么这个 Bean 在 Spring 容器中的类型就是DataSource。这个类型可以是具体的类,也可以是接口或者抽象类
- 配置文件
dataSource.driver=com.mysql.cj.jdbc.Driver;
dataSource.url=jdbc:mysql://localhost:3306/db02;
dataSource.username=root;
dataSource.password=Ting123321;
- 配置类
@Configuration
@PropertySource("classpath:application.properties")
public class MyConfiguration {
@Bean
public DruidDataSource dataSource(
@Value("${dataSource.driver}")
String driverClassName,
@Value("${dataSource.url}")
String url,
@Value("${dataSource.username}")
String username,
@Value("${dataSource.password}")
String password
) {
DruidDataSource dataSource = new DruidDataSource();
dataSource.setDriverClassName(driverClassName);
dataSource.setUrl(url);
dataSource.setUsername(username);
dataSource.setPassword(password);
return dataSource;
}
}
- 测试类
@Test
public void test13() {
AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext("com.mangfu12");
DruidDataSource bean = applicationContext.getBean(DruidDataSource.class);
System.out.println(bean.getUrl());
System.out.println(bean.getUsername());
System.out.println(bean.getPassword());
}
@Bean 细节
@Bean 的 BeanName问题
- @Bean 源码
public @interface Bean {
//前两个注解可以指定Bean的标识
//@AliasFor 是设置等价关系。这里 name 就是 value
@AliasFor("name")
String[] value() default {};
@AliasFor("value")
String[] name() default {};
//autowireCandidate 属性来指示该 Bean 是否候选用于自动装配。
//autowireCandidate 属性默认值为 true,表示该 Bean 是一个默认的装配目标,
//可被候选用于自动装配。如果将 autowireCandidate 属性设置为 false,则说明该 Bean 不是默认的装配目标,不会被候选用于自动装配。
boolean autowireCandidate() default true;
//指定初始化方法
String initMethod() default "";
//指定销毁方法
String destroyMethod() default "(inferred)";
}
- 指定 Bean 名称
@Configuration
public class MyConfig {
@Bean("mytest") //指定名称可以省略
public Test test() {
return new Test();
}
}
- @Bean 缺省情况下
Bean 名称和开头字母小写的方法名相同`
@Configuration
public class AppConfig {
@Bean
public TransferServiceImpl transferService() {
return new TransferServiceImpl();
}
}
以上配置完全等同于
<beans>
<bean id="transferService" class="com.acme.TransferServiceImpl"/>
</beans>
@Bean 初始化和销毁方法指定
@Bean
注解支持指定任意初始化和销毁回调方法,非常类似于Spring XML
在 bean 元素上的init-method
和destroy-method
属性,如以下示例所示:
- Bean
public class MyBean {
public void init() {
System.out.println("初始化");
}
public void destroy() {
System.out.println("销毁");
}
}
- Config 配置类
initMethod
:指定初始化方法destroyMethod
:指定销毁方法
@Configuration
@ComponentScan("com.mangfu12")
public class Config {
@Bean(initMethod = "init", destroyMethod = "destroy")
public MyBean bean() {
return new MyBean();
}
}
- 测试类
@Test
public void test15() {
//输出初始化
AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext("com.mangfu13");
//输出销毁
applicationContext.close();
}
@Bean 作用域(单例还是多例)
使用
@Bean注解
定义bean
时,能指定其特定的作用域,可选用在Bean 作用域
关内容里规定的任一标准作用域。其默认作用域是singleton(单例)
,不过,我们可以借助@Scope注解
来改变这个默认范围。
@Scope
:切换单例或者多例
@Configuration
@PropertySource(value = "classpath:application.properties")
public class JavaConfiguration {
@Value("${dataSource.url}")
private String url;
@Value("${dataSource.username}")
private String username;
@Value("${dataSource.password}")
private String password;
@Value("${dataSource.driver}")
private String driverClassName;
@Bean
@Scope("prototype")
public DruidDataSource dataSource() {
DruidDataSource dataSource = new DruidDataSource();
dataSource.setDriverClassName(driverClassName);
dataSource.setUrl(url);
dataSource.setUsername(username);
dataSource.setPassword(password);
return dataSource;
}
}
@Bean 互相注入
- 第一种方式
如果其他组件也是 @Bean 方法 也可以直接的调用方法 | 本质上从 ioc 容器获取组件
@Configuration
@PropertySource(value = "classpath:application.properties")
public class JavaConfiguration {
@Value("${dataSource.url}")
private String url;
@Value("${dataSource.username}")
private String username;
@Value("${dataSource.password}")
private String password;
@Value("${dataSource.driver}")
private String driverClassName;
@Bean
@Scope("prototype")
public DruidDataSource dataSource() {
DruidDataSource dataSource = new DruidDataSource();
dataSource.setDriverClassName(driverClassName);
dataSource.setUrl(url);
dataSource.setUsername(username);
dataSource.setPassword(password);
return dataSource;
}
@Bean
public JdbcTemplate jdbcTemplate() {
JdbcTemplate jdbcTemplate = new JdbcTemplate();
//需要DataSource 需要 ioc 容器的其他组件
//方案1: 如果其他组件也是 @Bean 方法 也可以直接的调用方法 | 本质上从 ioc 容器获取组件
//不推荐
jdbcTemplate.setDataSource(dataSource());
return jdbcTemplate;
}
}
- 第二种方式
形参变量注入
- 要求必须有对应的类型的组件, 如果没有抛异常
- 如果只有一个这种类型。自动注入
- 如果有多个这种类型。可以使用形参名称 = BeanName 指定注入
@Configuration
@PropertySource(value = "classpath:application.properties")
public class JavaConfiguration {
@Value("${dataSource.url}")
private String url;
@Value("${dataSource.username}")
private String username;
@Value("${dataSource.password}")
private String password;
@Value("${dataSource.driver}")
private String driverClassName;
@Bean
@Scope("prototype")
public DruidDataSource dataSource() {
DruidDataSource dataSource = new DruidDataSource();
dataSource.setDriverClassName(driverClassName);
dataSource.setUrl(url);
dataSource.setUsername(username);
dataSource.setPassword(password);
return dataSource;
}
@Bean
public JdbcTemplate jdbcTemplate(DataSource dataSource) {
JdbcTemplate jdbcTemplate = new JdbcTemplate();
//需要DataSource 需要 ioc 容器的其他组件
//方案2: 形参列表声明想要的组件类型, 可以是一个也可以是多个。ioc 容器会自动注入
//如果没有: 形参变量注入, 要求必须有对应的类型的组件, 如果没有抛异常
//如果有多个: 可以使用形参名称 = 对应的bean id
jdbcTemplate.setDataSource(dataSource);
return jdbcTemplate;
}
}
@Import 注解
@Import
注释允许从另一个配置类加载@Bean
定义,现在,在实例化上下文时不需要同时指定ConfigA.DataSourceConfig
和MainAppConfig
,只需显式提供 MainAppConfig ,如以下示例所示:
@Configuration
public class DataSourceConfig {
@Bean
public DruidDataSource dataSource() {
DruidDataSource dataSource = new DruidDataSource();
dataSource.setDriverClassName("com.mysql.cj.jdbc.Driver");
dataSource.setUrl("jdbc:mysql://localhost:3306/db02");
dataSource.setUsername("root");
dataSource.setPassword("Ting123321");
return dataSource;
}
}
~~~java
@Configuration
@Import(DataSourceConfig.class)
public class MainAppConfig {
}
public class ComponentTest {
@Test
public void testImportBeanConfig() {
ApplicationContext context = new AnnotationConfigApplicationContext(MainAppConfig.class);
DruidDataSource bean = context.getBean(DruidDataSource.class);
}
}
Spring 三种配置方式总结
XML 方式配置总结
配置文件
- 用 XML 格式写配置文件,把相关配置都放里面。
.bean 声明
- 用
<bean>
标签声明bean
,它包含“id”
(用于标识)、“class”
(指定对应类)这些基本信息,还有通过<property>
标签设置的属性信息,像用“name”
指定属性名,“value”
设简单值,“ref”
引用其他bean
。
.引入外部文件
- 若引入外部的配置文件,可用
<context:property-placeholder>
来操作。.
IoC 容器选择
- IoC 容器就选
ClassPathXmlApplicationContext
对象来实现相关功能。
XML + 注解方式配置总结
注解的用途
- 使用注解来标记参与 IoC(控制反转)的类以及完成属性装配工作。
.XML 文件作用
- 仍需 XML 文件,要借助
<context:component-scan>
标签指定扫描注解的范围,以便框架知晓哪些类上的注解需要处理。
.常用 IoC 注解
@Component
:通用标记组件类。@Service
:用于业务逻辑层类。@Controller
:标记控制层(如 Web 应用中处理请求)类。@Repository
:针对数据访问层类做标记。
.常用 DI 注解
@Autowired
:按类型自动装配依赖对象。@Qualifier
:配合 @Autowired,精准指定注入的 bean。@Resource
:可按名称或类型进行依赖注入。@Value
:注入简单字面量值。
.容器选择
- IoC 具体容器实现选用
ClassPathXmlApplicationContext
对象,实例化它并传入 XML 配置文件路径,就能让框架基于配置和注解完成管理、实例化等操作。*
完全注解方式配置总结
完全注解方式概述
- 完全注解方式就是不再使用 XML 文件了,而是依靠配置类结合各种注解来实现相关配置功能。
.配置类替代 XML 文件
- 原本放在 XML 文件里的配置,现在用添加了 @Configuration 注解的类来替代,这个类就相当于之前 XML 文件所承担的配置角色。
.常用 IoC 相关注解
@Component
:通用的组件标记,表明类是被 Spring 管理的组件。@Service
:专门用于标记业务逻辑层的类。@Controller
:多用于标记控制层(比如 Web 应用里处理请求的类)的类。@Repository
:常用来标记数据访问层(像操作数据库相关类)的类。
.常用 DI 相关注解
@Autowired
:按类型自动装配依赖对象。@Qualifier
:配合@Autowired
,用于更精准地指定要注入的 bean。@Resource
:可按名称或者类型来进行依赖注入。@Value
:用来注入像字符串、数字等简单的字面量值。
.注解范围指定
- 之前用
<context:component-scan>
标签指定扫描注解范围,现在换成在配置类里使用@ComponentScan(basePackages = {"com.atguigu.components"})
注解来指定要扫描的包路径范围,这样 Spring 就能知道去哪些地方查找带有相关注解的类了。
.引入外部配置文件
- 过去依靠
<context:property-placeholder>
引入外部配置文件,现在使用@PropertySource({"classpath:application.properties","classpath:jdbc.properties"})
注解来替代,它可以指定要加载的外部配置文件的路径,让配置类能获取到外部文件里的配置信息。
.Bean 声明替代
- 之前用
<bean>
标签声明bean
,现在通过在配置类的方法上添加@Bean
注解来实现,这个带注解的方法返回的对象就相当于之前<bean>
标签定义的bean
。
.IoC 容器实现选择
IoC
具体容器实现选择AnnotationConfigApplicationContext
对象,通过它来加载配置类,进而实现Spring
框架的管理、实例化等相关操作。
整合 Spring5-Test5 搭建测试环境
优点
在没有使用
SpringTest5
自动装配 的情况下,如果要在测试类中获取Spring
容器中的Person
和DruidDataSource
这两个Bean
,通常需要通过ApplicationContext
的getBean
方法来获取。例如
import org.junit.jupiter.api.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
public class ComponentTestTraditional {
@Test
public void test() {
ApplicationContext context = new AnnotationConfigApplicationContext(MainAppConfig.class);
// 使用getBean方法获取Person和DruidDataSource
Person person = (Person) context.getBean("person");
DruidDataSource dataSource = (DruidDataSource) context.getBean("dataSource");
System.out.println(person);
System.out.println(dataSource);
}
}
使用了
SpringTest5
后 可以在测试类中使用自动装配, 并且不用手动创建容器
@SpringJUnitConfig(MainAppConfig.class)
public class ComponentTest {
@Autowired
private Person person;
@Autowired
private DruidDataSource dataSource;
@Test
public void test() {
System.out.println(person);
System.out.println(dataSource);
}
}
使用第一步:导入依赖
<!--junit5测试-->
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-api</artifactId>
<version>5.3.1</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>6.0.6</version>
<scope>test</scope>
</dependency>
使用第二步:整合测试注解使用
- Bean
@Component
public class Person {
}
- DataSouceConfig 配置类1
@Configuration
public class DataSourceConfig {
@Bean
public DruidDataSource dataSource() {
DruidDataSource dataSource = new DruidDataSource();
dataSource.setDriverClassName("com.mysql.cj.jdbc.Driver");
dataSource.setUrl("jdbc:mysql://localhost:3306/db02");
dataSource.setUsername("root");
dataSource.setPassword("Ting123321");
return dataSource;
}
}
- MainAppConfig 配置类2
@Configuration
@Import(DataSourceConfig.class)
@ComponentScan(basePackages = {"com.mangfu15"})
public class MainAppConfig {
}
//@SpringJUnitConfig(locations = {"classpath:spring-context.xml"}) //指定配置文件xml
@SpringJUnitConfig(value = {MainAppConfig.class}) //指定配置类
public class Junit5IntegrationTest {
//这里不用 AnnoAnnotationConfigApplicationContext 获取 IoC 容器
//在测试范围内就能注入 Bean
@Autowired
private User user;
//不用 getbean 获取了。直接注入就行
@Autowired
private DruidDataSource dataSource;
@Test
public void testJunit5() {
System.out.println(user);
}
}
Spring AoP 面向切面编程
动态代理技术
JDK 动态代理
- JDK 动态代理是 Java 自带的一种代理方式。它要求目标类必须有接口,基于这个接口,JDK 在运行时会动态生成一个代理对象。这个代理对象和目标对象就像 “拜把子” 的兄弟,因为它们都实现了相同的接口,能以相同的方式被调用,只是代理对象在调用真正的方法前后可以添加额外的逻辑。
.CGLIB 动态代理
- CGLIB 动态代理是另一种代理技术。它的厉害之处在于不需要目标类有接口,通过让代理类继承目标类来实现代理,就好比代理类 “认” 目标类为 “干爹”。这样,代理类就能在目标类方法调用的前后插入自己的逻辑,从而对目标类的行为进行增强。
Spring AoP
AoP 概述
AOP 核心概念
- 切入点:实际被 AOP 控制的方法,需要被增强的方法
- 通知:封装共享功能的方法就是通知
AoP 通知类型
环绕就是一部分在目标方法之前,一部分在之后
- 它的返回值代表的是原始方法执行完毕的返回值
try {
前置通知 @Before
目标方法执行
返回后通知 @AfterReturning
} catch() {
异常后通知 @AfterThrowing
} finally {
后置通知 @After
}
AoP 通知顺序
AoP 切点表达式 @execution
AoP 切点表达式重用
第一种:创建存储切点的类维护
创建一个存储切点的类
单独维护切点表达式
execution 使用:类全限定符.方法名()
- 切点维护类
@Component
public class MyPointCut {
@Pointcut("execution(* com.atguigu.service.impl.*.*(..))")
public void pc(){}
- 重用类演示类
@Before("com.atguigu.pointcut.MyPointCut.pc()")
public void start() {
System.out.println("方法开始了");
}
@After("com.atguigu.pointcut.MyPointCut.pc()")
public void after() {
System.out.println("方法结束了");
}
@AfterThrowing("com.atguigu.pointcut.MyPointCut.pc()")
public void error() {
System.out.println("方法报错了");
}
第二种:当前类中提取表达式
定义一个空方法
注解 @Pointcut()
增强注解中引用切点 直接调用方法名
@Aspect
@Component
public class UserServiceAspect {
// 定义一个空方法,使用@Pointcut注解来定义切点表达式,这里表示匹配UserService接口下的所有方法
@Pointcut("execution(* com.example.demo.service.UserService.*(..))")
public void userServicePointcut() {}
// 在前置通知中复用上面定义的切点表达式,直接写切点方法名即可
@Before("userServicePointcut()")
public void beforeAddUser() {
System.out.println("在执行UserService的方法前执行的逻辑");
}
AoP 基本使用
底层技术组成
动态代理(InvocationHandler):
JDK原生
的实现方式,需要被代理的目标类必须实现接口。因为这个技术要求代理对象和目标对象实现同样的接口(兄弟两个拜把子模式)。cglib
:通过继承被代理的目标类(认干爹模式)实现代理,所以不需要目标类实现接口。AspectJ
:SpringAOP借用了AspectJ中的AOP注解。也就是说@Before
等注解都来自 AspectJ
默认代理方式演示(JDK原生)
第一步:导入依赖
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aspects</artifactId>
<version>6.0.6</version>
</dependency>
第二步:准备接口和实现类
public interface Calculator {
int add(int i, int j);
}
@Component
public class CalculatorPureImpl implements Calculator {
@Override
public int add(int i, int j) {
int result = i + j;
return result;
}
}
第三步:声明切面类 @Aspect
切面类要加入 IoC 容器
并且要使用@Aspect
声明为切面类
@Aspect
@Component
public class LogAdvice {
@Pointcut("execution(* com.mangfu.Calculator.*(..))")
public void Mypointcut(){}
@Before("Mypointcut()")
public void testBefore() {
System.out.println("before");
}
@AfterReturning("Mypointcut()")
public void testAfterReturning() {
System.out.println("afterReturning");
}
@AfterThrowing("Mypointcut()")
public void testAfterThrowing() {
System.out.println("afterThrowing");
}
@After("Mypointcut()")
public void testAfter() {
System.out.println("after");
}
}
第四步:用配置类开启 aspectj 注解支持 @EnableAspectJAutoProxy
@EnableAspectJAutoProxy
:开启 aspectj 注解支持
@Configuration
@ComponentScan("com.mangfu")
@EnableAspectJAutoProxy
public class MyConfig {
}
测试类
默认使用原生 JDK 代理。必须用接口来接收 IoC 类型的代理组件。因为它是继承自目标类接口代理类。不是目标类,所以不能用目标类类型接收
@SpringJUnitConfig(MyConfig.class)
public class MyTest {
//此时注入的是代理对象
@Autowired
private Calculator calculator;
@Test
public void test() {
System.out.println(calculator.add(1, 2));
}
}
环绕通知
就相当于直接进入切面类执行
- 接口和实现类
public interface Calculator {
int add(int i, int j);
}
@Component
public class CalculatorPureImpl implements Calculator {
@Override
public int add(int i, int j) {
int result = i + j;
return result;
}
}
- 配置类开启 AspectJ
@Configuration
@ComponentScan("com.mangfu")
@EnableAspectJAutoProxy
public class MyConfig {
}
- 切面类
ProceedingJoinPoint joinPoint
:目标方法对象jointPoint.getArgs()
: 获取目标方法运行的参数joinPoint.proceed(args)
: 执行目标方法
@Aspect
@Component
public class LogAdvice {
@Around("execution(* com.mangfu.Calculator.*(..))")
public Object around(ProceedingJoinPoint joinPoint) {
Object[] args = joinPoint.getArgs(); //获取目标方法运行的参数
Object result = null; //用于接收目标参数的返回值
try {
System.out.println("Before");
result = joinPoint.proceed(args); //调用目标方法
System.out.println("AfterReturning");
} catch (Throwable e) {
System.out.println("Afterthrowing");
throw new RuntimeException(e);
} finally {
System.out.println("After");
}
return result;
}
}
- 测试类
@SpringJUnitConfig(MyConfig.class)
public class MyTest {
@Autowired
private Calculator calculator;
@Test
public void test() {
System.out.println(calculator.add(1, 2));
}
}
JoinPoint 详解
CGLib 动态代理生效情况
在目标类没有实现任何接口的情况下,Spring会自动使用cglib技术实现代理。
- 如果目标类有接口, 选择使用
jdk
动态代理:实现目标接口
- 如果目标类没有接口, 选择
cglib
动态代理:继承目标对象
- 如果有接口, 就用接口接值
- 如果没有接口, 就用类进行接值
- 没实现接口的实体类和切面类
@Component
public class Calculator {
public int add(int i, int j) {
int result = i + j;
return result;
}
}
@Aspect
@Component
public class testBefore {
@Before("execution(* com.mangfu2.Calculator.add(..))" )
public void before(){
System.out.println("test sucessful");
}
}
- 配置类
@Component
public class Calculator {
public int add(int i, int j) {
int result = i + j;
return result;
}
}
- 测试类
@SpringJUnitConfig(MyConfig.class)
public class MyTest {
//建议用接口取。防止后期取不到代理类对象
//正常:aop - aop底层选择代理 - 选择jdk代理 - 根据接口生成代理类 - 代理对象和目标对象 是拜把子 兄弟关系。不是同一个
//这里实现类没有实现接口所以用 CGLib 动态代理
//aop - ioc 容器中真正存储的是代理对象不是目标对象
@Autowired
private Calculator calculator;
@Test
public void test(){
calculator.add(1,2);
}
}
Spring AoP 对获取 Bean 的影响
JDK 原生代理
声明一个接口,其仅有一个实现类,同时创建切面类对该接口的实现类应用通知:
- 按接口类型获取 bean 可正常获取。
- 按类获取 bean 则无法获取,原因在于应用切面后,实际存放在 IOC 容器中的是代理类对象,目标类本身并未放入 IOC 容器,所以依据目标类类型无法从 IOC 容器中找到相应对象
CGLib 代理
声明一个类,创建一个切面类,对上面的类应用通知
- 根据类获取 bean,能获取到
Spring TX 声明式事务
声明式事务概念
定义
:通过注解或 XML 配置的方式控制事务的提交和回滚,具体事务实现由第三方框架负责,开发者只需添加相应配置,无需直接进行事务操作。*优点
:可将事务控制和业务逻辑分离,提升代码的可读性与可维护性。
spring-tx
: 包含声明式事务实现的基本规范(事务管理器规范接口和事务增强等等)spring-jdbc
: 包含DataSource方式事务管理器实现类DataSourceTransactionManagerspring-orm
: 包含其他持久层框架的事务管理器实现类例如:Hibernate/Jpa等
.
spring-tx
就是 Spring 官方提供的事务接口,各大持久层框架需要自己实现这个接口,而spring-jdbc
就是jdbc
,jdbcTemplate
,mybatis
的实现类DataSourceTranscationManager
。spring-orm
就是Hibernate/Jpa
等持久层框架的实现类
声明式事务基本使用
第一步:加入依赖
jdbc, jdbcTemplate, mybatis
这三个持久层框架情况
<!-- 声明式事务依赖-->
<!-- 就是 tx 就是 spring 提供的事务接口-->
<!-- 声明式事务底层是 aop 所以要 aop 依赖-->
<!-- 声明式事务依赖-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-tx</artifactId>
<version>6.0.6</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aspects</artifactId>
<version>6.0.6</version>
</dependency>
<!-- 第三方实现 spring 事务接口的类的依赖-->
<!-- spring-jdbc -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>6.0.6</version>
</dependency>
- 如果是
Hibernate
等其他持久层框架就用spring-jdbc
换成spring-orm
依赖
第二步:在配置类上加上 @EnableTransactionManagement 注解开启事务管理
@Configuration
@ComponentScan("com.mangfu")
@PropertySource("classpath:jdbc.properties")
//@EnableAspectJAutoProxy //开启 aspectj 注解的支持
@EnableTransactionManagement //开启事务管理
public class JavaConfig {
@Value("${mangfu.driver}")
private String driver;
@Value("${mangfu.url}")
private String url;
@Value("${mangfu.username}")
private String username;
@Value("${mangfu.password}")
private String password;
//配置druid连接池
@Bean
public DataSource dataSource(){
DruidDataSource dataSource = new DruidDataSource();
dataSource.setDriverClassName(driver);
dataSource.setUrl(url);
dataSource.setUsername(username);
dataSource.setPassword(password);
return dataSource;
}
//配置 JDBCTemplate
@Bean
//jdbcTemplate
public JdbcTemplate jdbcTemplate(DataSource dataSource){
JdbcTemplate jdbcTemplate = new JdbcTemplate();
jdbcTemplate.setDataSource(dataSource);
return jdbcTemplate;
}
}
第三步:在配置类里配置事务管理器
事务管理器要设置基于哪个连接池进行操作
@Configuration
@ComponentScan("com.mangfu")
@PropertySource("classpath:jdbc.properties")
//@EnableAspectJAutoProxy //开启 aspectj 注解的支持
@EnableTransactionManagement //开启事务管理
public class JavaConfig {
@Value("${mangfu.driver}")
private String driver;
@Value("${mangfu.url}")
private String url;
@Value("${mangfu.username}")
private String username;
@Value("${mangfu.password}")
private String password;
//druid连接池
@Bean
public DataSource dataSource(){
DruidDataSource dataSource = new DruidDataSource();
dataSource.setDriverClassName(driver);
dataSource.setUrl(url);
dataSource.setUsername(username);
dataSource.setPassword(password);
return dataSource;
}
@Bean
//jdbcTemplate
public JdbcTemplate jdbcTemplate(DataSource dataSource){
JdbcTemplate jdbcTemplate = new JdbcTemplate();
jdbcTemplate.setDataSource(dataSource);
return jdbcTemplate;
}
//配置事务管理器
@Bean
public TransactionManager transactionManager(DataSource dataSource){
//内部要进行事务的操作, 基于的连接池。所以连接池的对象要给它。他才能对连接池的对象进行操作
DataSourceTransactionManager dataSourceTransactionManager = new DataSourceTransactionManager();
//需要连接池对象
dataSourceTransactionManager.setDataSource(dataSource);
return dataSourceTransactionManager;
}
}
第四步:给需要事务的地方加上 @Transactional 注解
@Treanscational
加在类上就是类里所有方法开启事务。加在方法上就单独那个方法有事务
- Dao层
@Repository
public class StudentDao {
@Autowired
private JdbcTemplate jdbcTemplate;
public void updateNameById(String name,Integer id){
String sql = "update students set name = ? where id = ? ;";
int rows = jdbcTemplate.update(sql, name, id);
}
public void updateAgeById(Integer age,Integer id){
String sql = "update students set age = ? where id = ? ;";
jdbcTemplate.update(sql,age,id);
}
}
- Service 层
@Service
public class StudentService {
@Autowired
private StudentDao studentDao;
/**
* 添加事务:
* @Transctional
* 位置: 方法 | 类上
* 方法: 当前方法有事务
* 类上: 泪下所有方法都有事务
*/
//这里有自动事务了。所以可以报错可以自动回滚
@Transactional
public void changeInfo(){
studentDao.updateAgeById(88,1);
int i = 1/0;
System.out.println("-----------");
studentDao.updateNameById("test1",1);
}
}
- 测试类
@SpringJUnitConfig(JavaConfig.class)
public class TxTest {
@Autowired
private StudentService studentService;
@Test
public void testTx(){
studentService.changeInfo();
}
}
事务的属性
事务属性:只读
只读模式可以提升查询事务的效率!,一般情况都是通过类添加注解添加事务,类下的所有方法都有事务,而查询方法一般不用添加事务。这个时候可以再次添加事务注解,设置为只读,提高效率查询效率
@Transactional(readOnly = ...)
@Transactional
@Service
public class StudentService {
@Autowired
private StudentDao studentDao;
//查询 没有必要添加事务。这里设置为只读提高效率
@Transactional(readOnly = true)
public void getStudentInfo() {
//获取学生信息 查询数据库 不修改
}
}
事务属性:超时
事务在执行过程中,有可能因为遇到某些问题,导致程序卡住,从而长时间占用数据库资源。而长时间占用资源,大概率是因为程序运行出现了问题(可能是Java程序或MySQL数据库或网络连接等等)。此时这个很可能出问题的程序应该被回滚,撤销它已做的操作,事务结束,把资源让出来,让其他正常程序可以执行。概括来说就是一句话:超时回滚,释放资源。
@Transactional(timeout = ...)
@Transactional(timeout = 3)
@Service
public class StudentService {
@Autowired
private StudentDao studentDao;
public void changeInfo() {
studentDao.updateAgeById(88,1);
System.out.println("-----------");
try {
Thread.sleep(4000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
studentDao.updateNameById("test2",1);
}
//因为这个注解会覆盖掉类上注解。所以要再设置一遍
@Transactional(readOnly = true, timeout = 3)
public void getStudentInfo() {
}
}
事务属性:回滚
默认情况下,发生运行时 (RuntimeException) 异常事务才回滚,所以我们可以指定 Exception 异常来控制所有异常都回滚
roollbackFor = 回滚的异常范围
:设置的异常都回滚noRollbackFor = 不回滚的异常范围
:控制某个异常不回滚
//所有异常都回滚,除了 FileNotFoundException
@Transactional(rollbackFor = Exception.class, noRollbackFor = FileNotFoundException.class)
public void changeInfo() throws FileNotFoundException {
studentDao.updateAgeById(99,1);
new FileInputStream("xxxx");
studentDao.updateNameById("test2",1);
}
事务属性:事务隔离级别
事务并发可能引发的问题
脏读
:一个事务读取另一个事务未提交的数据不可重复读
:一个事务就是读取了另一个事务提交的修改数据幻读
: 一个事务读取了另一个事务提交的插入数据
事务隔离级别
读未提交(Read Uncommitted)
:事务可以读取未被提交的数据,容易产生脏读、不可重复读和幻读等问题。实现简单但不太安全,一般不用。读已提交(Read Committed)
:事务只能读取已经提交的数据,可以避免脏读问题,但可能引发不可重复读和幻读。可重复读(Repeatable Read)
:在一个事务中,相同的查询将返回相同的结果集,不管其他事务对数据做了什么修改。可以避免脏读和不可重复读,但仍有幻读的问题。串行化(Serializable)
:最高的隔离级别,完全禁止了并发,只允许一个事务执行完毕之后才能执行另一个事务。可以避免以上所有问题,但效率较低,不适用于高并发场景。
.不同的隔离级别适用于不同的场景,需要根据实际业务需求进行选择和调整。
建议设置第二个隔离级别
isolation = Isolation.事务的隔离级别
READ_UNCOMMITTED 读未提交
READ_COMMITTED 读已提交
REPEATABLE_READ 可重复读
SERIALIZABLE 串行化
//设置事务隔离级别为可串行化
@Transactional(isolation = Isolation.SERIALIZABLE)
public void changeInfo() throws FileNotFoundException {
studentDao.updateAgeById(99,1);
new FileInputStream("xxxx");
studentDao.updateNameById("test2",1);
}
事务属性:事务传播行为
propagation = 传播规则
【默认是 Propagation.REQUIRED】
我们一般使用默认就行
名称【传播规则】 | 含义 |
---|---|
REQUIRED | 如果父方法有事务,就加入,如果没有就新建自己独立!父方法有事务报错,子方法会回滚 |
REQUIRES_NEW | 不管父方法是否有事务,我都新建事务,都是独立的!即使父方法有有事务报错。两个子方法也不会回滚 |
就是用父方法
topService()
调用 子方法changeAge()
和changeName()
。这两个子方法是否会进行回滚