typora-root-url: ..\media
Spring
一.简介
1.是什么
业务逻辑组件框架程序员的春天
SSH
- Spring Struts1 Hibernate
SSH
- Spring Struts2 Hibernate
SSH
- Spring SpringMVC Hibernate
SSM
- Spring SpringMVC MyBatis
Test:spring的单元测试模块
Core Container:核心容器(IOC),黑色代表这部分功能有哪些jar包,要使用这部分的完整功能,要导入这些jar包。
- spring-context-5.2.5.RELEASE.jar
- spring-beans-5.2.5.RELEASE.jar
- spring-core-5.2.5.RELEASE.jar
- spring-expression-5.2.5.RELEASE.jar
AOP+Aspects:面向切面编程模块
DataAccess 数据访问
- spring-jdbc-5.2.5.RELEASE
- spring-orm(object-relation-mapping)-5.2.5.RELEASE.
- Transaction spring-tx-5.2.5.RELEASE 事务
WEB
- spring-websocket 新技术
- spring-web-5.2.5.RELEASE.jar 和原生的web相关(servlet)
- spring-webmvc-5.2.5.RELEASE.jar 开发web项目的(mvc)
- spring-webmvc-portlet-5.2.5.RELEASE.jar 开发web应用的组件集成
2.主要内容
IOC
Inversion of Control
- 控制反转
- 使得组件的关系松散
AOP
Aspect Orientied Programming
- 面向切面编程
- 使得应用易于扩展
通用支持
- 整合其他框架
二.IOC
1.简介
Inversion of Control控制反转
凡是你所需要的对象并不是由自己所准备的而是由其他组件控制你所获取的对象的时候
简单来讲:我们所需要进行的操作是受到其他组件控制的
这种情况就称之为控制反转
注重的是结果
2.DI
Dependency injection
依赖注入
凡是你所需要使用的对象的属性的值不是由自己所准备的
而是由其他组件控制你的属性的值的时候
简单来讲:我们所需要操作的过程是收到其他组件所控制的
这种情况称之为依赖注入
注重的是过程
3.IDEA中使用Spring
第一步:导包(加入依赖),
第二步:配置
第三步:写测试方法
初始情况下,IDEA中无法自动创建Spring的配置文件但是当IDEA中对应的工程导入了Spring的基本依赖的时候
此时IDEA将会提供自动创建Spring配置文件的方式
导入Spring基本依赖
<properties>
<spring.version>5.2.5.RELEASE</spring.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-core</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-beans</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-expression</artifactId>
<version>${spring.version}</version>
</dependency>
</dependencies>
4.Spring配置文件
<bean id="userDao" class="dao.impl.UserDaoImpl"></bean>
<!--
id属性:当前bean的key
class属性:当前bean所对应的Java类的包名.类名
标签体:配置当前bean的属性的值
-->
<bean id="userService" class="service.impl.UserServiceImpl">
<!--
property标签:配置某一个属性,一个property标签对应一个属性
name属性:当前的属性名是谁
value属性:为当前的属性赋予一个简单值
ref属性:其他bean的引用
表示当前的属性的值是另一个由Spring所管理的bean
此时ref属性的值对应的是所管理的bean的id属性值
-->
<property name="userDao" ref="userDao"/>
</bean>
5.加载Spring容器
BeanFactory
- Spring容器核心
- 用于解析Spring容器
- 读取容器中的内容
ApplicationContext
- BeanFactory的子接口
- 简化了解析的操作
使用
ClassPathXmlApplicationContext
实现类- 其参数可以是一个字符串
- 也可以是一个字符串数组
- 也可以是一个可变长字符串
- 还支持通配符
public static void main(String[] args) {
ApplicationContext ac = new ClassPathXmlApplicationContext("applicationContext*.xml");
UserService userService = (UserService) ac.getBean("userService");
userService.login();
}
几个注意点:
- ApplicationContext:ioc容器的接口
- 给容器中注册了一个组件,我们也从容器中按照id拿到了这个组件。组建的创建工作是容器完成的
- 容器中对象的创建在容器创建完成的时候就已经创建好了
- 同一个组件在容器中是单实例的
- 容器中如果没有组件,获取组件会报异常
- ioc容器在创建对象的时候会用set方法为javabean的属性赋值
- javaBean的属性名是由getter/setter方法决定的,不要随意更改。getter/setter都自动生成。
6.装配
6-1 简单值装配
简单值:基本数据类型、String、包装类型、Class、Resource通过value属性或者value子标签实现简单值装配
<property name="name" value="张三"/>
<property name="age" value="18"/>
<property name="gender" value="男"/>
<!-- 简单值装配方式一 -->
<bean id="someBean" class="ioc01.SomeBean">
<!-- value属性:为当前指定的属性赋予一个简单值 -->
<property name="id" value="1"/>
<property name="name" value="admin"/>
<property name="salary" value="1000.0"/>
</bean>
<!-- 简单值装配方式二 -->
<bean id="someBean2" class="ioc01.SomeBean">
<!-- 可以使用value子标签进行简单值装配,效果与value属性一致 -->
<property name="id">
<value>2</value>
</property>
<property name="name">
<value>alice</value>
</property>
<property name="salary">
<value>2000.0</value>
</property>
</bean>
6-2 其他bean的引用
当当前的bean中的属性的值是另一个已经存在的bean的实例的时候可以使用ref属性或者ref标签进行其他bean的引用
通过ref属性或者ref子标签实现其他
<!-- 其他bean的引用 -->
<bean id="otherBean" class="ioc02.OtherBean">
<property name="id" value="1"/>
<property name="name" value="admin"/>
</bean>
<!-- 方式一 -->
<bean id="someBean" class="ioc02.SomeBean">
<!--
ref属性:指向当前容器中已经存在的另一个bean的id属性值
-->
<property name="otherBean" ref="otherBean"/>
</bean>
<!-- 方式二 -->
<bean id="someBean2" class="ioc02.SomeBean">
<!--
ref子标签:指定当前属性所引用的bean
bean属性:指向当前容器中已经存在的另一个bean的id属性值
-->
<property name="otherBean">
<ref bean="otherBean"></ref>
</property>
</bean>
7.实例化
实例化-->DI-->初始化-->使用-->销毁
7-1 实例化的途径
无参构造函数实例化对象
<bean id="someBean" class="ioc04.SomeBean"></bean>
有参构造函数实例化对象
- 通过
constructor-arg
标签来实现有参构造函数的配置 - 一个标签对应构造函数中的一个参数
index属性:指定当前参数的索引位置
- 索引从0开始
- 如果不设置该属性,默认按照参数顺序进行执行
type属性:指定当前参数的类型
- 该属性一般可以不配置,Spring自动检测参数的类型
<bean id="someBean2" class="ioc04.SomeBean">
<constructor-arg index="1" type="java.lang.String">
<value>admin</value>
</constructor-arg>
<constructor-arg index="0" type="java.lang.Integer">
<value>1</value>
</constructor-arg>
</bean>
静态工厂实例化对象
工厂类.静态方法()
实例工厂对象.方法()
静态工厂:工厂本身不用创建对象。通过工厂类的静态方法调用。
实例工厂:工厂本身需要创建对象。
- 静态工厂创建对象的时候,class指向的是当前对应的工厂类的class
- 但是如果仅仅只是通过class指向工厂,根本无法获取到我们想要的bean
- 该bean是根据工厂中对应的方法获取的
- 因此需要通过
factory-method
进行指定所调用的静态方法名
<!-- factory-method:返回的不是class配置的类型,而是通过工厂方法返回的类型-->
<bean id="person" class="com.hy.entity.PersonFactory"
factory-method="getPersonStatic">
</bean>
实例工厂实例化对象
- 该bean是通过工厂bean中的方法获取的
- 因此该bean需要调用指定的工厂bean中的方法
- 在配置中需要指定所使用的工厂bean与工厂方法
factory-bean
- 表示当前所调用的工厂bean是谁
- 其值是当前容器所管理的工厂bean的id值
factory-method
- 生产当前bean的工厂bean所使用的方法名
- 此处为实例方法
<!--配置工厂对象-->
<bean id="factory" class="com.hy.entity.PersonFactory2"></bean>
<!-- factory-bean:工厂bean,填写工厂类的id factory-method:使用哪个方法-->
<bean id="p1" factory-bean="factory" factory-method="getPerson"></bean>
7-2 初始化销毁方法
实例化===》属性注入===》aware接口的方法执行===》init-method执行了
init-method
- 将bean中的指定的方法设置为初始化方法
- 当方法被设置为初始化方法之后,其执行时可以获取到DI的值
- 该方法将会在DI之后,使用之前进行自动执行
destroy-method
- 将bean中指定的方法设置为销毁方法
- 将方法设置为销毁方法之后,Spring可以主动进行销毁
- 此时的销毁是强制执行的,该方法是由
ClassPathXmlApplicationContext
实现的 - 在
ApplicationContext
接口中没有提供销毁方法 - 因此,必须直接定义实现类
ClassPathXmlApplicationContext
提供了两种销毁方法
destroy()
- 调用方法的时候立刻销毁
- 不关心当前的bean是否已经使用完毕
- 因此,调用方法之后,如果仍然使用容器中的bean会报错
registerShutdownHook()
- 调用方法的时候不会立刻销毁
- 当虚拟机彻底停止之后才会进行销毁
- 因此,调用方法之后,仍然可以使用容器中的bean
<bean id="someBean" class="ioc07.SomeBean" init-method="a" destroy-method="b">
<property name="name" value="admin"/>
</bean>
public class Test {
public static void main(String[] args) {
// ApplicationContext ac = new ClassPathXmlApplicationContext("ioc07/spring.xml");
// SomeBean someBean = (SomeBean) ac.getBean("someBean");
// System.out.println(someBean);
// someBean.a();
// 执行销毁方法
// 在java中,我们只能对销毁方法做建议
// 无法强制其执行
// Runtime.getRuntime().gc();
ClassPathXmlApplicationContext ac = new ClassPathXmlApplicationContext("ioc07/spring.xml");
// 销毁方式一
ac.destroy();
// 销毁方式二
// ac.registerShutdownHook();
// System.out.println("Test.main");
// SomeBean someBean = (SomeBean) ac.getBean("someBean");
// System.out.println(someBean);
while (true){
}
}
}
7-3 组件作用域
默认情况下,IOC容器中所有的bean都是单例的有些时候,我们需要多个不同的实例
可以通过scope属性更改组件作用域
使得bean不再是单例的
scope属性:组件作用域,值有两种
- prototype:每一次调用都会生成一个新的实例
- singleton:默认值,单例
<bean id="someBean" class="ioc09.SomeBean" scope="prototype"></bean>
<bean id="someBean2" class="ioc09.SomeBean"></bean>
单实例的bean在容器启动完成之前就已经创建好对象,保存在容器中。任何时候获取都是之前创建好的实例。
多实例情况下:容器启动不会创建多实例bean。在获取的时候创建。
8.aware相关接口
将当前的bean实现ApplicationContextAware接口重写其中的setApplicationContext方法
将其作为一个注入操作
其容器的实例是由Spring帮我们传递的
传递到对应的
setApplicationContext
方法的参数中将该实例传递到当前的全局变量中,则当前bean中可以获取到当前容器的实例
public class SomeBean implements ApplicationContextAware {
private ApplicationContext ac;
@Override
public void setApplicationContext(ApplicationContext ac) throws BeansException {
this.ac = ac;
}
public void doSome(){
// ApplicationContext ac = new ClassPathXmlApplicationContext("ioc10/spring.xml");
System.out.println("doSome-ac:"+ac);
}
}
10.Resource
Resource属于简单值装配j2ee的规范
11.FactoryBean
11-1 简介
FactoryBean是Spring规定的一个接口,只要是这个接口的实现类,Spring都认为是一个工厂。
该bean本质上是一个工厂该bean并不是用于创建自己的
而是为了创建另一个bean
在某些时候,某些bean的实例化过程比较麻烦
但是对于使用者而言,只关心最终生成的bean
需要的仅仅只是最终的bean
其过程使用者并不关心
11-2 开发步骤
创建一个Java类
- 该类属于FactoryBean,用于生产一个对应的bean
- 该类要求实现FactoryBean接口
重写其中的三个方法
getObject()
:生产bean的过程getObjectType()
:生产的bean的类型isSingleton()
:生产的bean是否是单例的
public class DateFactoryBean implements FactoryBean {
@Override
public Object getObject() throws Exception {
Calendar c = Calendar.getInstance();
Date date = c.getTime();
return date;
}
@Override
public Class<?> getObjectType() {
return Date.class;
}
@Override
public boolean isSingleton() {
return false;
}
}
配置FactoryBean
- 在IOC容器中配置你所需要的的FactoryBean
- 该FactoryBean是用于产生某个具体的bean的
- 因此,配置的FactroyBean就相当于最终所需要的的bean
- 只是其class指向的是当前的FactoryBean
<bean id="date" class="ioc14.DateFactoryBean"></bean>
12.后处理bean
12-1 简介
对当前容器中的所有的bean做一个后处理在初始化之前或者之后做一些额外的处理
实例化-->DI-->
postProcessBeforeInitialization
-->初始化-->
postProcessAfterInitialization
-->使用-->销毁后处理bean并不是针对某一个bean进行处理
而是针对整个IOC容器
后处理bean是对整个IOC容器中所有的bean都生效
12-2 开发步骤
创建一个Java类
- 该类实现BeanPostProcessor接口
根据Spring版本不同,处理也不同
- Spring5之前,必须重写接口中的两个方法
- Spring5之后,可以选择性的重写某个方法
public class SomeBeanPostProcessor implements BeanPostProcessor {
/**
* 在初始化之前做后处理
* @param bean 当前需要处理的bean
* @param id 当前需要处理的bean的id
* @return
* @throws BeansException
*/
@Override
public Object postProcessBeforeInitialization(Object bean, String id) throws BeansException {
// 判断当前正在处理的bean是谁
// 对SomeBean的name做大写处理
if(bean instanceof SomeBean){
SomeBean someBean = (SomeBean) bean;
someBean.setName(someBean.getName().toUpperCase());
// return someBean;
}
// 对OtherBean的name做小写处理
if("otherBean".equals(id)){
OtherBean otherBean = (OtherBean) bean;
otherBean.setName(otherBean.getName().toLowerCase());
// return otherBean;
}
return bean;
}
/**
* 在初始化之后做后处理
* @param bean
* @param id
* @return
* @throws BeansException
*/
@Override
public Object postProcessAfterInitialization(Object bean, String id) throws BeansException {
return bean;
}
}
配置后处理bean
- 由于后处理bean并不是针对某一个bean,而是为所有的bean服务
- 因此配置的后处理bean不需要id属性
- 只需要指定谁是后处理bean即可
<bean id="someBean" class="ioc15.SomeBean">
<property name="id" value="1"/>
<property name="name" value="admin"/>
</bean>
<bean id="otherBean" class="ioc15.OtherBean">
<property name="name" value="Jack"/>
</bean>
<!-- 配置后处理bean -->
<bean class="ioc15.SomeBeanPostProcessor"/>
13.访问properties文件
driver=com.mysql.jdbc.Driver
url=jdbc:mysql://127.0.0.1:3306/mysql?useUnicode=true&characterEncoding=utf-8
username=gxh
password=123
public class SomeBean {
private String driver;
private String url;
private String username;
private String password;
<!-- 访问properties文件 -->
<!--
方式一:使用Spring提供的后处理bean进行访问
该方式已过时
-->
<!--<bean class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">-->
<!--<property name="location" value="classpath:ioc17/dataSource.properties"/>-->
<!--</bean>-->
<!-- 方式二:使用context命名空间提供的方法 -->
<context:property-placeholder location="classpath:ioc17/dataSource.properties"/>
<bean id="someBean" class="ioc17.SomeBean">
<property name="driver" value="${driver}"/>
<property name="url" value="${url}"/>
<property name="username" value="${username}"/>
<property name="password" value="${password}"/>
</bean>
数据库作为连接池是单例的是最合适的,所以可以让spring管理连接池。
14 bean的生命周期
![]()
三.AOP
1.简介
AOP:Aspect Oriented Programming:面向切面编程OOP:Object Oriented Programming:面向对象编程
面向切面编程
使得应用易于扩展
开闭原则
将应用中所需要使用的交叉业务逻辑提取出来封装成切面
由AOP容器在适当的时机
将这些切面织入到具体的业务逻辑中
指在运行期间,将某段代码动态的切入到指定方法的指定位置。
为了让一些非核心业务的代码和核心代码分离。
应用场景:
比如说日志管理;
事务管理;
methodA(){
开启事务;
try{
commit();
}catch(){
rollback;
}finally{
close()
}
}
2.目的
解耦合
- 将具体的核心业务逻辑与交叉业务逻辑相分离
切面的复用
- 一个切面被多次使用
独立模块化
- 例如原本交叉业务逻辑做的打印操作
- 将打印操作统一更换为日志操作
- 只要切面改了,所有的业务均跟着改变
- 独立模块化的前提是切面的复用
- 在不改变原有功能的基础上,增加新的功能
3.动态代理
AOP是基于动态代理来实现的动态代理是根据目标类的类加载器、代理的接口、交叉业务逻辑
生成对应的目标类的代理类
public class LogInvocationHandler implements InvocationHandler {
private Object target;
public LogInvocationHandler(Object target) {
this.target = target;
}
/**
* 交叉业务逻辑
* @param proxy 代理对象,完全没用
* @param method 需要代理的目标方法
* @param args 目标方法的参数列表
* @return 目标方法的返回值
* @throws Throwable
*/
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("在"+new SimpleDateFormat("yyyy年MM月dd日 HH:mm:ss").format(new Date())+"执行了"+method.getName()+"方法");
return method.invoke(target,args);
}
}
public static void main(String[] args) {
System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles","true");
SomeService someService = (SomeService) Proxy.newProxyInstance(
SomeServiceImpl.class.getClassLoader(),
SomeServiceImpl.class.getInterfaces(),
new LogInvocationHandler(new SomeServiceImpl())
);
someService.doSome();
System.out.println("---------------------------");
someService.doOther();
System.out.println("----------------------------------------");
OtherService otherService = (OtherService) Proxy.newProxyInstance(
OtherServiceImpl.class.getClassLoader(),
OtherServiceImpl.class.getInterfaces(),
new LogInvocationHandler(new OtherServiceImpl())
);
otherService.doSome();
System.out.println("----------------------");
otherService.doOther();
}
4.AOP
4.1 术语
连接点
- 类里面哪些方法可以被增强
切入点(真正关注的点)
- 实际真正被增强的方法
通知(增强)
- 实际增强的逻辑部分称为通知(增强)
通知有多种类型
- 前置通知 before
- 后置通知 afterRerurning
- 环绕通知 round
- 异常通知 afterThrowing
- 最终通知 after
切面
- 是个动作,把通知应用到切入点的的过程就叫做切面
连接点:可以切入通知方法的点
切入点:真正去做切入的点
从连接点中选出切入点的表达式:切入点表达式
4-2 POM依赖
spring框架一般都是基于AspectJ实现AOP操作
AspectJ不是spring的组成部分,独立aop框架。一般把AspectJ和Spring框架一起使用,进行AOP操作。
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aop</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>cglib</groupId>
<artifactId>cglib</artifactId>
<version>3.3.0</version>
</dependency>
<!-- https://mvnrepository.com/artifact/aopalliance/aopalliance -->
<dependency>
<groupId>aopalliance</groupId>
<artifactId>aopalliance</artifactId>
<version>1.0</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.aspectj/aspectjweaver -->
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.9.5</version>
</dependency>
4-3 步骤
1.导包
2.创建一个类,在类里面定义方法(核心业务类和接口)
3.创建增强类,书写增强方法
4.进行通知的配置
作用:表明对哪个类里面的哪个方法进行增强
execution(权限修饰符 返回类型 类全路径 方法名 方法参数(参数列表))
注意点:
1.getBean返回的类型是什么类型?如果通过类型去获取,怎么获取?
2.切入点表达式的范围?
4-4 通知类型
前置通知 before
- 在执行核心业务逻辑之前执行
后置通知
正常返回通知 afterReturning
- 当方法正常执行结束,没有遇到异常的时候执行
异常通知 afterThrowing
- 当方法运行出现异常的时候执行
(最终)后置通知 after
- 不管方法是否遇到异常均会执行
环绕通知 around
- 包含以上所有
4-5 无参通知
public class LogAdvice {
public void a(){
System.out.println("前置通知");
}
public void b(){
System.out.println("正常返回通知");
}
public void c(){
System.out.println("异常通知");
}
public void d(){
System.out.println("后置通知");
}
}
<!-- 定义目标类 -->
<bean id="someService" class="aop04.SomeServiceImpl"></bean>
<bean id="otherService" class="aop04.OtherServiceImpl"></bean>
<!-- 定义通知 -->
<bean id="logAdvice" class="aop04.LogAdvice"></bean>
<!--
引入了新的命名空间AOP,用于定义AOP的相关配置
引入了切点表达式,用于表示当前切面应用于哪些业务逻辑
-->
<aop:config>
<!--
定义切入点
表示需要使用对应交叉业务逻辑的核心业务逻辑有些哪些
简单来讲,即哪些类中的哪些方法需要使用交叉业务逻辑
id属性:当前切入点的唯一性标识符
expression属性:指定切点表达式
-->
<!-- 匹配SomeServiceImpl中的所有方法 -->
<aop:pointcut id="pc1" expression="within(aop04.SomeServiceImpl)"></aop:pointcut>
<!--
定义切面,即通知
ref属性:定义当前通知所使用的通知类是谁
其属性值对应的是bean的id属性值
before子标签:配置前置通知
after-returning子标签:正常返回通知
after-throwing子标签:异常通知
after:后置通知
method属性:当前通知所对应方法是bean中哪一个
pointcut-ref属性:当前切面所使用的切入点是谁
-->
<aop:aspect ref="logAdvice">
<aop:before method="a" pointcut-ref="pc1"/>
<aop:after-returning method="b" pointcut-ref="pc1"/>
<aop:after-throwing method="c" pointcut-ref="pc1"/>
<aop:after method="d" pointcut-ref="pc1"/>
</aop:aspect>
</aop:config>
4-6 切点表达式
execution
- 匹配你想要的一切
- 可以是类,可以是方法
- 语法:
execution(返回值类型 包名.类名.方法名(参数列表))
支持通配符用法
*
- 用法一:匹配0或者多个字符
- 用法二:匹配一个参数
..
- 表示匹配0或者多个参数
- 匹配任意多个路径
支持连接条件
- and:且的意思,多个条件必须同时满足
- or:或的意思,多个条件只要满足任意一个即可
not:非的意思,匹配不满足条件的方法
- 使用not前面必须有空格
<!-- 匹配SomeServiceImpl中的所有无参无返回值的方法 -->
<aop:pointcut id="pc1" expression="execution(void aop04.SomeServiceImpl.doSome())"></aop:pointcut>
<aop:pointcut id="pc1" expression="execution(java.lang.String aop04.SomeServiceImpl.doSome(java.lang.String))"></aop:pointcut>
<aop:pointcut id="pc1" expression="execution(* aop04.SomeServiceImpl.do*(*))"></aop:pointcut>
<aop:pointcut id="pc1" expression="execution(* aop04.SomeServiceImpl.do*(..))"></aop:pointcut>
<aop:pointcut id="pc1" expression="execution(* aop04.*.*(..))"></aop:pointcut>
<aop:pointcut id="pc1" expression="execution(* aop04.SomeServiceImpl.doSome(..))"></aop:pointcut>
<aop:pointcut id="pc2" expression="execution(* aop04.OtherServiceImpl.doOther(..))"></aop:pointcut>
<aop:pointcut id="pc1" expression="execution(* aop04.SomeServiceImpl.doSome(..)) or execution(* aop04.OtherServiceImpl.doOther(..))"></aop:pointcut>
<aop:pointcut id="pc1" expression="execution(* aop04.SomeServiceImpl.*(..)) or execution(* aop04.OtherServiceImpl.doOther(..))"></aop:pointcut>
<!-- 匹配SomeServiceImpl中的所有无参方法 -->
<aop:pointcut id="pc1" expression="execution(* aop04.SomeServiceImpl.*())"></aop:pointcut>
<!-- 匹配aop04包下所有除了doSome以外的方法 -->
<aop:pointcut id="pc1" expression="execution(* aop04.*.*(..)) and not execution(* aop04.*.doSome(..))"></aop:pointcut>
4-7 有参通知
如何获取与目标方法相关的信息
此时需要使用有参通知来实现
使用有参通知的时候,其参数必须是:
JoinPoint
注意:
通知方法的参数不能随便乱写。通知方法是Spring利用反射调用的。每次调用都需要确认参数是什么?
异常类型写Exception。
public class LogAdvice {
public void before(JoinPoint jp){
// 获取目标类
Object target = jp.getThis();
// 获取目标方法签名
Signature signature = jp.getSignature();
//获取方法名
String name = signature.getName();
// 获取目标方法参数列表
Object[] args = jp.getArgs();
System.out.println("前置通知:"+target+"中的"+signature.getName()+"方法即将执行");
}
//Object:指定通知方法可以接收的结果类型
public void afterReturning(JoinPoint jp, Object returnValue){
System.out.println("正常返回通知:"+jp.getSignature().getName()+"方法执行完成,方法返回值为:"+returnValue);
}
//Exception:指定通知方法可以接收的异常
public void afterThrowing(JoinPoint jp, Exception e){
System.out.println("异常通知:"+jp.getSignature().getName()+"方法执行出现了异常,异常为:"+e);
}
public void after(JoinPoint jp){
System.out.println("后置通知:"+jp.getSignature().getName()+"方法执行结束");
}
}
<bean id="someService" class="aop05.SomeServiceImpl"></bean>
<bean id="logAdvice" class="aop05.LogAdvice"></bean>
<aop:config>
<aop:pointcut id="pc1" expression="within(aop05.SomeServiceImpl)"/>
<aop:aspect ref="logAdvice">
<aop:before method="before" pointcut-ref="pc1"/>
<!--
returning属性:指定当前方法中的哪一个参数作为目标方法的返回值
-->
<aop:after-returning method="afterReturning" pointcut-ref="pc1" returning="returnValue"/>
<!--
throwing属性:指定当前方法中的哪一个参数作为接收目标方法异常的参数
-->
<aop:after-throwing method="afterThrowing" pointcut-ref="pc1" throwing="e"/>
<aop:after method="after" pointcut-ref="pc1"/>
</aop:aspect>
</aop:config>
4-8 环绕通知
spring中最强大的通知方法。
try{
//前置通知
method.invoke(obj,args);
//返回通知
}catch(){
//异常通知
}finally{
//后置通知
}
四合一通知就是环绕通知
环绕通知优先于其他通知执行。
环绕通知中有一个参数ProceedingJoinPoint pjp
4-9执行顺序:
环绕通知对方法存在三个要求
方法必须有返回值
- 返回值类型必须是Object
- 表示的就是目标方法的返回值
方法必须有参数
- 参数为:
proceedingJoinPoint
- 该参数是
JoinPoint
的子类 - 可以获取与目标方法相关的信息
- 可以用于执行目标方法
- 参数为:
- 方法必须抛出
Throwable
public class AroundAdvice {
//ProceedingJoinPoint
public Object around(ProceedingJoinPoint jp) throws Throwable{
Object returnValue = null;
System.out.println("环绕通知之前置通知");
long begin = System.currentTimeMillis();
try {
// jp.proceed();利用反射调用目标方法的执行,就是method.invoke(obj,args)
returnValue = jp.proceed();
long end = System.currentTimeMillis();
System.out.println("环绕通知之正常返回通知,执行方法共花费了"+(end-begin)+"毫秒");
} catch (Throwable throwable) {
System.out.println("环绕通知之异常通知");
} finally {
System.out.println("环绕通知之后置通知");
}
return returnValue;
}
}
Spring要求对通知方法的参数不能乱写。通知方法是spring利用反射调用的,所以每次方法调用得确定这个方法参数表的值。不知道是什么的参数一定要告诉spring是什么。
5-8 优先级
默认情况下根据配置的位置而定
谁的配置在前,谁的优先级高
优先级高的前置通知执行时机更前
优先级高的后置通知执行时机更后
在切面中进行配置,在
aop:aspect
标签中通过order
属性定义优先级值越小,优先级越高
<aop:aspect ref="logAdvice" order="2">
<aop:before method="before" pointcut-ref="pc1"/>
<aop:after-returning method="afterReturning" pointcut-ref="pc1" returning="returnValue"/>
<aop:after-throwing method="afterThrowing" pointcut-ref="pc1" throwing="e"/>
<aop:after method="after" pointcut-ref="pc1"/>
</aop:aspect>
<aop:aspect ref="aroundAdvice" order="1">
<aop:around method="around" pointcut-ref="pc1"/>
</aop:aspect>
5-9 抽取可重用的切入点表达式
步骤:
1.随便声明一个没有实现的void方法
2.给方法上加@PointCut注解
3.在通知方法上加上@Before("方法名()")
四.Annotation
Spring注解是从2.5开始在主流的SSM开发中,一般是配置文件+注解
在主流的SpringBoot开发中,一般是纯注解
1.IOC注解
普通bean
@Component
- 该注解标注在类上,表示该类是一个受Spring管理的bean
- 该注解可以传递参数,也可以不传递参数
当注解存在参数的时候
- 该参数即为当前bean的id属性值
当注解不存在参数的时候
- 当前bean的id默认为当前类名,首字母小写
持久层bean
@Repository
- 用于与普通bean一致
业务层bean
@Service
- 用法与普通bean一致
控制器bean
@Controller
- 详情请期待SpringMVC
注入值
@Value("值")
- 可以访问properties文件
- 当访问properties文件之后,需要使用配置文件+注解的方式实现
自动装配
@Autowired
- 自动根据byType或者byName查找对应的bean
- 自动识别
@Autowired@Qualifier("ob")
- 根据bean的id进行装配
- 如果没有找到对应的id值的bean
- 则会报错
- 不建议使用
@Component
public class SomeBean {
// @Autowired
@Autowired@Qualifier("ob")
private OtherBean otherBean;
@Component
public class OtherBean {
// @Value("1")
@Value("${someBean.id}")
private Integer id;
// private Integer id = 1;
// @Value("admin")
@Value("${someBean.name}")
private String name;
// private String name = "admin";
<context:property-placeholder location="classpath:bean.properties"/>
<!--
配置扫描注解所在包的操作
当配置了之后,Spring会自动扫描指定的包以及其子包中所有的注解
-->
<context:component-scan base-package="bean"/>
<!--实例1:use-default-filters:表示不适用默认filter。自己配置filter,include-filter:设置扫描哪些内容-->
<context:component-scan base-package="com" use-default-filters="false">
<!--type=assignable 按照类-->
<context:include-filter type="annotation" expression="org.springframework.stereotype.Controller"/>
</context:component-scan>
<!--实例2:配置com下的所有包,exclude-filter:设置哪些内容不扫描-->
<context:component-scan base-package="com">
<context:exclude-filter type="annotation" expression="org.springframework.stereotype.Service"/>
</context:component-scan>
someBean.id=1
someBean.name=admin
2.AOP注解
2-1 注解用法
切入点
- 在使用注解的时候,切点表达式也是一个方法
- 通过
@Pointcut(切点表达式)
进行标注 - 该方法可以没有方法体的内容,是一个空方法
- 定义了切入点之后,当前切入点的id即为当前方法名
切面
- 在类上使用
@Aspect
进行标注 - 表示当前类是一个切面的类
- 由于切面的类也是由Spring所管理的bean
- 因此,需要使用
@Component
进行标注
- 在类上使用
通知
前置通知
@Before(value="切入点")
value属性指定当前通知所使用的切入点是谁
如果注解的属性中只有
value="xxx"
这种情况下- 此时关键字
value=
可以省略
- 此时关键字
如果value属性的值不是一个字符串或者注解属性不止一个value
- 此时关键字
value=
不可以省略
- 此时关键字
正常返回通知
AfterReturning(..)
- value属性:指定当前通知所使用的切入点是谁
- returning属性:指定当前接收目标方法返回值的参数
异常通知
@AfterThrowing(..)
- value属性:指定当前通知所使用的切入点是谁
- throwing属性:指定当前接收目标方法异常的参数
后置通知
@After(..)
- value属性:指定当前通知所使用的切入点是谁
环绕通知
@Around(..)
- value属性:指定当前通知所使用的切入点是谁
@Component
@Aspect
public class LogAdvice {
//抽取可重复使用的切入点表达式
//声明一个void的方法
@Pointcut(value="execution(* bean.*.*(..))")
public void pointcutName(){};
@Before(value = "pointcutName()")
public void before(JoinPoint jp){
System.out.println("前置通知");
}
@AfterReturning(value = "pointcutName()",returning = "returnValue")
public void afterReturning(JoinPoint jp, Object returnValue){
System.out.println("正常返回通知,返回值为:"+returnValue);
}
@AfterThrowing(value = "pointcutName()",throwing = "e")
public void afterThrowing(JoinPoint jp,Exception e){
System.out.println("异常通知,异常为:"+e);
}
@After("pointcutName()")
public void after(JoinPoint jp){
System.out.println("后置通知");
}
@Around("pointcutName()")
public Object around(ProceedingJoinPoint jp) throws Throwable{
Object returnValue = null;
try {
System.out.println("环绕通知之前置通知");
returnValue = jp.proceed();
System.out.println("环绕通知之正常返回通知");
} catch (Throwable throwable) {
System.out.println("环绕通知之异常通知");
} finally {
System.out.println("环绕通知之后置通知");
}
return returnValue;
}
}
2-2 配置
<!-- 使用注解AOP需要进行配置 -->
<!-- 有两种方式进行配置 -->
<!-- 方式一:使用Spring提供的后处理bean -->
<bean class="org.springframework.aop.aspectj.annotation.AnnotationAwareAspectJAutoProxyCreator"></bean>
<!-- 方式二:使用AOP命名空间来实现 -->
<aop:aspectj-autoproxy/>
2-3 Advice执行顺序
注解版AOP通知的执行顺序与配置文件版不一致
正常返回时的执行顺序
- 环绕通知前置==》@Before==》执行目标方法==》@AfterReturning==>@After==>环绕通知返回==》环绕通知结束
出现异常时的执行顺序
- 环绕通知前置==》@Before==》执行目标方法==》@AfterThrowing==>@After==>环绕通知异常==》环绕通知结束
多个切面执行顺序
2-4 AOP 使用场景
- 日志管理
- 权限管理
- 安全检查
事务控制
- 前置通知 设置非自动提交
- 返回通知 提交事务
- 后置通知 关闭资源
- 异常通知 回滚
五.通用支持
与其他组件的整合
1.数据源的配置
Spring内置数据源
DriverManagerDataSource
第三方数据源
BasicDataSource
1-1 使用Spring内置的
- POM配置
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.38</version>
</dependency>
- 数据源配置
<bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
<property name="driverClassName" value="com.mysql.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://127.0.0.1:3306/?useUnicode=true&characterEncoding=utf-8"/>
<property name="username" value="gxh"/>
<property name="password" value="123"/>
</bean>
1-2 使用第三方的
- POM配置
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.38</version>
</dependency>
<dependency>
<groupId>commons-dbcp</groupId>
<artifactId>commons-dbcp</artifactId>
<version>1.4</version>
</dependency>
数据源配置
- 此处以properties文式为例
jdbc.driver=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://127.0.0.1:3306/mysql?useUnicode=true&characterEncoding=utf-8
jdbc.username=root
jdbc.password=
jdbc.maxActive=1
jdbc.init=1
jdbc.maxWait=3000
<context:property-placeholder location="classpath:dataSource.properties"/>
<bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource">
<property name="driverClassName" value="${jdbc.driver}"/>
<property name="url" value="${jdbc.url}"/>
<property name="username" value="${jdbc.username}"/>
<property name="password" value="${jdbc.password}"/>
<!-- 最大连接数 -->
<property name="maxActive" value="${jdbc.maxActive}"/>
<!-- 初始化连接数 -->
<property name="initialSize" value="${jdbc.init}"/>
<!-- 最大等待时间 -->
<property name="maxWait" value="${jdbc.maxWait}"/>
</bean>
2.JDBC支持
2-1 使用JdbcTemplate
使用Spring提供的模板类JdbcTemplate将JdbcTemplate注入给对应的dao
将dataSource注入给JdbcTemplate
1.查询所有用户,返回list
2.查询某个用户,返回对象
3.插入一个用户
4.批量插入用户
5.返回用户数量
6.删除一个用户
public class UserDaoImpl implements UserDao {
private JdbcTemplate jdbcTemplate;
public void setJdbcTemplate(JdbcTemplate jdbcTemplate) {
this.jdbcTemplate = jdbcTemplate;
}
@Override
public void insertUser(User user) {
String sql = new StringBuffer()
.append(" insert into ")
.append(" t_user ")
.append(" (username,password,phone,address)")
.append(" values ")
.append(" (?,?,?,?)")
.toString();
jdbcTemplate.update(sql,user.getUsername(),user.getPassword(),user.getPhone(),user.getAddress());
}
@Override
public void deleteById(Integer id) {
}
@Override
public void updateUser(User user) {
}
@Override
public User selectById(Integer id) {
String sql = new StringBuffer()
.append(" select id,username,password,phone,address ")
.append(" from t_user ")
.append(" where id = ? ")
.toString();
List<User> users = jdbcTemplate.query(sql,new UserRowMapper(),id);
return users.isEmpty()?null:users.get(0);
}
@Override
public List<User> selectAll() {
return null;
}
@Override
public Long insertReturnPrimaryKey(User user) {
KeyHolder keyHolder = new GeneratedKeyHolder();
jdbcTemplate.update(new PreparedStatementCreator() {
@Override
public PreparedStatement createPreparedStatement(Connection conn) throws SQLException {
String sql = new StringBuffer()
.append(" insert into ")
.append(" t_user ")
.append(" (username,password,phone,address)")
.append(" values ")
.append(" (?,?,?,?)")
.toString();
PreparedStatement ps = conn.prepareStatement(sql,PreparedStatement.RETURN_GENERATED_KEYS);
ps.setString(1,user.getUsername());
ps.setString(2,user.getPassword());
ps.setString(3,user.getPhone());
ps.setString(4,user.getAddress());
return ps;
}
},keyHolder);
return (Long) keyHolder.getKey();
}
}
<context:property-placeholder location="classpath:dataSource.properties"/>
<bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource">
<property name="driverClassName" value="${jdbc.driver}"/>
<property name="url" value="${jdbc.url}"/>
<property name="username" value="${jdbc.username}"/>
<property name="password" value="${jdbc.password}"/>
<!-- 最大连接数 -->
<property name="maxActive" value="${jdbc.maxActive}"/>
<!-- 初始化连接数 -->
<property name="initialSize" value="${jdbc.init}"/>
<!-- 最大等待时间 -->
<property name="maxWait" value="${jdbc.maxWait}"/>
</bean>
<!-- 配置JdbcTemplate,将dataSource注入给JdbcTemplate -->
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
<property name="dataSource" ref="dataSource"/>
</bean>
<!-- 配置userDao,将JdbcTemplate注入给UserDao -->
<bean id="userDao" class="dao.impl.UserDaoImpl">
<property name="jdbcTemplate" ref="jdbcTemplate"/>
</bean>
3.MyBatis 支持
3-1 POM配置
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.4.6</version>
</dependency>
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis-spring</artifactId>
<version>2.0.3</version>
</dependency>
3-2 配置文件
<context:property-placeholder location="classpath:dataSource.properties"/>
<bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
<property name="driverClassName" value="${jdbc.driver}"/>
<property name="url" value="${jdbc.url}"/>
<property name="username" value="${jdbc.username}"/>
<property name="password" value="${jdbc.password}"/>
</bean>
<!-- 整合mybatis的配置 -->
<!-- SqlSession 不用单独创建,每次做crud操作都需要Mapper接口的代理对象 而代理对象的创建又必须有 SqlSession对象创建 Spring在通过MyBatis创建Mapper接口代理对象的时候,底层自动把SqlSession会话对象创建出来 -->
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
<!-- 配置数据源 -->
<property name="dataSource" ref="dataSource"/>
<!-- 配置别名 -->
<property name="typeAliasesPackage" value="entity"/>
<!-- 注册mapper -->
<!--<property name="mapperLocations">-->
<!--<list>-->
<!--<value>classpath:mapper/UserMapper.xml</value>-->
<!-- 支持通配符用法 -->
<!--<value>classpath:mapper/*.xml</value>-->
<!--</list>-->
<!--</property>-->
<property name="mapperLocations" value="classpath:mapper/*.xml"></property>
<!-- 访问MyBatis的configuration配置 可以保留mybatis的一些个性化配置,比如缓存,日志等-->
<!--<property name="configLocation" value="classpath:mybatis-config.xml"/>-->
</bean>
<!-- 此时可以通过MyBatis的方式进行实现了,但是获取对应的dao实例相对麻烦 -->
<!-- Spring提供了获取对应的dao实例的方式 -->
<!-- 方式一:获取某一个dao实例,通过FactoryBean进行实现 (数据映射器MapperFactoryBean) -->
<bean id="userDao" class="org.mybatis.spring.mapper.MapperFactoryBean">
<property name="sqlSessionFactory" ref="sqlSessionFactory"/>
<property name="mapperInterface" value="dao.UserMapper"/>
</bean>
<!--
方式三:使用后处理bean对当前的工程进行整体操作
指定dao接口所在包,对该包进行扫包操作
通过扫包找到对应包下的所有的接口
自动生成对应的实例
此时bean的id为当前的类名,首字母小写
-->
<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
<property name="basePackage" value="dao"/>
</bean>
3-3 测试
ApplicationContext ac = new ClassPathXmlApplicationContext("applicationContext.xml");
// 方式一:使用源生的MyBatis实现方式
SqlSessionFactory factory = (SqlSessionFactory) ac.getBean("sqlSessionFactory");
SqlSession session = factory.openSession();
UserMapper userMapper = session.getMapper(UserMapper.class);
User user = userMapper.selectByPrimaryKey(1);
System.out.println(user);
// 方式二:使用MapperFactoryBean生成对应的dao的实例
// 此时的dao实例由Spring管理
UserMapper userMapper2 = (UserMapper) ac.getBean("userDao");
User user2 = userMapper.selectByPrimaryKey(1);
System.out.println(user2);
// 方式三:使用自动扫包操作
UserMapper userMapper3 = (UserMapper) ac.getBean("userMapper");
User user3 = userMapper3.selectByPrimaryKey(1);
System.out.println(user3);
4.事务支持
4-1 什么是事务
编程式事务:
try{
开启事务;
业务方法执行;
提交;
}catch(){
回滚;
}finally{
关闭;
}
声明式事务(Spring):
这是一个事务:
serviceA;
A C I D
所谓的事务,表示一次不可再分的操作序列这些操作序列中的所有操作
要么都执行,要么都不执行
它是一个不可分割的完整的工作单元
声明式事务:以前通过复杂的编程来编写一个事务,替换为只需要告诉Spring哪个方法是事务方法即可,Spring自动控制事务。
//获取连接
//设置非自动提交
目标代码运行
//正常提交
//异常回滚
//最终关闭
事务管理代码的固定模式作为一种横切关注点,可以通过AOP方法模块化,进而借助Spring AOP框架实现声明式事务管理。
编程式事务:
4-2 传统数据库的事务特性
ACID
A
- Atomicity
- 原子性
C
- Consistency
- 一致性
I
- Isolation
- 隔离性
D
- Durability
- 持久性
4-3 事务隔离级别
从小到大
static int TRANSACTION_NONE
- 指示事务不受支持的常量。
- 即:没有事务
static int TRANSACTION_READ_UNCOMMITTED
- 指示可以发生脏读 (dirty read)、不可重复读和幻读 (phantom read) 的常量。
- 即:读未提交数据
- 有事务,但是这个事务啥都不管
static int TRANSACTION_READ_COMMITTED
- 指示不可以发生脏读的常量;不可重复读和幻读可以发生。
- 即:读已提交数据
static int TRANSACTION_REPEATABLE_READ
- 指示不可以发生脏读和不可重复读的常量;幻读可以发生。
static int TRANSACTION_SERIALIZABLE
用不到- 指示不可以发生脏读、不可重复读和幻读的常量。
- 在执行期间,禁止其他事物对这个表进行增删改的操作
- 解决并发问题,性能十分低下。
- 此处存在悲观锁的问题
Spring建议的是使用DEFAULT,就是数据库本身的隔离级别,配置好数据库本身的隔离级别,无论在哪个框架中读写数据都不用操心
4-4 数据库并发产生的问题
脏读
- 一个事务读取到另一个尚未提交的事务
- 不可重复读 update
可重复读:在同一个事务内,读取一条数据读到的能保证是一样的
- 一个事务进行多次操作的时候,读取到的数据不一致
虚读(幻读) insert delete
- 一个事务进行多次操作的时候,读取到的数据量不一致(insert)
脏读是不允许发生的
脏读 | 不可重复读 | 幻读 | |
---|---|---|---|
READ_UNCOMMITTED:读未提交 | 有 | 有 | 有 |
READ_COMMITTED:读已提交 | 无 | 有 | 有 |
REPEATABLE_READ:可重复读 | 无 | 无 | 有 |
SERIALIZABLE: | 无 | 无 | 无 |
数据库对事物隔离级别的支持程度
oracle | mysql | |
---|---|---|
READ_UNCOMMITTED | 不支持 | 支持 |
READ_COMMITTED | 支持 | 支持 |
REPEATABLE_READ | 不支持 | 支持(默认) |
SERIALIZABLE | 支持 | 支持 |
根据业务特性做调整。
查询mysql的隔离级别:
select @@global.tx_isolation //查询全局隔离级别
select @@session.tx_isolation //查询当前会话的隔离级别
start TRANSACTION 开启事务
修改隔离级别sql:set session TRANSACTION ISOLATION LEVEL REPEATABLE READ
set session TRANSACTION ISOLATION LEVEL READ_COMMITTED
4-5 Spring事务特性
传播行为
事物的传播+事物的行为
如果 有多个事物进行嵌套运行,子事物是否要和大事物共用一个事物。
比如:
void service1{
method1(){
}
}
- 在Spring中,传播的规则有很多,一般需要熟练掌握其中两种
REQUIRED(默认)
- 表示当前的方法必须运行在一个存在事务管理的环境中
在运行时会判断当前的环境中是否存在事务管理
- 如果存在,则会自动加入到当前的事务管理中
- 如果不存在,则会开启一个新的事务环境用于执行当前的业务
- 一般用于增删改操作
- 如果是REQUIRED,事物里面的属性配置都用大事物中的属性。
@Transactional(propagation=Propagation.REQUIRED) //如果方法1有事务,方法1调用方法2之后,方法2使用方法1的事务 //如果方法1没有事务,方法1调用方法2之后,则创建一个新事物 REQUIRED //方法2调用方法2,不管1有没有事务,都会创建一个新的事务运行.并把之前的事务挂起(暂停) REQUIRES_NEW
REQUIRED: 和REQUIRES_NEW是最常用的。
REQUIRED:将之前的connection传过来
REQUIRES_NEW:这个方法直接使用新的connection
PROPAGATION_SUPPORTS--支持当前事务,如果当前没有事务,就以非事务方式执行。
PROPAGATION_MANDATORY--支持当前事务,如果当前没有事务,就抛出异常。
PROPAGATION_NOT_SUPPORTED--以非事务方式执行操作,如果当前存在事务,就把当前事务挂起。
PROPAGATION_NEVER--以非事务方式执行,如果当前存在事务,则抛出异常。SUPPORTS
- 表示当前的方法不是必须运行在事务管理的环境中
- 有事务可以运行,没有事务也可以运行
在运行时会判断当前的环境中是否存在事务管理
- 如果存在,则会自动加入到当前的事务管理中
- 如果不存在,则拉倒
- 一般用于查询操作
- 一般不会单独使用,会与其他属性联合使用
回滚条件
- 默认情况下,当遇到
RuntimeException
以及其子类的时候,会自动回滚 可以进行手动设置
rollbackFor="异常的class类型"
- 表示遇到指定的异常会进行回滚
noRollbackFor="异常的class类型"
- 表示遇到指定的异常不进行回滚
- 默认情况下,当遇到
只读优化
- 当你确定你的业务中有且仅有查询操作时才能使用
readOnly=true
- 一般与传播规则的SUPPORTS联合使用
超时
- Timeout
隔离级别
- 详见4-3
4-6 POM配置
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-tx</artifactId>
<version>${spring.version}</version>
</dependency>
4-7 Spring配置
以注解事务为例
事务切面==事务管理器
这个事务管理器就可以在目标方法运行前后进行事务控制。
PlatformTransactionManager
Spring整合JDBC与MyBatis使用的是相同的事务管理器
DataSourceTransactionManager
<context:component-scan base-package="service"/>
<!-- 事务配置 -->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"/>
</bean>
<!-- 配置AOP2.X注解事务 需要导入面向切面编程需要的依赖包 -->
<!--开启基于注解的事务控制模式-->
<tx:annotation-driven transaction-manager="transactionManager"/>
dao里定义基本的增删改查的方法
一个dao方法就对应一次sql操作
service定义的是一个业务操作
transAcoount(){
扣钱;
加钱;
}
4-8 注解使用
@Transactional
- 表示配置事务的相关操作
- 该注解可以标注在类上,也可以标注在方法上
- 该注解标注在类上表示当前类均使用此时进行的事务配置
- 该注解标注在方法上表示当前方法使用此时进行的实物配置
- 如果类上与方法上均有该注解,优先使用方法上的事务配置
propagation
属性- 配置传播规则的事务属性
- 其值是一个枚举类型,使用
Propagation
中提供的值 - 其值一般使用
REQUIRED
或者SUPPOTRS
rollbackFor
属性- 配置回滚条件
- 当遇到指定的异常时进行回滚
noRollbackFor
属性- 配置回滚条件
- 当遇到指定的异常时不进行回滚,可以让原来回滚的异常不回滚
readOnly=true
- 配置是否只读
- 当操作中有且仅有查询操作时使用,可以加快查询速度
- isolation 隔离级别
- noRollbackForClassName :基本不用,用
noRollbackFor
- timeout:超时,事务超出指定时间执行时长后自动终止并回滚 以秒为单位.指的是2次sql完成时间不超过设置的时间。
注意:默认 编译时异常不回滚。运行时异常回滚。
@Service
@Transactional(propagation = Propagation.REQUIRED,rollbackFor = Exception.class,noRollbackFor = ArithmeticException.class)
public class UserServiceImpl implements UserService {
@Autowired
private UserMapper userMapper;
@Override
public void regist(User user) throws UserExistException {
UserExample example = new UserExample();
example.or()
.andUsernameEqualTo(user.getUsername());
List<User> users = userMapper.selectByExample(example);
if(!users.isEmpty()){
throw new UserExistException("该用户已经被注册");
}
userMapper.insertSelective(user);
int i = 1/0;
}
@Transactional(propagation = Propagation.SUPPORTS,readOnly = true)
@Override
public User login(String username, String password) throws UserNotExistException {
UserExample example = new UserExample();
example.or()
.andUsernameEqualTo(username)
.andPasswordEqualTo(password);
List<User> users = userMapper.selectByExample(example);
if(users.isEmpty()){
throw new UserNotExistException("用户名或密码错误");
}
return users.get(0);
}
}
4-9 基于xml的事务配置(仅了解)
<!--1.Spring中提供事务管理器,配置这个事务管理器
2.配置出事务方法-->
<aop:config>
<aop:pointcut id="txpoint" expression="execution(* com.hy.spring.service.*.*(..))"/>
<!--aop:advisor:事务增强(建议)-->
<aop:advisor advice-ref="txadvice" pointcut-ref="txpoint" />
</aop:config>
<!--配置事务管理器-->
<tx:advice id="txadvice" transaction-manager="tm">
<!--事务属性-->
<tx:attributes>
<!--tx:method告诉Spring哪个方法是事务方法。
切入点表达式只是说明事务管理器切入这些方法-->
<tx:method name="transAccount" propagation="REQUIRED" />
</tx:attributes>
</tx:advice>
工厂bean(实现FactoryBean接口)和普通bean的区别:
BeanFactoryPostProcessor;在所有bean实例化之前,对整个bean定义的内容做修改
BeanPostProcessor:bean实例化完成了,在bean初始化前后对bean做处理
/** Cache of singleton objects: bean name to bean instance.
一级缓存:完整的javabean对象
*/
private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256);
/** Cache of singleton factories: bean name to ObjectFactory.
三级缓存 :bean实例化出来的空盒子
*/
private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<>(16);
/** Cache of early singleton objects: bean name to bean instance.
二级缓存 :循环依赖()
a <--> b
getBean() 加入三级缓存 populateBean()注入属性b ====>b:getBean() 加入三级缓存 注入属性a 从三级缓存中取到a,先注入空盒子 继续初始化b 继续a的初始化
*/
private final Map<String, Object> earlySingletonObjects = new ConcurrentHashMap<>(16);