2 Spring框架
2.1技术体系结构
单一架构
一个项目,一个工程,导出为一个war包,在一个Tomcat上运行。也叫all in one。
单一架构,项目主要应用技术框架为:Spring , SpringMVC , Mybatis
分布式架构
一个项目(对应 IDEA 中的一个 project),拆分成很多个模块,每个模块是一个 IDEA 中的一个 module。每一个工程都是运行在自己的 Tomcat 上。模块之间可以互相调用。每一个模块内部可以看成是一个单一架构的应用。
分布式架构,项目主要应用技术框架:SpringBoot (SSM), SpringCloud , 中间件等
2.2 框架概念
框架( Framework )是一个集成了基本结构、规范、设计模式、编程语言和程序库等基础组件的软件系统,它可以用来构建更高级别的应用程序。框架的设计和实现旨在解决特定领域中的常见问题,帮助开发人员更高效、更稳定地实现软件开发目标。
jar包 + 配置文件
2.3 SpringFramework
广义的Spring: Spring技术栈(全家桶)
狭义的Spring: Spring Framework 是Spring家族的基础框架
主要功能模块
功能模块 | 功能介绍 |
---|---|
Core Container | 核心容器,在 Spring 环境下使用任何功能都必须基于 IOC 容器。 |
AOP&Aspects | 面向切面编程 |
TX | 声明式事务管理。 |
Spring MVC | 提供了面向Web应用程序的集成功能。 |
2.4 Spring IoC容器
组件: 可以复用的java对象 (组件一定是对象, 但对象不一定是组件)
组件交给Spring管理的组件, 香肉Spring框架的其他功能
Spring IoC容器通过XML文件或者注解,轻松的对组件进行配置和管理,使得组件的切换、替换等操作更加的方便和快捷
2.4.1普通容器 & 复杂容器
普通容器
只能存储,没有更多功能
数组 / 集合
复杂容器
存储和管理对象的一生
Servlet 容器能够管理 Servlet(init,service,destroy)、Filter、Listener 这样的组件的一生,所以它是一个复杂容器。
名称 | 时机 | 次数 |
---|---|---|
创建对象 | 默认情况:接收到第一次请求 修改启动顺序后:Web应用启动过程中 | 一次 |
初始化操作 | 创建对象之后 | 一次 |
处理请求 | 接收到请求 | 多次 |
销毁操作 | Web应用卸载之前 | 一次 |
我们即将要学习的 SpringIoC 容器也是一个复杂容器。它们不仅要负责创建组件的对象、存储组件的对象,还要负责调用组件的方法让它们工作,最终在特定情况下销毁组件。
总结:Spring管理组件的容器,就是一个复杂容器,不仅存储组件,也可以管理组件之间依赖关系,并且创建和销毁组件等!
2.4.2 SpringIoC容器介绍
Spring IoC 容器,负责实例化、配置和组装 bean(组件)。**容器通过读取配置元数据来获取有关要实例化、配置和组装组件的指令。**配置元数据以 XML、Java 注解或 Java 代码形式表现。它允许表达组成应用程序的组件以及这些组件之间丰富的相互依赖关系。
上图显示了 Spring 容器工作原理的高级视图。应用程序类与配置元数据相结合,您拥有完全配置且可执行的系统或应用程序。
spring容器分两类
1. SpringIoC容器接口 BeanFactory
规范了容器具体的动作
BeanFactory
接口提供了一种高级配置机制,能够管理任何类型的对象,它是SpringIoC容器标准化超接口!
ApplicationContext
是 BeanFactory
的子接口。它扩展了以下功能:
- 更容易与 Spring 的 AOP 功能集成
- 消息资源处理(用于国际化)
- 特定于应用程序给予此接口实现,例如Web 应用程序的
WebApplicationContext
简而言之, BeanFactory
提供了配置框架和基本功能,而 ApplicationContext
添加了更多特定于企业的功能。 ApplicationContext
是 BeanFactory
的完整超集!
2. ApplicationContext容器实现类
类型名 | 简介 |
---|---|
ClassPathXmlApplicationContext | 通过读取类路径下的 XML 格式的配置文件创建 IOC 容器对象 |
FileSystemXmlApplicationContext | 通过文件系统路径读取 XML 格式的配置文件创建 IOC 容器对象(不用) |
AnnotationConfigApplicationContext | 通过读取Java配置类创建 IOC 容器对象 |
WebApplicationContext | 专门为 Web 应用准备,基于 Web 环境创建 IOC 容器对象,并将对象引入存入 ServletContext 域中。 |
Spring框架提供了多种配置方式:XML配置方式、注解方式和Java配置类方式
为了迎合当下开发环境,我们将以配置类+注解方式为主进行讲解!
2.4.3 Spring IoC/DI概念
-
IoC(Inversion of Control)控制反转
IoC 主要是针对对象的创建和调用控制而言的,也就是说,当应用程序需要使用一个对象时,不再是应用程序直接创建该对象,而是由 IoC 容器来创建和管理,即控制权由应用程序转移到 IoC 容器中,也就是“反转”了控制权。这种方式基本上是通过依赖查找的方式来实现的,即 IoC 容器维护着构成应用程序的对象,并负责创建这些对象。
-
DI (Dependency Injection) 依赖注入
DI 是指在组件之间传递依赖关系的过程中,将依赖关系在容器内部进行处理,这样就不必在应用程序代码中硬编码对象之间的依赖关系,实现了对象之间的解耦合。在 Spring 中,DI 是通过 XML 配置文件或注解的方式实现的。它提供了三种形式的依赖注入:构造函数注入、Setter 方法注入和接口注入。
2.5 Spring IoC实践和应用
2.5.1 Spring IoC/DI大体实现步骤
-
编写配置信息
描绘组件信息 & 组件之间的引用关系
-
实例化IoC容器对象
要指定配置信息
-
在Java代码中获取组件 使用组件
2.5.2 基于XML配置方式的组件管理
见代码
1.编写配置信息
<?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">
<!--引用和被引用的组件必须全部在ioc容器中-->
<!--1. 单个构造函数注入-->
<!--步骤一 将他们都放入ioc容器-->
<bean id="userDao" class="com.atguigu.ioc_02.UserDao"/>
<bean id="userService" class="com.atguigu.ioc_02.UserService">
<!--构造参数传值 di的配置
constructor-arg 构造参数传值 下面两个二选一
value 直接引用属性
ref 引用其他的bean 传入id
-->
<constructor-arg ref="userDao"/>
</bean>
<!--2. 多个构造函数注入-->
<bean id="userDao2" class="com.atguigu.ioc_02.UserDao"/>
<bean id="userService2" class="com.atguigu.ioc_02.UserService">
<!--参数按顺序进行填写-->
<constructor-arg value="23"/>
<constructor-arg value="Leo"/>
<constructor-arg ref="userDao2"/>
</bean>
<bean id="userDao2" class="com.atguigu.ioc_02.UserDao"/>
<bean id="userService2" class="com.atguigu.ioc_02.UserService">
<!--参数按参数名字进行填写 [推荐]-->
<constructor-arg name="age" value="23"/>
<constructor-arg name="name" value="Leo"/>
<constructor-arg name="userDao" ref="userDao2"/>
</bean>
<!--2. 触发setter方法进行注入-->
<bean id="movieFinder" class="com.atguigu.ioc_02.MovieFinder"/>
<bean id="simpleMovieLister" class="com.atguigu.ioc_02.SimpleMovieLister">
<!--name是属性名 set方法去掉set 首字母小写-->
<property name="movieName" value="消失的她"/>
<property name="movieFinder" ref="movieFinder"/>
</bean>
</beans>
2.实例化IoC容器对象
//方式一 推荐
//构造函数(String ...配置文件) 可以填一个或多个
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring-03.xml");
//方式二
//先创建IoC容器对象,再指定配置文件,再刷新
ClassPathXmlApplicationContext applicationContext1 = new ClassPathXmlApplicationContext();
applicationContext1.setConfigLocations("spring-03.xml");
applicationContext1.refresh();
}
/**
* 如何在ioc容器中获取组件Bean
*/
public void getBeanFromIoC(){
//1.创建IoC容器对象
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring-03.xml");
//2.读取ioc容器组件
//方案一 根据id获得, 返回值类型是Object 需要强转不推荐
HappyComponent happyComponent = (HappyComponent)applicationContext.getBean("happyComponent");
//方式二 根据id,同时执行bean的类型class
HappyComponent happyComponent2 = applicationContext.getBean("happyComponent",HappyComponent.class);
//方式三 直接根据类型获取
// 会存在问题,同一个类型在ioc中只能有一个bean,否则会报错
//ioc的配置一定是实现类, 也可以根据接口类型获取值
HappyComponent happyComponent3 = applicationContext.getBean(HappyComponent.class);
happyComponent2.doWork();
//这三个都是一个对象, 因为默认是单例,可以设置
}
2.5.3 周期方法设置
<!--
配置初始化和销毁方法
spinrgIoC容器会自动调用
-->
<bean id="javaBean" class="com.atguigu.ioc_04.JavaBean" init-method="init" destroy-method="clear"/>
2.5.4 组件作用域配置
-
Bean作用域概念
<bean
标签声明Bean,只是将Bean的信息配置给SpringIoC容器!在IoC容器中,这些
<bean
标签对应的信息转成Spring内部BeanDefinition
对象,BeanDefinition
对象内,包含定义的信息(id,class,属性等等)!这意味着,
BeanDefinition
与类
概念一样,SpringIoC容器可以可以根据BeanDefinition
对象反射创建多个Bean对象实例。具体创建多少个Bean的实例对象,由Bean的作用域Scope属性指定!
-
scope作用域可选值
取值 | 含义 | 创建对象的时机 | 默认值 |
---|---|---|---|
singleton | 在 IOC 容器中,这个 bean 的对象始终为单实例 | IOC 容器初始化时 | 是 |
prototype | 这个 bean 在 IOC 容器中有多个实例 | 获取 bean 时 | 否 |
如果是在WebApplicationContext环境下还会有另外两个作用域(但不常用):
取值 | 含义 | 创建对象的时机 | 默认值 |
---|---|---|---|
request | 请求范围内有效的实例 | 每次请求 | 否 |
session | 会话范围内有效的实例 | 每次会话 | 否 |
-
作用域配置
配置scope范围
<!--bean的作用域
准备两个引用关系的组件类即可!!
-->
<!-- scope属性:取值singleton(默认值),bean在IOC容器中只有一个实例,IOC容器初始化时创建对象 -->
<!-- scope属性:取值prototype,bean在IOC容器中可以有多个实例,getBean()时创建对象 -->
<bean id="happyMachine8" scope="prototype" class="com.atguigu.ioc.HappyMachine">
<property name="machineName" value="happyMachine"/>
</bean>
<bean id="happyComponent8" scope="singleton" class="com.atguigu.ioc.HappyComponent">
<property name="componentName" value="happyComponent"/>
</bean>
- 作用域测试
@Test
public void testExperiment08() {
ApplicationContext iocContainer = new ClassPathXmlApplicationContext("配置文件名");
HappyMachine bean = iocContainer.getBean(HappyMachine.class);
HappyMachine bean1 = iocContainer.getBean(HappyMachine.class);
//多例对比 false
System.out.println(bean == bean1);
HappyComponent bean2 = iocContainer.getBean(HappyComponent.class);
HappyComponent bean3 = iocContainer.getBean(HappyComponent.class);
//单例对比 true
System.out.println(bean2 == bean3);
}
2.5.5 FactoryBean特性和使用
-
FactoryBean简介
FactoryBean
接口是Spring IoC容器实例化逻辑的可插拔性点。用于配置复杂的Bean对象,可以将创建过程存储在
FactoryBean
的getObject方法!FactoryBean<T>
接口提供三种方法:-
T getObject()
:返回此工厂创建的对象的实例。该返回值会被存储到IoC容器!
-
boolean isSingleton()
:如果此
FactoryBean
返回单例,则返回true
,否则返回false
。此方法的默认实现返回true
(注意,lombok插件使用,可能影响效果)。 -
Class<?> getObjectType()
: 返回getObject()
方法返回的对象类型,如果事先不知道类型,则返回null
。
-
public class JavaBeanFactoryBean implements FactoryBean<JavaBean> {
public void setName(String name) {
this.name = name;
}
private String name;
@Override
public JavaBean getObject() throws Exception {
JavaBean javaBean = new JavaBean();
javaBean.setName(name);
return javaBean;
}
@Override
public Class<?> getObjectType() {
return JavaBean.class;
}
}
如果要给对象赋值可以拐个弯给工厂赋值
<!--
id: getObject方法返回的对象的标识
Class: factoryBean标准化工厂类
-->
<bean id="javaBean" class="com.atguigu.ioc_05.JavaBeanFactoryBean"/>
FactoryBean和BeanFactory区别
**FactoryBean **是 Spring 中一种特殊的 bean,可以在 getObject() 工厂方法自定义的逻辑创建Bean!是一种能够生产其他 Bean 的 Bean。FactoryBean 在容器启动时被创建,而在实际使用时则是通过调用 getObject() 方法来得到其所生产的 Bean。因此,FactoryBean 可以自定义任何所需的初始化逻辑,生产出一些定制化的 bean。
一般情况下,整合第三方框架,都是通过定义FactoryBean实现!!!
BeanFactory 是 Spring 框架的基础,其作为一个顶级接口定义了容器的基本行为,例如管理 bean 的生命周期、配置文件的加载和解析、bean 的装配和依赖注入等。BeanFactory 接口提供了访问 bean 的方式,例如 getBean() 方法获取指定的 bean 实例。它可以从不同的来源(例如 Mysql 数据库、XML 文件、Java 配置类等)获取 bean 定义,并将其转换为 bean 实例。同时,BeanFactory 还包含很多子类(例如,ApplicationContext 接口)提供了额外的强大功能。
总的来说,FactoryBean 和 BeanFactory 的区别主要在于前者是用于创建 bean 的接口,它提供了更加灵活的初始化定制功能,而后者是用于管理 bean 的框架基础接口,提供了基本的容器功能和 bean 生命周期管理。
BUG
username是mysql的关键词,所以将“username”改成“jdbc.username”
jdbc.driverClassName=com.mysql.cj.jdbc.Driver
jdbc.username=root
jdbc.password=2333
jdbc.url=jdbc:mysql://127.0.0.1:3306/studb
--------实践-------
2.6 使用注解的方式
-
组件标记注解和区别
Spring 提供了以下多个注解,这些注解可以直接标注在 Java 类上,将它们定义成 Spring Bean。
注解 | 说明 |
---|---|
@Component | 该注解用于描述 Spring 中的 Bean,它是一个泛化的概念,仅仅表示容器中的一个组件(Bean),并且可以作用在应用的任何层次,例如 Service 层、Dao 层等。 使用时只需将该注解标注在相应类上即可。 |
@Repository | 该注解用于将数据访问层(Dao 层)的类标识为 Spring 中的 Bean,其功能与 @Component 相同。 |
@Service | 该注解通常作用在业务层(Service 层),用于将业务层的类标识为 Spring 中的 Bean,其功能与 @Component 相同。 |
@Controller | 该注解通常作用在控制层(如SpringMVC 的 Controller),用于将控制层的类标识为 Spring 中的 Bean,其功能与 @Component 相同。 |
2.6.1 使用组件的步骤
-
配置文件
-
配置指定包
-
普通配置包扫描
<context:component-scan base-package="com.atguigu.ioc_01"/>
会扫描com.atguigu.ioc_01及其子包
-
排除包下的注解
<context:component-scan base-package="com.atguigu"> 排除包下的注解 <context:exclude-filter type="annotation" expression="org.soringframework.stereotype.Repository"/> <context:exclude-filter type="annotation" expression="org.soringframework.stereotype.Controller"/> </context:component-scan>
-
指定包, 只要指定的注解
<context:component-scan base-package="com.atguigu" user-default-filters="false"> <context:include-filter type="annotation" expression="org.soringframework.stereotype.Repository"/> </context:component-scan>
-
组件BeanName问题
在我们使用 XML 方式管理 bean 的时候,每个 bean 都有一个唯一标识——id 属性的值,便于在其他地方引用。现在使用注解后,每个组件仍然应该有一个唯一标识。
-
默认情况:类名首字母小写就是 bean 的 id。例如:SoldierController 类对应的 bean 的 id 就是 soldierController。
-
使用value属性指定
@Controller(value = "tianDog")
public class SoldierController {
}
当注解中只设置一个属性时,value属性的属性名可以省略:
@Service("smallDog")
public class SoldierService {
}
2.6.2 组件(Bean)的作用域和周期方法注解
-
组件周期方法配置
-
周期方法概念
我们可以在组件类中定义方法,然后当IoC容器实例化和销毁组件对象的时候进行调用!这两个方法我们成为生命周期方法!
类似于Servlet的init/destroy方法,我们可以在周期方法完成初始化和释放资源等工作。
-
周期方法声明
-
public class BeanOne {
//周期方法要求: 方法命名随意,但是要求方法必须是 public void 无形参列表
@PostConstruct //注解制指定初始化方法
public void init() {
// 初始化逻辑
}
}
public class BeanTwo {
@PreDestroy //注解指定销毁方法
public void cleanup() {
// 释放资源逻辑
}
}
-
组件作用域配置
-
Bean作用域概念
<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 时 | 否 |
如果是在WebApplicationContext环境下还会有另外两个作用域(但不常用):
取值 | 含义 | 创建对象的时机 | 默认值 |
---|---|---|---|
request | 请求范围内有效的实例 | 每次请求 | 否 |
session | 会话范围内有效的实例 | 每次会话 | 否 |
3. 作用域配置
@Scope(scopeName = ConfigurableBeanFactory.SCOPE_SINGLETON) //单例,默认值
@Scope(scopeName = ConfigurableBeanFactory.SCOPE_PROTOTYPE) //多例 二选一
public class BeanOne {
//周期方法要求: 方法命名随意,但是要求方法必须是 public void 无形参列表
@PostConstruct //注解制指定初始化方法
public void init() {
// 初始化逻辑
}
}
2.6.3 自动装配注入
替代ref
-
@Autowired注解
在成员变量上直接标记@Autowired注解即可,不需要提供setXxx()方法。
可以在 成员变量 / 构造器 / setXX方法 前添加该注解
流程如下
还有一个佛系装配, 能装就装, 装不上就装不上 (不推荐)
@Autowired(required = false)
如果有多个资源产生冲突了需要再添加注解来指定
@Autowired @Qualifiter(value = "userServiceImpl")
-
@Resource
相当于一个优化
@Autowired + @Qualifiter(value = “userServiceImpl”) = @Resource(name = “userServiceImpl”)
可能需要导入依赖
2.6.4 基本数据类型注入
替代value
- 直接赋值
- @Value (一般是用来读取外部的变量)
@Value("19")
private int age ;
@Value("${jdbc.username:admin}")
private String username;
<context:property-placeholder location="classpath:jdbc.properties" />
添加配置信息
注解练习----spring-ioc-annotation-practice-05
2.7 完全注解开发
使用配置类, 代替xml配置文件
要做三件事情:
- 包扫描注解配置
- 引用外部的配置文件
- 声明第三方依赖的bean文件
使用步骤:
- 添加@congifruation 代表我们是配置类
- 实现上面的三个功能注解
配置类
@ComponentScan({"com.atguigu.ioc_01"})
@PropertySource("classpath:druid.properties")
@Configuration
public class JavaConfiguration {
@Value("${jdbc.url}")
private String url;
@Value("${jdbc.driver}")
private String driver;
@Value("${jdbc.username}")
private String username;
@Value("${jdbc.password}")
private String password;
//第三方依赖的bean文件
/**
* 方法的返回值: bean组件类型或者其接口和父类
* 方法的名字:bean的id
* 需要使用@Bean注解 让配置类的方法创建的组件存储到ioc容器
*/
@Bean
public DruidDataSource dataSource(){
DruidDataSource dataSource = new DruidDataSource();
dataSource.setUrl(url);
dataSource.setDriverClassName(driver);
dataSource.setUsername(username);
dataSource.setPassword(password);
return dataSource;
}
}
测试类
public void test(){
//1. 创建ioc容器
AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(JavaConfiguration.class);
//2. 获取bean1
StudentController bean = applicationContext.getBean(StudentController.class);
System.out.println("Bean = "+bean);
}
几个问题
-
BeanName问题
默认: 方法名
指定: name属性起名字
-
周期方法指定
1.原有注解方案
2.bean属性指定
-
作用域
和之前一样是@Scope注解, 默认单例
-
如何引用其他的ioc组件
@Bean
public JdbcTemplate jdbcTemplate(){
JdbcTemplate jdbcTemplate = new JdbcTemplate();
//方案一
jdbcTemplate.setDataSource(dataSource());
return jdbcTemplate;
}
@Bean
public JdbcTemplate jdbcTemplate1(DataSource dataSource){
//方案二 在形参列表生命想要的组件类型, 可以是一个也可以是多个 ioc容器会注入
//形参变量注入 要求必须要求有对应类型的组件, 如果没有抛异常
//如果有多个 形参名要等于对应的bean的name
JdbcTemplate jdbcTemplate = new JdbcTemplate();
jdbcTemplate.setDataSource(dataSource());
return jdbcTemplate;
}
@import配置类
可以一次导入多个配置类
@Import(value = {JavaConfigurationB.class})
public class JavaConfigurationA {
}
这样导入A的话也导入了B
注解配置总结
声明配置类 @Configuration
扫描包注解 @ComponentScan
引用外部配置 @PropertySource
声明Bena @Bean
配置类对应的Ioc容器 AnnotationConfigApplicationContext
小练习-----
2.8 整合Spring5-Test5 搭建测试环境
在测试类上添加注解
@SpringJUnitConfig(value = JavaConfig.class)
//locations指定配置文件xml value指定配置类
//可以快速创建ioc容器 使得测试更加快捷
3 Spring AOP面向切面编程
aop的应用场景
非核心代码重复
对于附加的重复的代码需要解耦合, 将重复的代码统一提取, 并且动态插入到每个业务方法
代理模式
让我们在调用目标方法的时候,不再是直接对目标方法进行调用,而是通过代理类间接调用。让不属于目标方法核心逻辑的代码从目标方法中剥离出来——解耦。
有静态代理和动态代理两种
动态代理
-
JDK动态代理
java原生
要求目标类必须有接口, 接口生成一个代理类
-
cglib动态代理
第三方 集成到sping
不要求目标类有接口, 直接根据目标类生成一个子类对象
在实践开发不需要写代理代码,使用SpringAOP框架
面向切面编程思维(AOP)
是对OOP面向对象编程的补充和完善
oop的缺点 是允许纵向关系,不适合定义横向的关系, 可以继承但是不能修改细节
AOP允许非核心代码提取出去,最后利用横切的代理技术把这些代码插入
使用AOP可以在不修改原来代码的基础上添加新功能
AOP的应用场景
- 日志记录
- 事务处理
- 异常处理等等
AOP术语名词介绍
-
横切关注点
核心关注点是与业务紧密相关的,横切关注点与业务本身无关,像日志、事务、异常、权限认证是横切关注点
-
通知(增强)
要提取的代码叫做增强
- 前置增强 目标方法之前增强代码 开启事务
- 返回增强 正常返回的增强代码 事务提交
- 异常增强 发生异常调用的增强代码 事务回滚
- 后置增强 最后一定会被调用 日志的结束统计
- 环绕增强 环绕调用
-
连接点
哪些方法需要切入,这些方法叫做连接点
-
切入点
选中的连接点
-
切面
增强插到切点,切入点和通知的结合
-
目标 target
被代理的目标对象
-
代理 proxy
创建的代理对象
-
织入weave
切入的动作
AOP实现步骤
一、正常的不加横切关注点的写好
- 导入依赖
- 正常编写核心业务,加入ioc容器
- 编写ioc的配置类和文件
- 测试环境
二、写横切关注点
-
增强类(存储横切关注点的代码)
-
增强类的配置(插入切点的位置,切点指定,切面配置)
-
写好方法
-
使用注解配置,指定插入目标方法的位置
前置 @before
后置 @AfterReturning
异常 @AfterThrowing
最后 @After
环绕 @Around (特殊)@Before(“execution(* com.atguigu.service.impl..(…))”)
-
补全注释
加入ioc容器@Component
配置切面@Aspect
-
-
开启aop的配置
配置类加入@EnableAspectJAutoProxy注解
在方法中获取切点信息
/* 增强方法中获取目标方法信息
* 1. 全部增强方法中,获取目标方法的信息(方法名,参数,访问修饰符,所属的类的信息。。。)
* JoinPoint包含目标方法的信息
* 2. 在@AfterReturning 获取返回的结果
* Object result接收返回结果,需要在注解中指定
* 3. 在AfterThrowing 获取异常的信息
* Throwable throwable接收异常信息,需要在注解中指定
* 需要增加连接点,是目标方法信息封装对象
*/
@Component
@Aspect//是切面就会有对应的提示
public class MyAdvice {
@Before("execution(* com..impl.*.*(..))")
public void before(JoinPoint joinPoint){
//获取方法属于类的信息
String simpleName = joinPoint.getTarget().getClass().getSimpleName();
//获取方法名称
int modifiers = joinPoint.getSignature().getModifiers();//获取访问修饰符
String s = Modifier.toString(modifiers);//将访问访问修饰符的int形式转换成Sting形式
String name = joinPoint.getSignature().getName();//获取方法名
//获取参数列表
Object[] args = joinPoint.getArgs();//获取目标方法参数
}
@AfterReturning(value = "execution(* com..impl.*.*(..))",returning = "result")
public void afterReturning(JoinPoint joinPoint,Object result){//正常结束会走
}
@After("execution(* com..impl.*.*(..))")
public void after(JoinPoint joinPoint){
}
@AfterThrowing(value = "execution(* com..impl.*.*(..))",throwing = "throwable")
public void afterThrowing(JoinPoint joinPoint,Throwable throwable){
}
}
切点表达式
可以通过定义匹配规则,来选择需要被切入的目标对象
固定语法 execution(1 2 3.4.5(6))
-
访问修饰符
public / private
-
方法的返回参数类型
String int void
如果不考虑访问修饰符和返回值 正两位整合成一起写*
(如果不考虑, 必须是两个都不考虑)
-
包的位置
具体包 com.atguigu.service.impl
单层模糊 com.atguigu.service.* *单层模糊
多层模糊 com…impl …是任意层的模糊 …不能开头
找所有impl包的类: com…impl 不能写…impl 可以写成*…impl
-
类的名称
具体: CalculatorPureImpl
模糊: *
部分模糊: *Impl
-
方法名 (语法和类名一致)
-
形参列表 没有参数就是() 有具体参数(String)
模糊参数(…) 有没有参数都可以
部分模糊 (String…) 第一个是Sting 后面无所谓
(…String) 最后一个是String
切点表达式的提取和复用
先提前写一个,再引用
@Pointcut("excution(* com.atguigu.*.*.*(..))")
public void pc(){}
@Before("pc()")
public void before(){}
建议创建一个存储切点的类
单独维护切点表达式
@Pointcut("excution(* com.atguigu.service.impl.*)")
环绕通知 @Around 自定义位置
环绕通知需要在通知中定义目标方法的执行
需要抛出异常
代替了之前几个
@Component
@Aspect
public class TxAroundAdvice {
@Around("com.atguigu.pointcut.MyPointCut.pc()")
public Object transaction(ProceedingJoinPoint joinPoint){
Object[] args = joinPoint.getArgs();
Object result = null;
try {
System.out.println("开启事务");
result = joinPoint.proceed(args);
System.out.println("结束事务");
} catch (Throwable e) {
System.out.println("事务回滚");
throw new RuntimeException(e); //需要抛出异常 让调用者获取异常
} finally {
}
return result;
}
}
切面优先级
@Order(10)//指定一个优先级的值,值越小优先级越高
注解实现总结
Spring声明式事务
声明式事务概念
编程式事务
编程式事务是指手动编写程序来管理事务, 即通过编写代码的方式直接控制事物的提交和回滚
会产生冗余,复用度不高
Connection conn = ...;
try {
// 开启事务:关闭事务的自动提交
conn.setAutoCommit(false);
// 核心操作
// 业务代码
// 提交事务
conn.commit();
}catch(Exception e){
// 回滚事务
conn.rollBack();
}finally{
// 释放数据库连接
conn.close();
}
spring-tx 声明式事务的框架 transaction manager
指定哪些方法需要添加事务 添加配置文件
两个步骤
-
选择一个合适的事务管理器实现加入ioc容器
Spring声明式事务给我们提供了多个管理器的实现
需要哪种就加入到ioc容器即可
- mybatis jdbcTempate -> DataSource TM
- hibernate -> HibernateTM
- …
-
指定哪些方法需要添加事务
添加@Transactional注解
事务的属性
只读模式
@Transactional(readOnly = true)
超时时间
默认永远不超时
设置秒数@Transactional(timeout = 3)
指定异常回滚和指定异常不回滚
默认情况下, 指定发生运行时异常事务才会回滚
我们可以指定Exception异常来控制所有异常都会滚
rollbackFor = Exception.class
noRollbackFor = 回滚异常范围内, 控制某个异常不回滚
事务隔离级别
隔离级别越高越安全, 但是性能越低
- 读未提交 事务可以读取未被提交的数据, 产生脏读\不可重复读\幻读
- 读已提交 事务只能读取已经提交的数据, 可以避免脏读问题, 会产生不可重复读和幻读
- 可重复读 在一个事务中,相同的查询将返回相同的结果集(不管其他事务对数据做了什么修改),可以避免脏读\不可重复读, 但仍有幻读
- 串行化 最高的隔离级别, 完全禁止并发
可重复读是保证和之前读的结果是一致的, 幻读是没看到别的地方产生了更新
@Transactional(isolation = Isolation.REPEATABLE_READ)
事务传播行为
名称 | 含义 |
---|---|
REQUIRED 默认值 | 如果父方法有事务,就加入,如果没有就新建自己独立! |
REQUIRES_NEW | 不管父方法是否有事务,我都新建事务,都是独立的! |
@Transactional(propagation = Propagation.REQUIRED)
public void changeAge(){
studentDao.updateAgeById(99,1);
}
@Transactional(propagation = Propagation.REQUIRED)
public void changeName(){
studentDao.updateNameById("test2",1);
int i = 1/0;
}
@Transactional
public void topService(){
studentService.changeAge();
studentService.changeName();
}