1. 简介
1.1 框架介绍
- 框架这个概念源自于建筑学,隶属土木工程,后发展到软件工程领域
- 软件工程框架
- 经过验证
- 具有一定的功能
- 半成品软件
1.2 框架的作用
- 提高开发效率
- 增强可重用性
- 提供编写规范
- 节约维护成本
- 解耦底层实现原理
1.3 Spring简介
- Spring是分层的JavaSE/EE应用full-stack、轻量级、开源框架
- Spring体系结构
- 底层是核心容器
- Beans
- Core
- Context
- SpringEL表达式
- 中间层技术
- AOP
- Aspects
- 应用层技术
- 数据访问与数据集成
- Web集成
- Web实现
- 基于Test测试
- 底层是核心容器
- Spring发展史
1.4 Spring优势
- 方便解耦,简化开发
- 方便集成各种优秀框架
- 方便程序的测试
- AOP编程的支持
- 声明式事务的支持
- 降低JavaEE API的使用难度
- Java源码是经典学习范例
2. 耦合与内聚
- 耦合(Coupling)
- 代码书写过程中所使用技术的结合紧密度,用于衡量软件中各个模块之间的互联程度
- 内聚(Cohesion)
- 代码书写过程中单个模块内部各组成部分之间的联系,用于衡量软件中各个功能模块内部的功能联系
- 程序书写的目标:高内聚,低耦合
- 就是同一个模块内的各个元素之间要高度紧密,但是各个模块之间的相互依存度却不要那么紧密
3. 工厂模式发展
4. IoC介绍
- IoC(Inversion Of Control) 控制反转,Spring反向控制应用程序所需要使用的外部资源
- Spring控制的资源全部放置在Spring容器中,该容器称为IoC容器
5. IoC使用XML文件配置
5.1 bean
bean
标签,归属于beans
标签- 作用:定义spring中的资源,受此标签定义的资源将受到spring控制
- 格式:
<beans> <bean /> </beans>
- 基本属性
- id:bean的名称,通过id值获取bean
- class:bean的类型
- name:bean的名称,可以通过name值获取bean,用于多人配合时给bean起别名
5.2 scope
scope
属性,归属于bean
标签- 作用:定义bean的作用范围
- 格式:
<bean scope="singleton"></bean>
- 取值
- singleton:设定创建出的对象保存在spring容器中,是一个单例对象
- prototype:设定创建出的对象保存在spring容器中,是一个非单例的对象
- request、session、application、websocket:设定创建出的对象放置在web容器对应位置
5.3 init-method,destroy-method
init-method
属性和destroy-method
属性,归属于bean
标签- 作用:定义bean对象在初始化或销毁时完成的工作
- 格式:
<bean init-method="init" destroy-method="destroy"></bean>
- 取值:bean对应的类中对应的具体方法名
- 注意事项
- 当scope=“singleton”时,spring容器中有且仅有一个对象,init方法在创建容器时仅执行一次
- 当scope=“prototype”时,spring容器要创建同一类型的多个对象,init方法在每个对象创建时均执行一次
- 当scope=“singleton”时,关闭容器会导致bean实例的销毁,调用destroy方法一次
- 当scope=“prototype”时,对象的销毁由垃圾回收机制gc()控制,destroy方法将不会被执行
5.4 factory-method
factory-method
属性,归属于bean
标签- 作用:定义bean对象创建方式,使用静态工厂的形式创建bean,兼容早期遗留系统的升级工作
- 格式:
<bean class="FactoryClassName" factory-method="factoryMethodName"></bean>
- 取值:工厂bean中用于获取对象的静态方法名
- 注意事项
- class属性必须配置成静态工厂的类名
5.5 factory-bean
factory-bean
属性,归属于bean
标签- 作用:定义bean对象创建方式,使用实例工厂的形式创建bean,兼容早期遗留系统的升级工作
- 格式:
<bean factory-bean="factoryBeanId" factory-method="factoryMethodName"></bean>
- 取值:工厂bean中用于获取对象的实例方法名
- 注意事项
- 使用实例工厂创建bean首先需要将实例工厂配置bean,交由spring进行管理
factory-bean
是实例工厂的beanId
5.6 DI
- IoC(Inversion Of Control) 控制翻转,Spring反向控制应用程序所需要使用的外部资源
- DI(Dependency Injection) 依赖注入,应用程序运行依赖的资源由Spring为其提供,资源进入应用程序的方式称为注入
- IoC与DI的关系
- IoC与DI是同一件事站在不同的角度看待问题
5.7 set注入
property
标签,归属于bean
标签- 作用:使用set方法的形式为bean提供资源
- 格式:
<bean> <property /> </bean>
- 基本属性
- name:对应bean中的属性名,要求该属性必须提供可访问的set方法
- value:设定非引用类型属性对应的值,不能与ref同时使用
- ref:设定引用类型属性对应bean的id,不能与value同时使用
- 注意:一个bean可以有多个property标签
5.8 构造器注入
constructor-arg
标签,归属于bean
标签- 作用:使用构造方法的形式为bean提供资源,兼容早期遗留系统的升级工作
- 格式:
<bean> <constructor-arg /></bean>
- 基本属性
- name:对应bean中的构造方法所携带的参数名
- value:设定非引用类型构造方法参数对应的值,不能与ref同时使用
- 其他属性
- ref:设定引用类型构造方法参数对应bean的id,不能与value同时使用
- type:设定构造方法参数的类型,用于按类型匹配参数或进行类型校验
- index:设定构造方法参数的位置,用于按位置匹配参数,参数index值从0开始计数
- 注意:一个bean可以有多个constructor-arg标签
5.9 集合类型数据注入
array、list、set、map、props
标签,归属与property
标签或constructor-arg
标签- 作用:注入集合数据类型属性
- 格式:
<property> <list></list> </property>
<!--list-->
<property name="al">
<list>
<value>haha</value>
<value>66666</value>
</list>
</property>
<!--props-->
<property name="properties">
<props>
<prop key="name">haha666</prop>
<prop key="value">666666</prop>
</props>
</property>
<!--array-->
<property name="arr">
<array>
<value>123456</value>
<value>66666</value>
</array>
</property>
<!--set-->
<property name="hs">
<set>
<value>haha</value>
<value>66666</value>
</set>
</property>
<!--map-->
<property name="hm">
<map>
<entry key="name" value="haha66666"/>
<entry key="value" value="6666666666"/>
</map>
</property>
5.10 使用p命名空间简化配置
p:propertyName
,p:propertyName-ref
属性,归属于bean标签- 作用:为bean注入属性值
- 格式:
<bean p:propertyName="propertyValue" p:propertyName-ref="beanId"/>
- 注意:使用p命令空间需要先开启spring对p命令空间的的支持,在beans标签中添加对应空间支持
<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:p="http://www.springframework.org/schema/p" xsi:schemaLocation="http://www.springframework.org/schema/beans https://www.springframework.org/schema/beans/spring-beans.xsd">
5.11 SpEL
- Spring提供了对EL表达式的支持,统一属性注入格式
- 归属于
value
属性值 - 作用:为
bean
注入属性值 - 格式:
<property value="EL"></bean>
- 注意:所有属性值不区分是否引用类型,统一使用value赋值
- 所有格式统一使用
value=“****”
- 常量
#{10} #{3.14} #{2e5} #{‘itcast’}
- 引用
bean #{beanId}
- 引用
bean
属性#{beanId.propertyName}
- 引用
bean
方法beanId.methodName().method2()
- 引用静态方法
T(java.lang.Math).PI
- 运算符支持
#{3 lt 4 == 4 ge 3}
- 正则表达式支持
#{user.name matches‘[a-z]{6,}’}
- 集合支持
#{likes[3]}
- 常量
5.12 properties文件
- Spring提供了读取外部properties文件的机制,使用读取到的数据为bean的属性赋值
- 操作步骤
- 准备外部properties文件
- 开启context命名空间支持
xmlns:context="http://www.springframework.org/schema/context"
- 加载指定的properties文件
<context:property-placeholder location="classpath:filename.properties">
- 使用加载的数据
<property name="propertyName" value="${propertiesName}"/>
- 注意
- 如果需要加载所有的properties文件,可以使用
*.properties
表示加载所有的properties文件 - 读取数据使用
${propertiesName}
格式进行,其中propertiesName
指properties文件中的属性名
- 如果需要加载所有的properties文件,可以使用
5.13 import标签
import
标签,归属于beans
标签- 作用:在当前配置文件中导入其他配置文件中的项
- 格式:
<beans> <import /> </beans>
- 基本属性
- resource:加载的配置文件名
- Spring容器加载多个配置文件
new ClassPathXmlApplicationContext("config1.xml","config2.xml");
- Spring容器中的bean定义冲突问题
- 同id的bean,后定义的覆盖先定义的
- 导入配置文件可以理解为将导入的配置文件复制粘贴到对应位置
- 导入配置文件的顺序与位置不同可能会导致最终程序运行结果不同
5.14 第三方资源配置:阿里数据源方案Druid
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
<property name="driverClassName" value="com.mysql.jdbc.Driver"></property>
<property name="url" value="jdbc:mysql://localhost:3306/spring_ioc"></property>
<property name="username" value="root"></property>
<property name="password" value="root"></property>
</bean>
6. ApplicationContext
- ApplicationContext相关介绍
- ApplicationContext是一个接口,提供了访问spring容器的API
- ClassPathXmlApplicationContext是一个类,实现了上述功能
- ApplicationContext的顶层接口是BeanFactory
- BeanFactory定义了bean相关的最基本操作
- ApplicationContext在BeanFactory基础上追加了若干新功能
- 对比BeanFactory
- BeanFactory创建的bean采用延迟加载形式,使用才创建
- ApplicationContext创建的bean默认采用立即加载形式
- FileSystemXmlApplicationContext可以加载文件系统中任意位置的配置文件,而ClassPathXmlApplicationContext只能加载类路径下的配置文件
//BeanFactory Resource res = new ClassPathResource("applicationContext.xml"); BeanFactory bf = new XmlBeanFactory(res); UserService userService = (UserService)bf.getBean("userService");
7. IoC使用注解开发
7.1 注解驱动
- 注解驱动的意义
- 工程启动时使用注解的形式替代xml配置,将繁杂的spring配置文件从工程中彻底消除掉,简化书写
- 注解驱动的弊端
- 为了达成注解驱动的目的,可能会将原先很简单的书写,变的更加复杂
- XML中配置第三方开发的资源是很方便的,但使用注解驱动无法在第三方开发的资源中进行编辑,因此会增大开发工作量
7.2 启动注解功能
- 启动注解扫描,加载类中配置的注解项
<context:component-scan base-package="packageName"/>
- 说明:
- 在进行包所扫描时,会对配置的包及其子包中所有文件进行扫描
- 扫描过程是以文件夹递归迭代的形式进行的
- 扫描过程仅读取合法的java文件
- 扫描时仅读取spring可识别的注解
- 扫描结束后会将可识别的有效注解转化为spring对应的资源加入IoC容器
- 注意:
- 无论是注解格式还是XML配置格式,最终都是将资源加载到IoC容器中,差别仅仅是数据读取方式不同
- 从加载效率上来说注解优于XML配置文件
7.3 bean的定义
@Component @Controller @Service @Repository
为类注解- 位置:类定义上方
- 作用:设置该类为
spring
管理的bean
- 范例:
@Component public class ClassName{}
- 说明:
@Controller、@Service 、@Repository
是@Component
的衍生注解,功能同@Component
- 相关属性
- value(默认):定义bean的访问id
7.4 bean的作用域
@Scope
类注解- 位置:类定义上方
- 作用:设置该类作为bean对应的scope属性
- 范例:
@Scope public class ClassName{}
- 相关属性
- value(默认):定义bean的作用域,默认为singleton
7.5 bean的生命周期
@PostConstruct、@PreDestroy
为方法注解- 位置:方法定义上方
- 作用:设置该类作为bean对应的生命周期方法
- 范例:
@PostConstruct public void init() { System.out.println("init..."); }
7.6 加载第三方资源
@Bean
为方法注解- 位置:方法定义上方
- 作用:设置该方法的返回值作为spring管理的bean
- 范例:
@Bean("dataSource") public DruidDataSource createDataSource() { return ……; }
- 说明:
- 因为第三方bean无法在其源码上进行修改,使用
@Bean
解决第三方bean
的引入问题 - 该注解用于替代XML配置中的静态工厂与实例工厂创建
bean
,不区分方法是否为静态或非静态 @Bean
所在的类必须被spring
扫描加载,否则该注解无法生效
- 因为第三方bean无法在其源码上进行修改,使用
- 相关属性
- value(默认):定义bean的访问id
7.7 bean的非引用类型属性注入
@Value
为属性注解、方法注解- 位置:属性定义上方,方法定义上方
- 作用:设置对应属性的值或对方法进行传参
- 范例:
@Value("${jdbc.username}") private String username;
- 说明:
- value值仅支持非引用类型数据,赋值时对方法的所有参数全部赋值
- value值支持读取properties文件中的属性值,通过类属性将properties中数据传入类中
- value值支持SpEL
@value
注解如果添加在属性上方,可以省略set方法(set方法的目的是为属性赋值)
- 相关属性
- value(默认):定义对应的属性值或参数值
7.8 bean的引用类型属性注入
@Autowired、@Qualifier
为属性注解、方法注解- 位置:属性定义上方,方法定义上方
- 作用:设置对应属性的对象或对方法进行引用类型传参
- 范例:
@Autowired(required = false) @Qualifier("userDao") private UserDao userDao;
- 说明:
@Autowired
默认按类型装配,指定@Qualifier
后可以指定自动装配的bean的id
- 相关属性
- required:定义该属性是否允许为null
7.9 bean的引用类型属性注入
@Primary
为类注解- 位置:类定义上方
- 作用:设置类对应的bean按类型装配时优先装配
- 范例:
@Primary public class ClassName{}
- 说明:
@Autowired
默认按类型装配,当出现相同类型的bean
,使用@Primary
提高按类型自动装配的优先级,多个@Primary
会导致优先级设置无效
7.10 bean的引用类型属性注入
@Inject、@Named、@Resource
- 说明:
@Inject
与@Named
是JSR330规范中的注解,功能与@Autowired
和@Qualifier
完全相同,适用于不同架构场景@Resource
是JSR250规范中的注解,可以简化书写格式
@Resource
相关属性- name:设置注入的bean的id
- type:设置注入的bean的类型,接收的参数为Class类型
7.11 加载properties文件
@PropertySource
为类注解- 位置:类定义上方
- 作用:加载properties文件中的属性值
- 范例:
@PropertySource(value = "classpath:filename.properties") public class ClassName { @Value("${propertiesAttributeName}") private String attributeName; }
- 说明:
- 不支持*通配格式,一旦加载,所有spring控制的bean中均可使用对应属性值
- 相关属性
- value(默认):设置加载的properties文件名
- ignoreResourceNotFound:如果资源未找到,是否忽略,默认为false
7.12 纯注解格式
@Configuration、@ComponentScan
为类注解- 位置:类定义上方
- 作用:设置当前类为spring核心配置加载类
- 范例:
@Configuration @ComponentScan("scanPackageName") public class SpringConfigClassName{ }
- 说明:
- 核心配合类用于替换spring核心配置文件,此类可以设置空的,不设置变量与属性
- bean扫描工作使用注解·@ComponentScan·替代
- AnnotationConfigApplicationContext
- 加载纯注解格式上下文对象,需要使用AnnotationConfigApplicationContext
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(SpringConfig.class);
- 加载纯注解格式上下文对象,需要使用AnnotationConfigApplicationContext
7.13 第三方bean配置与管理
@Import
为类注解- 位置:类定义上方
- 作用:导入第三方bean作为spring控制的资源
- 范例:
@Configuration @Import(OtherClassName.class) public class ClassName { }
- 说明:
@Import
注解在同一个类上,仅允许添加一次,如果需要导入多个,使用数组的形式进行设定- 在被导入的类中可以继续使用
@Import
导入其他资源(了解) @Bean
所在的类可以使用导入的形式进入spring容器,无需声明为bean
7.14 依赖加载
@DependsOn
为类注解、方法注解- 位置:bean定义的位置(类上或方法上)
- 作用:控制bean的加载顺序,使其在指定bean加载完毕后再加载
- 范例:
@DependsOn("beanId") public class ClassName { }
- 说明
- 配置在方法上,使
@DependsOn
指定的bean
优先于@Bean
配置的bean
进行加载 - 配置在类上,使
@DependsOn
指定的bean
优先于当前类中所有@Bean
配置的bean
进行加载 - 配置在类上,使
@DependsOn
指定的bean
优先于@Component
等配置的bean
进行加载
- 配置在方法上,使
- 相关属性
- value(默认):设置当前bean所依赖的bean的id
@Order
为配置类注解- 位置:配置类定义的位置(类上)
- 作用:控制配置类的加载顺序
- 范例:
@Order public class SpringConfigClassName { }
@Lazy
为类注解、方法注解- 位置:bean定义的位置(类上或方法上)
- 作用:控制bean的加载时机,使其延迟加载
- 范例:
@Lazy public class ClassName { }
7.15 依赖加载应用场景
@DependsOn
- 微信订阅号,发布消息和订阅消息的bean的加载顺序控制
- 双11活动期间,零点前是结算策略A,零点后是结算策略B,策略B操作的数据为促销数据。策略B加载顺序与促销数据的加载顺序
@Lazy
- 程序灾难出现后对应的应急预案处理是启动容器时加载时机
@Order
- 多个种类的配置出现后,优先加载系统级的,然后加载业务级的,避免细粒度的加载控制
7.16 注解整合MyBatis
- 注解整合MyBatis分析
- 业务类使用注解形式声明bean,属性采用注解注入
- 建立独立的配置管理类,分类管理外部资源,根据功能进行分类,并提供对应的方法获取bean
- 使用注解形式启动bean扫描,加载所有注解配置的资源(bean)
- 使用AnnotationConfigApplicationContext对象加载所有的启动配置类,内部使用导入方式进行关联
- 注解整合MyBatis步骤
- 修改mybatis外部配置文件格式为注解格式
- 业务类使用@Component声明bean,使用@Autowired注入对象
- 建立配置文件JDBCConfig与MyBatisConfig类,并将其导入到核心配置类SpringConfig
- 开启注解扫描
- 使用AnnotationConfigApplicationContext对象加载配置项
7.17 注解整合Junit
- Spring接管Junit的运行权,使用Spring专用的Junit类加载器
- 为Junit测试用例设定对应的spring容器:
- 从Spring5.0以后,要求Junit的版本必须是4.12及以上
- Junit仅用于单元测试,不能将Junit的测试类配置成spring的bean,否则该配置将会被打包进入工程中
- 导入Spring整合Junit坐标
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>5.1.9.RELEASE</version>
</dependency>
- Spring整合Junit测试用例注解格式
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = SpringConfig.class)
public class UserServiceTest {
}
8. IoC底层核心原理
8.1 IoC核心接口
8.2 组件扫描器
- 开发过程中,需要根据需求加载必要的bean,排除指定bean
8.3 设定组件扫描加载过滤器
@ComponentScan
为类注解- 位置:类定义上方
- 作用:设置spring配置加载类扫描规则
- 范例:
@ComponentScan( value="com.chenfu", //设置基础扫描路径 excludeFilters = //设置过滤规则,当前为排除过滤 @ComponentScan.Filter( //设置过滤器 type= FilterType.ANNOTATION, //设置过滤方式为按照注解进行过滤 classes=Repository.class) //设置具体的过滤项,过滤所有@Repository修饰的bean )
- includeFilters:设置包含性过滤器
- excludeFilters:设置排除性过滤器
- type:设置过滤器类型
8.4 自定义组件过滤器
TypeFilter
接口- 作用:自定义类型过滤器
- 范例:
public class MyTypeFilter implements TypeFilter { public boolean match(MetadataReader mr, MetadataReaderFactory mrf) throws IOException { ClassMetadata cm = metadataReader.getClassMetadata(); tring className = cm.getClassName(); if(className.equals("com.chenfu.dao.impl.BookDaoImpl")){ return false; } return false; } }
8.5 自定义导入器
bean
只有通过配置才可以进入spring
容器,被spring
加载并控制- 配置
bean
的方式如下:- XML文件中使用
<bean/>
标签配置 - 使用
@Component
及衍生注解配置
- XML文件中使用
- 企业开发过程中,通常需要配置大量的
bean
,需要一种快速高效配置大量bean
的方式 ImportSelector
接口- 作用:自定义bean导入器
- 范例:
public class MyImportSelector implements ImportSelector { public String[] selectImports(AnnotationMetadata icm) { return new String[]{"com.itheima.dao.impl.AccountDaoImpl"}; } }
@Configuration @ComponentScan("com.itheima") @Import(MyImportSelector.class) public class SpringConfig { }
8.6 自定义注册器
ImportBeanDefinitionRegistrar
接口- 作用:自定义bean定义注册器
- 范例:
public class MyImportBeanDefinitionRegistrar implements ImportBeanDefinitionRegistrar { public void registerBeanDefinitions(AnnotationMetadata icm, BeanDefinitionRegistry r) { ClassPathBeanDefinitionScanner scanner = new ClassPathBeanDefinitionScanner(r, false); TypeFilter tf = new TypeFilter() { public boolean match(MetadataReader mr, MetadataReaderFactory mrf) throws IOException { return true; } }; scanner.addIncludeFilter(tf); //scanner.addExcludeFilter(tf); scanner.scan("com.itheima"); } }
8.7 bean初始化过程解析
8.8 bean初始化过程解析
BeanFactoryPostProcessor
- 作用:定义了在bean工厂对象创建后,bean对象创建前执行的动作,用于对工厂进行创建后业务处理
- 运行时机:当前操作用于对工厂进行处理,仅运行一次
BeanPostProcessor
- 作用:定义了所有bean初始化前后进行的统一动作,用于对bean进行创建前业务处理与创建后业务处理
- 运行时机:当前操作伴随着每个bean的创建过程,每次创建bean均运行该操作
InitializingBean
- 作用:定义了每个bean的初始化前进行的动作,属于非统一性动作,用于对bean进行创建前业务处理
- 运行时机:当前操作伴随着任意一个bean的创建过程,保障其个性化业务处理
- 注意:上述操作均需要被spring容器加载放可运行
8.9 bean初始化过程解析
8.10 繁琐的bean初始化过程处理
FactoryBean
- 对单一的bean的初始化过程进行封装,达到简化配置的目的
FactoryBean
与BeanFactory
区别FactoryBean
:封装单个bean的创建过程BeanFactory
:Spring容器顶层接口,定义了bean相关的获取操作
9. AOP
9.1 简介
- AOP)(Aspect Oriented Programing) 面向切面编程,一种编程范式,隶属于软工范畴,指导开发者如何组织程序结构
- AOP弥补了OOP的不足,基于OOP基础上进行横向开发
- uOOP规定程序开发以类为主题模型,一切围绕对象进行,完成某个任务先构建模型
- uAOP程序开发主要关注基于OOP开发中的共性功能,一切围绕共性功能进行,完成某个任务先构建可能遇到的所有共性功能(当所有功能都开发出来也就没有共性与非共性之分)
9.2 作用
- 伴随着AOP时代的降临,可以从各个行业的标准化、规范化开始入手,一步一步将所有共性功能逐一开发完毕,最终以功能组合来完成个别业务模块乃至整体业务系统的开发
- 目标:将软件开发由手工制作走向半自动化/全自动化阶段,实现“插拔式组件体系结构”
- 搭建
9.3 优势
- 提高代码的可重用性
- 业务代码编码更简洁
- 业务代码维护更高效
- 业务功能扩展更便捷
9.4 相关概念
- Joinpoint(连接点):就是方法
- Pointcut(切入点):就是挖掉共性功能的方法
- Advice(通知):就是共性功能,最终以一个方法的形式呈现
- Aspect(切面):就是共性功能与挖的位置的对应关系
- Target(目标对象):就是挖掉功能的方法对应的类产生的对象,这种对象是无法直接完成最终工作的
- Weaving(织入):就是将挖掉的功能回填的动态过程
- Proxy(代理):目标对象无法直接完成工作,需要对其进行功能回填,通过创建原始对象的代理对象实现
- Introduction(引入/引介):就是对原始对象无中生有的添加成员变量或成员方法
9.5 AOP开发过程
- 开发阶段(开发者完成)
- 正常的制作程序
- 将非共性功能开发到对应的目标对象类中,并制作成切入点方法
- 将共性功能独立开发出来,制作成通知
- 在配置文件中,声明切入点
- 在配置文件中,声明切入点与通知间的关系,即切面
- 运行阶段(AOP完成)
- Spring容器加载配置文件,监控所有配置的切入点方法的执行
- 当监控到切入点方法被运行,使用代理机制,动态创建目标对象的代理对象,根据通知类别,在代理对象的对应位置将通知对应的功能织入,完成完整的代码逻辑并运行
9.6 入门案例
- 导入相关坐标
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.9.4</version>
</dependency>
- 确认要抽取的功能,并将其制作成方法保存到专用的类中,删除原始业务中对应的功能
- 将所有进行AOP操作的资源加载到IoC容器中
- 使用配置的方式描述被抽取功能的位置,并描述抽取功能与对应位置的关系
<!--aop配置-->
<aop:config>
<!--配置切入点-->
<aop:pointcut id="pt" expression="execution(* *..*())"/>
<!--配置切面-->
<aop:aspect ref="myAdvice">
<!—通知与切入点之间的关系-->
<aop:before method="logAdvice" pointcut-ref="pt"/>
</aop:aspect>
</aop:config>
- 运行程序
10. AOP使用XML文件配置
10.1 AspectJ
- Aspect(切面)用于描述切入点与通知间的关系,是AOP编程中的一个概念
- AspectJ是基于java语言对Aspect的实现
10.2 AOP配置
10.2.1 aop:config
aop:config
标签,归属于beans
标签- 作用:设置AOP
- 格式:
<beans> <aop:config>……</aop:config> <aop:config>……</aop:config> </beans>
- 说明:一个beans标签中可以配置多个aop:config标签
10.2.2 aop:aspect
aop:aspect
标签,归属于aop:config
标签- 作用:设置具体的AOP通知对应的切入点
- 格式:
<aop:config> <aop:aspect ref="beanId">……</aop:aspect> <aop:aspect ref="beanId">……</aop:aspect> </aop:config>
- 说明:
一个aop:config
标签中可以配置多个aop:aspect
标签 - 基本属性:
- ref :通知所在的bean的id
10.2.3 aop:pointcut
aop:pointcut
标签,归属于aop:config
标签、aop:aspect
标签- 作用:设置切入点
- 格式:
<aop:config> <aop:pointcut id="pointcutId" expression="……"/> <aop:aspect> <aop:pointcut id="pointcutId" expression="……"/> </aop:aspect> </aop:config>
- 说明:
- 一个
aop:config
标签中可以配置多个aop:pointcut
标签,且该标签可以配置在aop:aspect
标签内
- 一个
- 基本属性:
- id :识别切入点的名称
- expression :切入点表达式
10.3 切入点
- 切入点描述的是某个方法
- 切入点表达式是一个快速匹配方法描述的通配格式,类似于正则表达式
10.4 切入点表达式的组成
- 切入点描述的是某个方法
- 切入点表达式是一个快速匹配方法描述的通配格式,类似于正则表达式
关键字(访问修饰符 返回值 包名.类名.方法名(参数)异常名)
- 关键字:描述表达式的匹配模式(参看关键字列表)
- 访问修饰符:方法的访问控制权限修饰符
- 类名:方法所在的类(此处可以配置接口名称)
- 异常:方法定义中指定抛出的异常
- 范例:
execution(public User com.itheima.service.UserService.findById(int))
10.4.1 切入点表达式——关键字
- execution :匹配执行指定方法
- args :匹配带有指定参数类型的方法
- within :……
- this :……
- target :……
- @within :……
- @target :……
- @args :……
- @annotation :……
- bean :……
- reference pointcut :……
10.4.2 切入点表达式——通配符
*
:单个独立的任意符号,可以独立出现,也可以作为前缀或者后缀的匹配符出现execution(public * com.chenfu.*.UserService.find*(*))
- 匹配com.chenfu包下的任意包中的UserService类或接口中所有find开头的带有一个参数的方法
..
:多个连续的任意符号,可以独立出现,常用于简化包名与参数的书写execution(public User com..UserService.findById(..))
- 匹配com包下的任意包中的UserService类或接口中所有名称为findById的方法
+
:专用于匹配子类类型execution(* *..*Service+.*(..))
10.4.3 切入点表达式——逻辑运算符
&&
:连接两个切入点表达式,表示两个切入点表达式同时成立的匹配||
:连接两个切入点表达式,表示两个切入点表达式成立任意一个的匹配!
:连接单个切入点表达式,表示该切入点表达式不成立的匹配
10.4.4 切入点表达式——范例
execution(* *(..))
execution(* *..*(..))
execution(* *..*.*(..))
execution(public * *..*.*(..))
execution(public int *..*.*(..))
execution(public void *..*.*(..))
execution(public void com..*.*(..))
execution(public void com..service.*.*(..))
execution(public void com.itheima.service.*.*(..))
execution(public void com.itheima.service.User*.*(..))
execution(public void com.itheima.service.*Service.*(..))
execution(public void com.itheima.service.UserService.*(..))
execution(public User com.itheima.service.UserService.find*(..))
execution(public User com.itheima.service.UserService.*Id(..))
execution(public User com.itheima.service.UserService.findById(..))
execution(public User com.itheima.service.UserService.findById(int))
execution(public User com.itheima.service.UserService.findById(int,int))
execution(public User com.itheima.service.UserService.findById(int,*))
execution(public User com.itheima.service.UserService.findById(*,int))
execution(public User com.itheima.service.UserService.findById())
execution(List com.itheima.service.*Service+.findAll(..))
10.5 切入点的三种配置方式
<aop:config>
<!--配置公共切入点-->
<aop:pointcut id="pt1" expression="execution(* *(..))"/>
<aop:aspect ref="myAdvice">
<!--配置局部切入点-->
<aop:pointcut id="pt2" expression="execution(* *(..))"/>
<!--引用公共切入点-->
<aop:before method="logAdvice" pointcut-ref="pt1"/>
<!--引用局部切入点-->
<aop:before method="logAdvice" pointcut-ref="pt2"/>
<!--直接配置切入点-->
<aop:before method="logAdvice" pointcut="execution(* *(..))"/>
</aop:aspect>
</aop:config>
10.6 切入点配置经验
- 企业开发命名规范严格遵循规范文档进行
- 先为方法配置局部切入点
- 再抽取类中公共切入点
- 最后抽取全局切入点
- 代码走查过程中检测切入点是否存在越界性包含
- 代码走查过程中检测切入点是否存在非包含性进驻
- 设定AOP执行检测程序,在单元测试中监控通知被执行次数与预计次数是否匹配
- 设定完毕的切入点如果发生调整务必进行回归测试
(以上规则适用于XML配置格式)
10.7 通知
10.7.1 五种通知类型
- 前置通知
- 原始方法执行前执行,如果通知中抛出异常,阻止原始方法运行
- 应用:数据校验
- 后置通知
- 原始方法执行后执行,无论原始方法中是否出现异常,都将执行通知
- 应用:现场清理
- 返回后通知
- 原始方法正常执行完毕并返回结果后执行,如果原始方法中抛出异常,无法执行
- 应用:返回值相关数据处理
- 抛出异常后通知
- 原始方法抛出异常后执行,如果原始方法没有抛出异常,无法执行
- 应用:对原始方法中出现的异常信息进行处理
- 环绕通知
- 在原始方法执行前后均有对应执行执行,还可以阻止原始方法的执行
- 应用:十分强大,可以做任何事情
10.7.2 aop:before
aop:before
标签,归属于aop:aspect
标签- 作用:设置前置通知
- 格式:
<aop:aspect ref="adviceId"> <aop:before method="methodName" pointcut="……"/> </aop:aspect>
- 说明:一个
aop:aspect
标签中可以配置多个aop:before
标签 - 基本属性:
- method :在通知类中设置当前通知类别对应的方法
- pointcut :设置当前通知对应的切入点表达式,与pointcut-ref属性冲突
- pointcut-ref :设置当前通知对应的切入点id,与pointcut属性冲突
10.7.3 aop:after
aop:after
标签,归属于aop:aspect
标签- 作用:设置后置通知
- 格式:
<aop:aspect ref="adviceId"> <aop:after method="methodName" pointcut="……"/> </aop:aspect>
- 说明:一个
aop:aspect
标签中可以配置多个aop:after
标签 - 基本属性:
- method :在通知类中设置当前通知类别对应的方法
- pointcut :设置当前通知对应的切入点表达式,与pointcut-ref属性冲突
- pointcut-ref :设置当前通知对应的切入点id,与pointcut属性冲突
10.7.4 aop:after-returning
aop:after-returning
标签,归属于aop:aspect
标签- 作用:设置返回后通知
- 格式:
<aop:aspect ref="adviceId"> <aop:after-returning method="methodName" pointcut="……"/> </aop:aspect>
- 说明:一个aop:aspect标签中可以配置多个aop:after-returning标签
- 基本属性:
- method :在通知类中设置当前通知类别对应的方法
- pointcut :设置当前通知对应的切入点表达式,与pointcut-ref属性冲突
- pointcut-ref :设置当前通知对应的切入点id,与pointcut属性冲突
10.7.5 aop:after-throwing
aop:after-throwing
标签,归属于aop:aspect
标签- 作用:设置抛出异常后通知
- 格式:
<aop:aspect ref="adviceId"> <aop:after-throwing method="methodName" pointcut="……"/> </aop:aspect>
- 说明:一个
aop:aspect
标签中可以配置多个aop:after-throwing
标签 - 基本属性:
- method :在通知类中设置当前通知类别对应的方法
- pointcut :设置当前通知对应的切入点表达式,与pointcut-ref属性冲突
- pointcut-ref :设置当前通知对应的切入点id,与pointcut属性冲突
10.7.6 aop:around
aop:around
标签,归属于aop:aspect
标签- 作用:设置环绕通知
- 格式:
<aop:aspect ref="adviceId"> <aop:around method="methodName" pointcut="……"/> </aop:aspect>
- 说明:一个
aop:aspect
标签中可以配置多个aop:around
标签 - 基本属性:
- method :在通知类中设置当前通知类别对应的方法
- pointcut :设置当前通知对应的切入点表达式,与pointcut-ref属性冲突
- pointcut-ref :设置当前通知对应的切入点id,与pointcut属性冲突
10.7.7 环绕通知的开发方式
- 环绕通知是在原始方法的前后添加功能,在环绕通知中,存在对原始方法的显式调用
public Object around(ProceedingJoinPoint pjp) throws Throwable { Object ret = pjp.proceed(); return ret; }
- 环绕通知方法相关说明:
- 方法须设定Object类型的返回值,否则会拦截原始方法的返回。如果原始方法返回值类型为void,通知方 也可以设定返回值类型为void,最终返回null
- 方法需在第一个参数位置设定ProceedingJoinPoint对象,通过该对象调用proceed()方法,实现对原始方法的调用。如省略该参数,原始方法将无法执行
- 使用proceed()方法调用原始方法时,因无法预知原始方法运行过程中是否会出现异常,强制抛出Throwable对象,封装原始方法中可能出现的异常信息
10.7.8 通知顺序
- 当同一个切入点配置了多个通知时,通知会存在运行的先后顺序,该顺序以通知配置的顺序为准
10.7.9 通知获取参数数据
- 第一种情况:
- 设定通知方法第一个参数为JoinPoint,通过该对象调用getArgs()方法,获取原始方法运行的参数数组
public void before(JoinPoint jp) throws Throwable { Object[] args = jp.getArgs(); }
- 所有的通知均可以获取参数
- 设定通知方法第一个参数为JoinPoint,通过该对象调用getArgs()方法,获取原始方法运行的参数数组
- 第二种情况:
- 设定切入点表达式为通知方法传递参数(锁定通知变量名)
- 原始方法
- 第三种情况
- 设定切入点表达式为通知方法传递参数(改变通知变量名的定义顺序)
- 原始方法
10.7.10 通知获取返回值数据
- 第一种:返回值变量名
- 设定返回值变量名
- 原始方法
public int save() { System.out.println("user service running..."); return 100; }
- AOP配置
<aop:aspect ref="myAdvice"> <aop:pointcut id="pt3" expression="execution(* *(..)) "/> <aop:after-returning method="afterReturning" pointcut-ref="pt3" returning="ret"/> </aop:aspect>
- 通知类
public void afterReturning(Object ret) { System.out.println(ret); }
- 适用于返回后通知(after-returning)
- 第二种:
- 在通知类的方法中调用原始方法获取返回值
- 原始方法
public int save() { System.out.println("user service running..."); return 100; }
- AOP配置
<aop:aspect ref="myAdvice"> <aop:pointcut id="pt2" expression="execution(* *(..)) "/> <aop:around method="around" pointcut-ref="pt2" /> </aop:aspect>
- 通知类
public Object around(ProceedingJoinPoint pjp) throws Throwable { Object ret = pjp.proceed(); return ret; }
- 适用于环绕通知(around)
10.7.11 通知获取异常数据
- 第一种:通知类的方法中调用原始方法捕获异常
- 在通知类的方法中调用原始方法捕获异常
- 原始方法
public void save() { System.out.println("user service running..."); int i = 1/0; }
- AOP配置
<aop:aspect ref="myAdvice"> <aop:pointcut id="pt4" expression="execution(* *(..)) "/> <aop:around method="around" pointcut-ref="pt4" /> </aop:aspect>
- 通知类
public Object around(ProceedingJoinPoint pjp) throws Throwable { Object ret = pjp.proceed(); //对此处调用进行try……catch……捕获异常,或抛出异常 return ret; }
- 适用于环绕通知(around)
- 第二种:
- 设定异常对象变量名
- 原始方法
public void save() { System.out.println("user service running..."); int i = 1/0; }
- AOP配置
<aop:aspect ref="myAdvice"> <aop:pointcut id="pt4" expression="execution(* *(..)) "/> <aop:after-throwing method="afterThrowing" pointcut-ref="pt4" throwing="t"/> </aop:aspect>
- 通知类
public void afterThrowing(Throwable t){ System.out.println(t.getMessage()); }
- 适用于返回后通知(after-throwing)
11. AOP使用注解配置
11.1 AOP配置
11.2 注解开发AOP制作步骤
- 导入坐标(伴随spring-context坐标导入已经依赖导入完成)
- 开启AOP注解支持
- 配置切面@Aspect
- 定义专用的切入点方法,并配置切入点@Pointcut
- 为通知方法配置通知类型及对应切入点@Before
注意
- 在XML格式基础上
11.3 注解开发AOP注意事项
- 切入点最终体现为一个方法,无参无返回值,无实际方法体内容,但不能是抽象方法
- 引用切入点时必须使用方法调用名称,方法后面的()不能省略
- 切面类中定义的切入点只能在当前类中使用,如果想引用其他类中定义的切入点使用“类名.方法名()”引用
- 可以在通知类型注解后添加参数,实现XML配置中的属性,例如after-returning后的returning属性
11.4 AOP注解详解
11.4.1 @Aspect
- 位置:类定义上方
- 作用:设置当前类为切面类
- 格式:
@Aspect public class AopAdvice { }
- 说明:一个
beans
标签中可以配置多个aop:config
标签
11.4.2 @Pointcut
- 位置:方法定义上方
- 作用:使用当前方法名作为切入点引用名称
- 格式:
@Pointcut("execution(* *(..))") public void pt() { }
- 说明:被修饰的方法忽略其业务功能,格式设定为无参无返回值的方法,方法体内空实现(非抽象)
11.4.3 @Before
- 位置:方法定义上方
- 作用:标注当前方法作为前置通知
- 格式:
@Before("pt()") public void before(){ }
11.4.4 @After
- 位置:方法定义上方
- 作用:标注当前方法作为后置通知
- 格式:
@After("pt()") public void after(){ }
11.4.5 @AfterReturning
- 位置:方法定义上方
- 作用:标注当前方法作为返回后通知
- 格式:
@AfterReturning(value="pt()",returning = "ret") public void afterReturning(Object ret) { }
- 特殊参数:
- returning :设定使用通知方法参数接收返回值的变量名
11.4.6 @AfterThrowing
- 位置:方法定义上方
- 作用:标注当前方法作为异常后通知
- 格式:
@AfterThrowing(value="pt()",throwing = "t") public void afterThrowing(Throwable t){ }
- 特殊参数:
- throwing :设定使用通知方法参数接收原始方法中抛出的异常对象名
11.4.7 @Around
- 位置:方法定义上方
- 作用:标注当前方法作为环绕通知
- 格式:
@Around("pt()") public Object around(ProceedingJoinPoint pjp) throws Throwable { Object ret = pjp.proceed(); return ret; }
11.5 AOP注解开发通知执行顺序控制
- AOP使用XML配置情况下,通知的执行顺序由配置顺序决定,在注解情况下由于不存在配置顺序的概念的概念,参照通知所配置的方法名字符串对应的编码值顺序,可以简单理解为字母排序
- 同一个通知类中,相同通知类型以方法名排序为准
- 不同通知类中,以类名排序为准
- 使用@Order注解通过变更bean的加载顺序改变通知的加载顺序
- 企业开发经验
- 通知方法名由3部分组成,分别是前缀、顺序编码、功能描述
- 前缀为固定字符串,例如baidu、itheima等,无实际意义
- 顺序编码为6位以内的整数,通常3位即可,不足位补0
- 功能描述为该方法对应的实际通知功能,例如exception、strLenCheck
- 制通知执行顺序使用顺序编码控制,使用时做一定空间预留
- 003使用,006使用,预留001、002、004、005、007、008
- 使用时从中段开始使用,方便后期做前置追加或后置追加
- 最终顺序以运行顺序为准,以测试结果为准,不以设定规则为准
11.6 AOP注解驱动
@EnableAspectJAutoProxy
- 位置:Spring注解配置类定义上方
- 作用:设置当前类开启AOP注解驱动的支持,加载AOP注解
- 格式:
@Configuration @ComponentScan("com.itheima") @EnableAspectJAutoProxy public class SpringConfig { }
12. AOP底层原理
12.1 静态代理
- 装饰者模式(Decorator Pattern):在不惊动原始设计的基础上,为其添加功能
public class UserServiceDecorator implements UserService{
private UserService userService;
public UserServiceDecorator(UserService userService) {
this.userService = userService;
}
public void save() {
//原始调用
userService.save();
//增强功能(后置)
System.out.println("刮大白");
}
}
12.2 动态代理——JDK Proxy
- JDKProxy动态代理是针对对象做代理,要求原始对象具有接口实现,并对接口方法进行增强
public class UserServiceJDKProxy {
public UserService createUserServiceJDKProxy(final UserService userService){
//获取被代理对象的类加载器
ClassLoader classLoader = userService.getClass().getClassLoader();
//获取被代理对象实现的接口
Class[] classes = userService.getClass().getInterfaces();
//对原始方法执行进行拦截并增强
InvocationHandler ih = new InvocationHandler() {
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//前置增强内容
Object ret = method.invoke(userService, args);
//后置增强内容
System.out.println("刮大白2");
return ret;
}
};
//使用原始被代理对象创建新的代理对象
UserService proxy = (UserService) Proxy.newProxyInstance(classLoader,classes,ih);
return proxy;
}
}
12.3 动态代理——CGLIB
- CGLIB(Code Generation Library),Code生成类库
- CGLIB动态代理不限定是否具有接口,可以对任意操作进行增强
- CGLIB动态代理无需要原始被代理对象,动态创建出新的代理对象
public class UserServiceImplCglibProxy {
public static UserServiceImpl createUserServiceCglibProxy(Class clazz){
//创建Enhancer对象(可以理解为内存中动态创建了一个类的字节码)
Enhancer enhancer = new Enhancer();
//设置Enhancer对象的父类是指定类型UserServerImpl
enhancer.setSuperclass(clazz);
Callback cb = new MethodInterceptor() {
public Object intercept(Object o, Method m, Object[] a, MethodProxy mp) throws Throwable {
Object ret = mp.invokeSuper(o, a);
if(m.getName().equals("save")) {
System.out.println("刮大白");
}
return ret;
}
};
//设置回调方法
enhancer.setCallback(cb);
//使用Enhancer对象创建对应的对象
return (UserServiceImpl)enhancer.create();
}
}
12.4 代理模式的选择
- Spirng可以通过配置的形式控制使用的代理形式,默认使用jdkproxy,通过配置可以修改为使用cglib
- XML配置
<!--XMP配置AOP--> <aop:config proxy-target-class="false"></aop:config>
- XML注解支持
<!--注解配置AOP--> <aop:aspectj-autoproxy proxy-target-class="false"/>
- 注解驱动
//注解驱动 @EnableAspectJAutoProxy(proxyTargetClass = true)
12.5 织入时机
13. spring 事务
13.1 事务
- 概念:事务指数据库中多个操作合并在一起形成的操作序列
- 作用:
- 当数据库操作序列中个别操作失败时,提供一种方式使数据库状态恢复到正常状态(A),保障数据库即使在异常状态下仍能保持数据一致性(C)(要么操作前状态,要么操作后状态)。
- 当出现并发访问数据库时,在多个访问间进行相互隔离,防止并发访问操作结果互相干扰(I)。
- 事务特征(ACID)
- 原子性(Atomicity)指事务是一个不可分割的整体,其中的操作要么全执行或全不执行
- 一致性(Consistency)事务前后数据的完整性必须保持一致
- 隔离性(Isolation)事务的隔离性是多个用户并发访问数据库时,数据库为每一个用户开启的事务,不能被其他事务的操作数据所干扰,多个并发事务之间要相互隔离
- 持久性(Durability)持久性是指一个事务一旦被提交,它对数据库中数据的改变就是永久性的,接下来即使数据库发生故障也不应该对其有任何影响
- 事务的隔离级
- 脏读
- 允许读取未提交的信息
- 原因:Read uncommitted
- 解决方案: (表级读锁)
- 不可重复读
- 读取过程中单个数据发生了变化
- 解决方案: Repeatable read (行级写锁)
- 幻读
- 读取过程中数据条目发生了变化
- 解决方案: Serializable(表级写锁)
- 脏读
13.2 Spring事务核心对象
13.2.1 核心对象介绍
- J2EE开发使用分层设计的思想进行,对于简单的业务层转调数据层的单一操作,事务开启在业务层或者数据层并无太大差别,当业务中包含多个数据层的调用时,需要在业务层开启事务,对数据层中多个操作进行组合并归属于同一个事务进行处理
- Spring为业务层提供了整套的事务解决方案
- PlatformTransactionManager
- TransactionDefinition
- TransactionStatus
13.2.2 PlatformTransactionManager
- 平台事务管理器实现类
- DataSourceTransactionManager 适用于Spring JDBC或MyBatis
- HibernateTransactionManager 适用于Hibernate3.0及以上版本
- JpaTransactionManager 适用于JPA
- JdoTransactionManager 适用于JDO
- JtaTransactionManager 适用于JTA
- JPA、JDO、JTA
- JPA(Java Persistence API)Java EE 标准之一,为POJO提供持久化标准规范,并规范了持久化开发的统一API,符合JPA规范的开发可以在不同的JPA框架下运行
- JDO(Java Data Object )是Java对象持久化规范,用于存取某种数据库中的对象,并提供标准化API。与JDBC相比,JDBC仅针对关系数据库进行操作,JDO可以扩展到关系数据库、文件、XML、对象数据库(ODBMS)等,可移植性更强
- JTA(Java Transaction API)Java EE 标准之一,允许应用程序执行分布式事务处理。与JDBC相比,JDBC事务则被限定在一个单一的数据库连接,而一个JTA事务可以有多个参与者,比如JDBC连接、JDO 都可以参与到一个JTA事务中
- 此接口定义了事务的基本操作
- 获取事务 :
TransactionStatus getTransaction(TransactionDefinition definition)
- 提交事务 :
void commit(TransactionStatus status)
- 回滚事务 :
void rollback(TransactionStatus status)
- 获取事务 :
13.2.3 TransactionDefinition
- 此接口定义了事务的基本信息
- 获取事务定义名称
String getName()
- 获取事务的读写属性
boolean isReadOnly()
- 获取事务隔离级别
int getIsolationLevel()
- 获事务超时时间
int getTimeout()
- 获取事务传播行为特征
int getPropagationBehavior()
- 获取事务定义名称
13.2.4 TransactionStatus
- 此接口定义了事务在执行过程中某个时间点上的状态信息及对应的状态操作
13.3 事务控制方式
- 三种方式
- 编程式
- XML配置文件声明式
- 注解声明式
- 案例
- 银行转账业务说明
- 银行转账操作中,涉及从A账户到B账户的资金转移操作。数据层仅提供单条数据的基础操作,未设计多账户间的业务操作。
- 案例环境(基于Spring、Mybatis整合)
- 业务层接口提供转账操作
/** * 转账操作 * @param outName 出账用户名 * @param inName 入账用户名 * @param money 转账金额 */ public void transfer(String outName,String inName,Double money);
- 业务层实现提供转账操作
public void transfer(String outName,String inName,Double money){ accountDao.inMoney(outName,money); accountDao.outMoney(inName,money); }
- 数据层提供对应的入账与出账操作
<update id="inMoney"> update account set money = money + #{money} where name = #{name} </update> <update id="outMoney"> update account set money = money - #{money} where name = #{name} </update>
- 业务层接口提供转账操作
- 编程式事务
public void transfer(String outName,String inName,Double money){ //创建事务管理器 DataSourceTransactionManager dstm = new DataSourceTransactionManager(); //为事务管理器设置与数据层相同的数据源 dstm.setDataSource(dataSource); //创建事务定义对象 TransactionDefinition td = new DefaultTransactionDefinition(); //创建事务状态对象,用于控制事务执行 TransactionStatus ts = dstm.getTransaction(td); accountDao.inMoney(outName,money); int i = 1/0; //模拟业务层事务过程中出现错误 accountDao.outMoney(inName,money); //提交事务 dstm.commit(ts); }
13.4 使用AOP控制事务
- 将业务层的事务处理功能抽取出来制作成AOP通知,利用环绕通知运行期动态织入
public Object tx(ProceedingJoinPoint pjp) throws Throwable { DataSourceTransactionManager dstm = new DataSourceTransactionManager(); dstm.setDataSource(dataSource); TransactionDefinition td = new DefaultTransactionDefinition(); TransactionStatus ts = dstm.getTransaction(td); Object ret = pjp.proceed(pjp.getArgs()); dstm.commit(ts); return ret; }
- 配置AOP通知类,并注入dataSource
<bean id="txAdvice" class="com.itheima.aop.TxAdvice"> <property name="dataSource" ref="dataSource"/> </bean>
- 使用环绕通知将通知类织入到原始业务对象执行过程中
<aop:config> <aop:pointcut id="pt" expression="execution(* *..transfer(..))"/> <aop:aspect ref="txAdvice"> <aop:around method="tx" pointcut-ref="pt"/> </aop:aspect> </aop:config>
13.5 声明式事务(XML)
13.5.1 示例
- AOP配置事务是否具有特例性?
public Object tx(ProceedingJoinPoint pjp) throws Throwable {
DataSourceTransactionManager dstm = new DataSourceTransactionManager();
dstm.setDataSource(dataSource);
TransactionDefinition td = new DefaultTransactionDefinition();
TransactionStatus ts = dstm.getTransaction(td);
Object ret = pjp.proceed(pjp.getArgs());
dstm.commit(ts);
return ret;
}
<bean id="txAdvice" class="com.itheima.aop.TxAdvice">
<property name="dataSource" ref="dataSource"/>
</bean>
- 使用tx命名空间配置事务专属通知类
<tx:advice id="txAdvice" transaction-manager="txManager">
<tx:attributes>
<tx:method name="*" read-only="false" />
<tx:method name="get*" read-only="true" />
<tx:method name="find*" read-only="true" />
</tx:attributes>
</tx:advice>
- 使用aop:advisor在AOP配置中引用事务专属通知类
<aop:config>
<aop:pointcut id="pt" expression="execution(* *..*(..))"/>
<aop:advisor advice-ref="txAdvice" pointcut-ref="pt"/>
</aop:config>
13.5.2 aop:advice与aop:advisor区别
- aop:advice配置的通知类可以是普通java对象,不实现接口,也不使用继承关系
- aop:advisor配置的通知类必须实现通知接口
- MethodBeforeAdvice
- AfterReturningAdvice
- ThrowsAdvice
- ……
13.5.3 tx配置----tx:advice
tx:advice
标签,归属于beans标签- 作用:专用于声明事务通知
- 格式:
<beans> <tx:advice id="txAdvice" transaction-manager="txManager"> </tx:advice> </beans>
- 基本属性:
- id :用于配置aop时指定通知器的id
- transaction-manager :指定事务管理器bean
13.5.4 tx配置----tx:attributes
tx:attributes
标签,归属于tx:advice
标签- 作用:定义通知属性
- 格式:
<tx:advice id="txAdvice" transaction-manager="txManager"> <tx:attributes> </tx:attributes> </tx:advice>
13.5.5 tx配置----tx:method
tx:method
标签,归属于tx:attribute
标签- 作用:设置具体的事务属性
- 格式:
<tx:attributes> <tx:method name="*" read-only="false" /> <tx:method name="get*" read-only="true" /> </tx:attributes>
- 说明:
- 通常事务属性会配置多个,包含1个读写的全事务属性,1个只读的查询类事务属性
- tx:method属性
13.6 事务传播
- 事务管理员
- 事务协调员
- 事务传播行为描述的是事务协调员对事务管理员所携带事务的处理态度
- 事务传播应用
- 场景A:生成订单业务
- 子业务S1:记录日志到数据库表X(需要新事务)
- 子业务S2:保存订单数据到数据库表Y
- …
- 如果S2或S3或……事务提交失败,此时S1是否回滚?如何控制?
- 场景B:生成订单业务
- 背景1:订单号生成依赖数据库中一个专门用于控制订单号编号生成的表M获取
- 背景2:每次获取完订单号,表M中记录的编号自增1
- 子业务S1:从表M中获取订单编号(S1需要新事务)
- 子业务S2:保存订单数据,订单编号来自于表M
- ……
- 如果S2或S3或……事务提交失败,此时S1是否回滚?如何控制?
- 场景A:生成订单业务
13.7 声明式事务(注解)
13.7.1 @Transactional
@Transactional
为方法注解,类注解,接口注解- 位置:方法定义上方,类定义上方,接口定义上方
- 作用:设置当前类/接口中所有方法或具体方法开启事务,并指定相关事务属性
- 范例:
@Transactional( readOnly = false, timeout = -1, isolation = Isolation.DEFAULT, rollbackFor = {ArithmeticException.class, IOException.class}, noRollbackFor = {}, propagation = Propagation.REQUIRES_NEW )
13.7.2 tx:annotation-driven
tx:annotation-driven
标签,归属于beans
标签- 作用:开启事务注解驱动,并指定对应的事务管理器
- 范例:
<tx:annotation-driven transaction-manager="txManager"/>
13.8 声明式事务(纯注解驱动)
@EnableTransactionManagement
为类注解- 位置:Spring注解配置类上方
- 作用:开启注解驱动,等同XML格式中的注解驱动
- 范例:
@Configuration @ComponentScan("com.itheima") @PropertySource("classpath:jdbc.properties") @Import({JDBCConfig.class,MyBatisConfig.class,TransactionManagerConfig.class}) @EnableTransactionManagement public class SpringConfig { }
public class TransactionManagerConfig { @Bean public PlatformTransactionManager getTransactionManager(@Autowired DataSource dataSource){ return new DataSourceTransactionManager(dataSource); } }
13.9 模板对象
13.9.1 Spring模块对象
- TransactionTemplate
- JdbcTemplate
- RedisTemplate
- RabbitTemplate
- JmsTemplate
- HibernateTemplate
- RestTemplate
13.9.2 JdbcTemplate
- 提供标准的sql语句操作API
public void save(Account account) {
String sql = "insert into account(name,money)values(?,?)";
jdbcTemplate.update(sql,account.getName(),account.getMoney());
}
13.9.3 NamedParameterJdbcTemplate
- 提供标准的具名sql语句操作API
public void save(Account account) {
String sql = "insert into account(name,money)values(:name,:money)";
Map pm = new HashMap();
pm.put("name",account.getName());
pm.put("money",account.getMoney());
jdbcTemplate.update(sql,pm);
}
13.9.4 RedisTemplate
- RedisTemplate对象结构
public void changeMoney(Integer id, Double money) {
redisTemplate.opsForValue().set("account:id:"+id,money);
}
public Double findMondyById(Integer id) {
Object money = redisTemplate.opsForValue().get("account:id:" + id);
return new Double(money.toString());
}
13.10 事务底层原理
- 策略模式(Strategy Pattern)
- 使用不同策略的对象实现不同的行为方式,策略对象的变化导致行为的变化。
- 使用不同策略的对象实现不同的行为方式,策略对象的变化导致行为的变化。