Spring 初识
优点:
- 轻量级、非入侵式框架
- 控制反转(IOC),面向切面编程(AOP)
- 支持事务处理,支持框架整合
总结:Spring 就是一个轻量级的控制反转(IOC)和面向切面编程(AOP)的框架。
环境初步
依赖:或可导入 spring-context 依赖
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>5.2.5.RELEASE</version>
</dependency>
beans.xml:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="hello" class="com.luis.domain.Hello">
<property name="str" value="hello spring"/>
</bean>
</beans>
测试:
ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");
Hello hello = (Hello)context.getBean("hello");
System.out.println(hello);
IOC 思想理解
- IOC(控制反转)是一种思想,DI(依赖注入)是实现 IOC 的一种方式。
- 控制:谁来控制对象的创建,传统应用程序的对象是由程序本身控制创建,使用 Spring 后,对象是由 Spring 来创建
- 反转:程序本身不创建对象,而变成被动的接收对象
- 依赖注入:就是利用 set 方法来进行注入的
- IOC 是一种编程思想,由主动的编程编程被动的接收
- 通过控制反转,我们彻底不用在程序中去改动了,要实现不同的操作只需要在 xml 配置文件中进行修改即可
- 所谓的 IOC,一句话来说,就是对象由 Spring 来创建,管理,装配!
- 个人认为,所谓控制反转就是获得依赖对象的方式反转了。
- 采用 XML 的方式配置 Bean 的时候,Bean 的定义信息是和实现分离的,而采用注解的方式可以把两者合为一体,Bean 的定义信息直接以注解的形式定义在实现类中,从而达到零配置的目的。
- 控制反转是一种通过描述(XML 或注解)并通过第三方去生产或获取特定对象的方式。在 Spring 中实现控制反转的是 IoC 容器,其实现方法是依赖注入(DI)。
IOC 创建对象的方式
-
默认是通过无参构造创建(set 赋值注入)
<bean id="hello" class="com.luis.domain.Hello"> <property name="str" value="hello spring"/> </bean>
-
可以使用有参构造创建(构造赋值注入)
-
通过参数赋值创建(推荐+掌握)
<bean id="hello" class="com.luis.domain.Hello"> <constructor-arg name="str" value="test"/> </bean>
-
通过类型赋值创建(不建议)
<bean id="hello" class="com.luis.domain.Hello"> <constructor-arg type="java.lang.String" value="tt"/> </bean>
-
通过下标赋值创建
<bean id="hello" class="com.luis.domain.Hello"> <constructor-arg index="0" value="t"/> </bean>
总结:在 Spring 容器加载配置文件时,容器中管理的对象就全部被实例化了。
-
Spring 主配置
Bean 的配置
<!--
id:bean 的唯一标识符
class:对象的全限定名,即包名 + 类型
name:和 alias 标签作用一样,起别名,支持配置多个;容器中获取对象的时候可用此别名获取
-->
<bean id="hello" class="com.luis.domain.Hello" name="hello3,hello4">
<property name="str" value="hello spring"/>
</bean>
别名
作用:给 bean 配置别名后,从容器中获取 bean 时,可以通过 bean 的 id,也可以通过自定义的别名
使用位置:和 bean 标签同级
<alias name="hello" alias="hello2"/>
import
使用位置:和 bean 标签同级
作用:一般用于团队开发使用,可以将多个配置文件,导入合并为一个
- 主配置文件中一般只是导入其他配置,不定义对象
- 其他副配置一般有两种分类方式:一种是按模块,一种是按类型(dao、service、controller 等)
举例:
假设 applicationContext.xml 为主配置,beans1.xml、beans2.xml、beans3.xml 均为副配置,可在主配置 applicationContext.xml 中使用 import 标签将其他副配置合并导入,这样,其他副配置中内容也可正常使用。
<!--主配置 applicationContext.xml 中-->
<import resource="classpath:beans1.xml"/>
<import resource="classpath:beans2.xml"/>
<import resource="classpath:beans3.xml"/>
<!--也可以直接使用通配符的方式:注意需要放在同一个目录下,而且不要造成死循环-->
<import resource="classpath:beans*.xml"/>
ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
Hello hello = (Hello)context.getBean("xxx"); // 其他副配置中注册的对象也可获取到
System.out.println(hello);
Spring 容器的对象管理
应该放容器中管理的对象
Spring 中的对象默认都是单例的,在容器中叫这个名称的对象只有一个
- dao 类
- service 类
- controller 类
- 工具类
不应该放容器中管理的对象
- 实体类
- servlet
- listener
- filter 等
DI 依赖注入 - 基于 XML
核心总览:
- set 注入
- 构造注入
- 引用类型自动注入
基于 xml 的DI 依赖注入主要有两种方式:set 注入和构造注入
Set 方式注入(重点)
- 依赖注入:Set 注入!
- 依赖:bean 对象的创建依赖于容器
- 注入:也就是赋值的意思,指 bean 对象中的所有属性,由容器来注入
Set 注入下对象不同类型属性的注入格式示例:
// 实体类 Address
public class Address {
private String addr;
// getter和setter省略
// toString省略
}
// 实体类 Student
public class Student {
private String name;
private Address address;
private String[] books;
private List<String> hobbys;
private Map<String,String> card;
private Set<String> games;
private String wife;
private Properties info;
// getter和setter省略
// toString省略
}
<bean id="address" class="com.luis.domain.Address">
<property name="addr" value="苏州"/>
</bean>
<bean id="student" class="com.luis.domain.Student">
<!-- 普通值注入:value -->
<property name="name" value="林威"/>
<!-- bean注入:ref -->
<property name="address" ref="address"/>
<!-- 数组注入:array -->
<property name="books">
<array>
<value>天气之子</value>
<value>斗破苍穹</value>
</array>
</property>
<!-- List集合注入:list -->
<property name="hobbys">
<list>
<value>看动漫</value>
<value>编程</value>
<value>阅读</value>
</list>
</property>
<!-- Map集合注入:map -->
<property name="card">
<map>
<entry key="身份证" value="123456789"/>
<entry key="银行卡" value="987654321"/>
</map>
</property>
<!-- Set集合注入:set -->
<property name="games">
<set>
<value>LOL</value>
<value>Steam</value>
</set>
</property>
<!-- null注入:null -->
<property name="wife">
<null/>
</property>
<!-- 属性类Properties注入:props -->
<property name="info">
<props>
<prop key="username">luis</prop>
<prop key="password">123</prop>
</props>
</property>
</bean>
测试结果:
Student{
name='林威',
address=Address{addr='苏州'},
books=[天气之子, 斗破苍穹],
hobbys=[看动漫, 编程, 阅读],
card={身份证=123456789, 银行卡=987654321},
games=[LOL, Steam],
wife='null',
info={password=123, username=luis}}
构造器方式注入
前面 IOC 通过有参构造创建对象时已经讲述,此处简要介绍
三种方式:
参数(推荐)
下标(不建议)
类型
<!--方式一:通过参数构造输入-->
<bean id="hello" class="com.luis.domain.Hello">
<constructor-arg name="str" value="test"/>
</bean>
<!--方式二:通过下标构造输入-->
<bean id="hello" class="com.luis.domain.Hello">
<constructor-arg index="0" value="t"/>
</bean>
<!--方式三:通过类型构造输入-->
<bean id="hello" class="com.luis.domain.Hello">
<constructor-arg type="java.lang.String" value="tt"/>
</bean>
引入类型的自动注入
Bean 的自动装配
- 在 Spring 中有三种装配的方式
- 在 XML 中显示配置
- 在 Java 中显示配置
- 隐式的自动装配 bean【重要】(即引入类型的自动注入)
- 自动装配是 Spring 满足 bean 依赖的一种方式
- Spring 会在上下文中自动寻找,并自动给 bean 装配属性
- 在说自动装配之前,我们先聊一聊什么是手动装配。
- 手动装配:就是我们自己给定属性,然后赋值
- Spring IOC 容器可以自动装配 Bean,需要做的仅仅是在 bean 的 autowire 属性里指定自动装配的模式
- byName(按名称注入)和byType(按类型注入)两种模式可实现引用类型的自动注入/装配
引用类型的自动注入: spring框架根据某些规则可以给引用类型赋值,不用你再给引用类型赋值了。
使用的规则常用的是byName, byType.
1.byName(按名称注入) : java类中引用类型的属性名和spring容器中(配置文件)<bean> 的id名称一样,
且数据类型是一致的,这样的容器中的bean,spring能够赋值给引用类型。
语法:
<bean id="xx" class="yyy" autowire="byName">
简单类型属性赋值
</bean>
2.byType(按类型注入) : java类中引用类型的数据类型和spring容器中(配置文件)<bean>的class属性
是同源关系的,这样的bean能够赋值给引用类型
同源就是一类的意思:
1.java类中引用类型的数据类型和bean的class的值是一样的。
2.java类中引用类型的数据类型和bean的class的值父子类关系的。
3.java类中引用类型的数据类型和bean的class的值接口和实现类关系的
测试环境:
// 实体类
public class Cat {
public void shout() {
System.out.println("mmm~");
}
}
public class Dog {
public void shout() {
System.out.println("www~");
}
}
public class Person {
private Dog dog;
private Cat cat;
private String name;
// getter and setter 省略
}
byName 自动注入
byName(按名称注入):
java 类中引用类型的属性名和 spring 容器中(配置文件)bean 的 id 名称一样,
且数据类型是一致的,这样的容器中的 bean,spring 能够赋值给引用类型。
<bean id="dog" class="com.luis.domain.Dog"/>
<bean id="cat" class="com.luis.domain.Cat"/>
<!--
byName:java类中引用类型的属性名和spring容器中(配置文件)<bean> 的id名称一样,
且数据类型是一致的,这样的容器中的bean,spring能够赋值给引用类型。
-->
<bean id="person" class="com.luis.domain.Person" autowire="byName">
<property name="name" value="luis"/>
</bean>
byType 自动注入
byType(按类型注入):
java 类中引用类型的数据类型和 spring 容器中(配置文件)bean 的 class 属性
是同源关系的,这样的bean能够赋值给引用类型。
同源关系:相同类型、父子继承、接口和实现类。
注意:使用 byType 按类型自动注入,在 xml 配置文件中声明 bean 的时候就只能有一个符合要求的,多了就会报错!
<bean id="dog" class="com.luis.domain.Dog"/>
<bean id="cat" class="com.luis.domain.Cat"/>
<!--
byType:java类中引用类型的数据类型和spring容器中(配置文件)<bean>的class属性
是同源关系的,这样的bean能够赋值给引用类型
同源关系:相同类型、父子继承、接口和实现类
-->
<bean id="person" class="com.luis.domain.Person" autowire="byType">
<property name="name" value="luis"/>
</bean>
测试:
ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
Person person = context.getBean("person", Person.class);
person.getCat().shout();
person.getDog().shout();
拓展方式注入(了解)
Bean 的作用域
设置方法:在 bean 标签中作为 scope 属性进行设置
-
单例模式(Spring 默认机制):每次 get 的都是同一个对象
<bean id="address" class="com.luis.domain.Address" scope="singleton"> <property name="addr" value="苏州"/> </bean>
-
原型模式:每次从容器中 get 的时候,都会产生一个新对象
<bean id="address" class="com.luis.domain.Address" scope="prototype"> <property name="addr" value="苏州"/> </bean>
-
其余的 request、session、application 这些只能在 web 开发中使用到,作用域如其名
DI 依赖注入 - 基于注解
- 使用注解的方式完成对象创建和属性赋值
以后开发,注解为主,配置为辅!
使用步骤:
-
添加 spring-context 依赖(主要是使用 spring-context 依赖中包含的 spring-aop 依赖)
<dependency> <groupId>org.springframework</groupId> <artifactId>spring-context</artifactId> <version>5.2.5.RELEASE</version> </dependency>
-
在类中使用 Spring 的注解(有多个不同功能的注解)
-
在 Spring 的配置文件中添加组件扫描器标签,说明注解在项目中的位置
使用技巧:在 IDEA 中直接打 <comp... 就可以有提示!!!
<!--声明组件扫描器(component-scan),组件就是Java对象,需要指定包名--> <context:component-scan base-package="com.luis"/> <!--指定多个包的三种方式--> <!--方式一:使用多次组件扫描器,每次指定不同的包--> <context:component-scan base-package="com.luis.ba01"/> <context:component-scan base-package="com.luis.ba02"/> <!--方式二:使用分隔符(;或,)分隔多个包名--> <context:component-scan base-package="com.luis.ba01;com.luis.ba02"/> <!--方式三:直接指定父包--> <context:component-scan base-package="com.luis"/>
常用注解
- @Component:创建除 dao,service、controller 之外的对象专用
- @Repository:创建 dao 类对象专用
- @Service:创建 service 类对象专用
- @Controller:创建 controller 类对象专用
- @Value:给简单类型属性赋值(推荐直接在属性上使用)
- @Autowired:给引用类型属性赋值(推荐直接在属性上使用,使用的是自动注入原理,默认 byType)
- @Resource:给引用类型属性赋值(推荐直接在属性上使用,使用的是自动注入原理,默认 byName)
@Component
作用:创建对象
特点:创建除 dao,service、controller 之外的对象专用
// 正规完全写法
// @Component(value = "myStudent")
// 常用省略value写法(开发常用)
@Component("myStudent")
// 不指定value,系统自动指定为类名首字母小写
// @Component
public class Student {}
@Repository
作用:创建对象
用法同 @Component
特点:放在持久层 dao 类上,表示创建 dao 对象,dao 对象可访问数据库
@Service
作用:创建对象
用法同 @Component
特点:放在业务层 service 类上,表示创建 service 对象,service 对象做业务处理,有事务等功能。
@Controller
作用:创建对象
用法同 @Component
特点:放在控制器 controller 类上,表示创建 controller 对象,controller 对象可接收用户参数,显示请求处理结果。
@Value
作用:给简单类型属性赋值
使用方式:
- 直接在属性定义上使用(无需set方法,开发常用,推荐使用)
- 在 set 方法上使用(不建议)
@Value("luis")
private String name;
@Value("22")
public void setAge(Integer age) {
this.age = age;
}
@Autowired
作用:给引用类型属性赋值
使用方式:
- 在引用类型属性上直接使用(无需 set 方法,开发常用,推荐使用)
- 在 set 方法上使用(不建议)
注意:
给引用类型属性赋值,使用的是自动注入原理,支持 byName 和 byType
默认使用的是 byType 自动注入
如果不使用默认的 byType 自动注入,需要使用 byName 自动注入,需要在添加 @Autowired 注解的前提下,再添加一个 @Qualifier 注解,并为其指定 bean 的 id。
关于 @Autowired 的属性 required:
- 添加 @Autowired 属性时,其有一个 required 属性,是 boolean 类型,默认为 true
- required = true:表示若引用类型赋值失败,程序报错,并终止执行
- required = false:表示若引用类型赋值失败,程序正常执行,引用类型赋值 null
- 建议:不要设置为 false,建议使用默认的 true 值,可提前暴露问题!
// 使用默认的 byType 自动注入方式
@Autowired
private Address address;
// 使用 byName 自动注入方式
@Autowired
@Qualifier("myAddr")
private Address address;
@Resource
来源:来自 JDK 中,不是来自 Spring,Spring 提供对其的支持
作用:给引用类型属性赋值
使用方式:
- 在引用类型属性上直接使用(无需 set 方法,开发常用,推荐使用)
- 在 set 方法上使用(不建议)
注意:
- 给引用类型属性赋值,使用的是自动注入原理,支持 byName 和 byType
- 默认先使用的是 byName 自动注入,若 byName 自动注入方式失败,则再使用 byType 自动注入
- 如果希望只使用 byName 方式,只需要为其指定 name 属性值即可
// 默认先使用 byName,若 byName 失败,则使用 byType
@Resource
private Address address;
// 指定只使用 byName 方式
@Resource(name = "myAddr")
private Address address;
关于注解和 XML 配置方式选择
- 对于不经常改变的使用注解,经常改变的,使用配置文件方式
AOP
什么时候考虑使用 AOP ?
- 需要给项目中一个类保留其原有功能前提下,进行功能完善,可以使用 AOP 实现。
- 需要给项目中多个类增加一个相同的功能,可以使用 AOP 实现。
- 需要给业务方法增加事务,日志输出时,可以使用 AOP 实现。
动态代理
两种实现方式
- jdk动态代理
- 使用 jdk 中的 Proxy,Method,InvocaitonHanderl 创建代理对象
- jdk 动态代理要求目标类必须实现接口
- cglib 动态代理
- 第三方的工具库,创建代理对象
- 原理是继承,通过继承目标类,创建子类,子类就是代理对象
- 要求目标类不能是 final的, 方法也不能是 final 的
动态代理作用(掌握)
- 在目标类源代码不改变的情况下,增加功能
- 减少代码的重复
- 专注业务逻辑代码
- 解耦合,让你的业务功能和日志,事务非业务功能分离
理解 AOP
概念
-
AOP(Aspect Orient Programming)面向切面编程
-
面向切面编程, 基于动态代理的,可以使用 jdk,cglib 两种代理方式 。
-
Aspect: 切面,给你的目标类增加的功能,就是切面。 像我们用的日志,事务都是切面。
- 切面的特点: 一般都是非业务方法,独立使用的。
-
Orient:面向,对着。
-
Programming:编程
总结:AOP 就是动态代理的规范化, 把动态代理的实现步骤,方式都定义好了,让开发人员用一种统一的方式,使用动态代理。作用:
- 在目标类不改变源代码的前提下,增强功能
- 减少重复的代码
- 专注业务功能的实现
- 解耦合:将业务功能和日志,事务这些非业务功能分离开,解耦合
怎么理解面向切面编程 ?
面试常问!
- 需要在分析项目功能时,找出切面
- 合理的安排切面的执行时间(在目标方法前, 还是目标方法后)
- 合理的安排切面执行的位置,在哪个类,哪个方法增加增强功能
相关术语
- Aspect:切面,表示增强的功能,就是一堆代码,完成某个一个功能。非业务功能,常见的切面功能有日志,事务,统计信息,参数检查,权限验证。
- JoinPoint:连接点,连接业务方法和切面的位置,就是某类中的业务方法。
- Pointcut:切入点,指多个连接点方法的集合,多个方法;表示切面功能执行的位置。
- 目标对象:给哪个类的方法增加功能,这个类就是目标对象。
- Advice:通知,通知表示切面功能执行的时间。
切面三个关键的要素:
- 切面的功能代码,切面干什么
- 切面的执行位置,使用 Pointcut 表示切面执行的位置
- 切面的执行时间,使用 Advice 表示时间,在目标方法之前,还是目标方法之后
AOP 的实现
AOP 是一个规范,是动态代理的一个规范化,一个标准 AOP 的技术实现框架有以下两种:
- Spring (不用此方式)
- spring 在内部实现了 aop 规范,能做 aop 的工作。
- spring 主要在事务处理时使用 aop。
- 我们项目开发中很少使用 spring 的 aop 实现!因为 spring 的 aop 比较笨重。
- aspectJ(掌握)
- 一个开源的专门做 aop 的框架。
- spring 框架中集成了 aspectj 框架,通过 spring 就能使用 aspectj 的功能。
aspectJ 框架实现 aop 有两种方式:
- 使用 xml 的配置文件 :配置全局事务
- 使用注解
我们在项目中要做 aop 功能,一般都使用注解,aspectj 有5个注解。
重点:一般使用 aspectJ 的 AOP 实现,不用 Spring 的。
aspectJ 框架实现 AOP
AOP 主要通过注解方式实现!做事务则通过配置的方式!
明确切面三个要素
- 执行时间:Advice(通知,增强)
- 执行位置:Pointcut(切入点)
- 功能代码:切面功能(自己写的非业务功能代码)
切面三个关键的要素复述:
- 切面的功能代码,切面干什么(我们自己写的功能代码)
- 切面的执行位置,使用 Pointcut 表示切面执行的位置
- 切面的执行时间,使用 Advice 表示时间,在目标方法之前,还是目标方法之后
切面的执行时间:这个执行时间在规范中叫做 Advice (通知,增强),在 aspectj 框架中使用注解表示的;也可以使用 xml 配置文件中的标签。
- @Before:前置通知【掌握】
- @AfterReturning:后置通知【掌握】
- @Around:环绕通知【掌握】
- @AfterThrowing:异常通知【了解】
- @After:最终通知【了解】
切入点表达式:表示执行位置(理解下列举例的五个使用即可)
注解:@Pointcut 定义切入点【掌握】
简述实现步骤
此处大概演示前置通知的使用,其他通知或切入点表达式的写法见后续笔记。
主要看下固定的实现步骤!
注意:如果切入点表达式无法匹配到目标方法,则既不会创建动态代理对象,也不会有功能增强!
-
添加 spring-context,spring-aspects、junit 依赖
<dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.11</version> <scope>test</scope> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-context</artifactId> <version>5.2.5.RELEASE</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-aspects</artifactId> <version>5.2.5.RELEASE</version> </dependency>
-
创建目标接口以及目标接口实现类
public interface SomeService { void doSome(String name,Integer age); } public class SomeServiceImpl implements SomeService { @Override public void doSome(String name, Integer age) { System.out.println("doSome method execute!"); } }
-
创建切面类,加 @Aspect 注解;创建切面方法,添加相关 Advice 通知,并指定切入点表达式;在方法中写切面功能
@Aspect public class MyAspect { // 完全格式的切入点表达式示例[访问权限 返回值 包名+类名+方法名(参数)] 必须:返回值、方法名(参数) // @Before("execution(public void com.luis.service.impl.SomeServiceImpl.doSome(String,Integer))") // @Before("execution(void com.luis.service.impl.SomeServiceImpl.doSome(String,Integer))") // @Before("execution(void *..SomeServiceImpl.doSome(String,Integer))") // @Before("execution(* *..SomeServiceImpl.do*(..))") @Before("execution(* do*(..))") // 必须有:返回值、方法名(参数) public void addDate() { System.out.println("前置通知:时间 " + new Date()); } }
-
在配置文件中将对象交给容器创建或使用注解创建(使用注解的话记得声明组件扫描器)
-
在配置文件中声明 aspectj 的自动代理生成器
<bean id="someService" class="com.luis.service.impl.SomeServiceImpl"/> <bean id="myAspect" class="com.luis.aspect.MyAspect"/> <!--声明自动代理生成器--> <aop:aspectj-autoproxy/>
-
测试,获取修改后的目标类对象(也是动态代理类)执行相关方法
@Test public void test() { ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml"); SomeService proxy = (SomeService) context.getBean("someService"); System.out.println(proxy.getClass().getName()); proxy.doSome("name", 22); }
测试结果:
com.sun.proxy.$Proxy8 前置通知:时间 Wed Mar 16 14:12:22 CST 2022 doSome method execute!
JoinPoint(掌握)
注意:JoinPoint 只可使用在切面类中切面方法/通知方法的第一个形参位置上
使用位置:只可使用在切面类中切面方法/通知方法的第一个形参位置上
作用:可以在通知方法中获取方法执行时的信息,例如方法名,方法实参等
注意:
- 如果切面功能中需要用到方法的信息,就可以通过加入 JoinPoint 参数获取
- JoinPoint 参数的值是由框架赋予,必须是第一个位置的参数!
使用示例:
@Aspect
@Component("myAspect")
public class MyAspect {
@Before("execution(* do*(..))")
public void addDate(JoinPoint jp) {
System.out.println("前置通知:时间 " + new Date());
System.out.println("方法签名:" + jp.getSignature());
System.out.println("方法名:" + jp.getSignature().getName());
Object[] args = jp.getArgs();
for (Object arg : args) {
System.out.println("方法参数:" + arg);
}
}
}
打印内容:
com.sun.proxy.$Proxy14
前置通知:时间 Wed Mar 16 15:35:37 CST 2022
方法签名:void com.luis.service.SomeService.doSome(String,Integer)
方法名:doSome
方法参数:name
方法参数:22
doSome method execute!
@Before(掌握)
前置通知方法定义格式:
- 公共方法 public
- 方法没返回值
- 方法名称自定义
- 方法可以有参数,也可以没有参数;如果有参数,参数不是自定义的,有几个参数可以使用;如必须放在第一个位置上的 JoinPoint 参数,可以获取方法名,方法参数等信息。
前置通知注解:@Before
属性:value,是切入点表达式,表示切面的功能执行的位置
使用位置:在切面方法上面
特点:
- 在目标方法之前先执行
- 不会改变目标方法的执行结果
- 不会影响目标方法的执行
示例:
@Before("execution(* doSome(..))")
public void addDate(JoinPoint jp) {
System.out.println("前置通知:时间 " + new Date());
System.out.println("方法签名:" + jp.getSignature());
System.out.println("方法名:" + jp.getSignature().getName());
Object[] args = jp.getArgs();
for (Object arg : args) {
System.out.println("方法参数:" + arg);
}
}
@AfterReturning(掌握)
后置通知方法定义格式:
- 公共方法 public
- 方法没返回值
- 方法名称自定义
- 方法必须有一个参数,参数名自定义,推荐写 Object!
- 可以添加必须放在第一个位置上的 JoinPoint 参数,可以获取方法名,方法参数等信息。
后置通知注解:@AfterReturning
属性:
- value:切入点表达式
- returning:自定义的变量,表示目标方法的返回值的;该自定义变量名必须和通知方法的形参名一样。
位置:在方法定义的上面
特点:
- 在目标方法之后执行的
- 能够获取到目标方法的返回值,可以根据这个返回值做不同的处理功能
- 可以修改这个返回值
示例:
@AfterReturning(value = "execution(* *..SomeServiceImpl.doOther(..))", returning = "res")
public void myAfterReturning(JoinPoint jp, Object res) {
System.out.println("后置通知:执行相关事务");
if (res instanceof Student) {
Student s = (Student) res;
s.setName("newName");
s.setAge(11);
res = s;
}
}
@Around(掌握)
环绕通知是功能最强的通知!
环绕通知方法定义格式:
- public
- 必须有一个返回值,推荐使用Object
- 方法名称自定义
- 方法有参数,固定的参数: ProceedingJoinPoint(JoinPoint 是其父接口)
环绕通知注解:@Around
属性:value,切入点表达式
位置:在方法的定义上面
特点:
- 它是功能最强的通知
- 在目标方法的前和后都能增强功能
- 可控制目标方法是否被调用执行
- 可修改原来的目标方法的执行结果,直接影响最后的调用结果
环绕通知,等同于 jdk 动态代理的 InvocationHandler 接口
参数: ProceedingJoinPoint
- 等同于 Method
- 作用就是执行目标方法
返回值: 就是目标方法的执行结果,可以被修改。
环绕通知: 经常做事务, 在目标方法之前开启事务,执行目标方法, 在目标方法之后提交事务。
示例:
@Around(value = "execution(* *..SomeServiceImpl.doAround(..))")
public Object myAround(ProceedingJoinPoint pjp) throws Throwable {
System.out.println("环绕通知:目标方法执行前开启事务");
// 执行目标方法;相当于动态代理的 Method.invoke()
Object result = pjp.proceed(); // 在此处可以控制目标方法是否可调用
System.out.println("环绕通知:目标方法执行后关闭事务");
// 可以修改返回结果
if (result != null) {
result = "new modify";
}
return result;
}
测试结果:
com.sun.proxy.$Proxy18
环绕通知:目标方法执行前开启事务
目标方法 doAround 执行了
环绕通知:目标方法执行后关闭事务
new modify
@AfterThrowing
了解即可,实际开发中很少用!
/**
* @Aspect : 是aspectj框架中的注解。
* 作用:表示当前类是切面类。
* 切面类:是用来给业务方法增加功能的类,在这个类中有切面的功能代码
* 位置:在类定义的上面
*/
@Aspect
public class MyAspect {
/**
* 异常通知方法的定义格式
* 1.public
* 2.没有返回值
* 3.方法名称自定义
* 4.方法有个一个Exception, 如果还有是JoinPoint
*/
/**
* @AfterThrowing:异常通知
* 属性:1. value 切入点表达式
* 2. throwinng 自定义的变量,表示目标方法抛出的异常对象。
* 变量名必须和方法的参数名一样
* 特点:
* 1. 在目标方法抛出异常时执行的
* 2. 可以做异常的监控程序, 监控目标方法执行时是不是有异常。
* 如果有异常,可以发送邮件,短信进行通知
*
* 执行就是:
* try{
* SomeServiceImpl.doSecond(..)
* }catch(Exception e){
* myAfterThrowing(e);
* }
*/
@AfterThrowing(value = "execution(* *..SomeServiceImpl.doSecond(..))", throwing = "ex")
public void myAfterThrowing(Exception ex) {
System.out.println("异常通知:方法发生异常时,执行:"+ex.getMessage());
//发送邮件,短信,通知开发人员
}
}
@After
了解即可,实际开发中很少用!
/**
* @Aspect : 是aspectj框架中的注解。
* 作用:表示当前类是切面类。
* 切面类:是用来给业务方法增加功能的类,在这个类中有切面的功能代码
* 位置:在类定义的上面
*/
@Aspect
public class MyAspect {
/**
* 最终通知方法的定义格式
* 1.public
* 2.没有返回值
* 3.方法名称自定义
* 4.方法没有参数, 如果还有是JoinPoint,
*/
/**
* @After :最终通知
* 属性: value 切入点表达式
* 位置: 在方法的上面
* 特点:
* 1.总是会执行
* 2.在目标方法之后执行的
*
* try{
* SomeServiceImpl.doThird(..)
* }catch(Exception e){
*
* }finally{
* myAfter()
* }
*
*/
@After(value = "execution(* *..SomeServiceImpl.doThird(..))")
public void myAfter(){
System.out.println("执行最终通知,总是会被执行的代码");
//一般做资源清除工作的。
}
}
@Pointcut
辅助功能注解,定义管理切入点表达式,可实现切入点表达式复用。
/**
* @Aspect : 是aspectj框架中的注解。
* 作用:表示当前类是切面类。
* 切面类:是用来给业务方法增加功能的类,在这个类中有切面的功能代码
* 位置:在类定义的上面
*/
@Aspect
public class MyAspect {
@After(value = "mypt()")
public void myAfter(){
System.out.println("执行最终通知,总是会被执行的代码");
//一般做资源清除工作的。
}
@Before(value = "mypt()")
public void myBefore(){
System.out.println("前置通知,在目标方法之前执行~");
}
/**
* @Pointcut: 定义和管理切入点, 如果你的项目中有多个切入点表达式是重复的,可以复用的。
* 可以使用@Pointcut
* 属性:value 切入点表达式
* 位置:在自定义的方法上面
* 特点:
* 当使用@Pointcut定义在一个方法的上面 ,此时这个方法的名称就是切入点表达式的别名。
* 其它的通知中,value属性就可以使用这个方法名称,代替切入点表达式了
*/
@Pointcut(value = "execution(* *..SomeServiceImpl.doThird(..))")
private void mypt(){
//无需代码
}
}
关于使用的是 jdk 动态代理还是 cglib 动态代理
声明自动代理生成器:使用aspectj框架内部的功能,创建目标对象的代理对象。
创建代理对象是在内存中实现的, 修改目标对象的内存中的结构。 创建为代理对象,
所以目标对象就是被修改后的代理对象。
aspectj-autoproxy:会把spring容器中的所有的目标对象,一次性都生成代理对象。
<aop:aspectj-autoproxy />
目标对象有接口时,默认使用jdk动态代理方式
目标对象无接口时,会使用cglib动态代理方式
目标对象有接口,想强制使用cglib动态代理方式,
声明自动代理生成器时这样设置即可:(这种方式,程序执行时效率会稍微高一些)
<aop:aspectj-autoproxy proxy-target-class="true" />
Spring 集成 Mybatis
前言:
把 mybatis 框架和 spring 集成在一起,像一个框架一样使用。
用的技术是:ioc
为什么 ioc 能把 mybatis 和 spring 集成在一起,像一个框架, 是因为 ioc 能创建对象。 可以把 mybatis 框架中的对象交给spring 统一创建, 开发人员从 spring 中获取对象。 开发人员就不用同时面对两个或多个框架了, 就面对一个 spring 即可。
mybatis 中要使用 dao 对象,需要使用 getMapper() 方法, 怎么能使用 getMapper() 方法,需要哪些条件 ?
- 获取SqlSession 对象, 需要使用 SqlSessionFactory 的 openSession() 方法。
- 创建 SqlSessionFactory 对象,通过读取 mybatis 的主配置文件,才能创建 SqlSessionFactory 对象 。
需要 SqlSessionFactory 对象, 使用 Factory 能获取 SqlSession ,有了 SqlSession 就能有 dao , 目的就是获取 dao 对象 ,Factory 创建需要读取主配置文件。
我们会使用独立的连接池类替换 mybatis 默认自己带的, 把连接池类也交给 spring 创建。
通过以上的说明,我们需要让 spring 创建以下对象
- 独立的连接池类的对象,使用阿里的 druid 连接池
- SqlSessionFactory 对象
- 创建出 dao 对象
需要学习就是上面三个对象的创建语法,使用 xml 的 bean 标签。
<语法都是固定的,没有什么技术含量,容易学!!!>
基本整合步骤参考:
-
创建 maven 项目,完善目录结构
-
导入所需依赖和资源插件并 reload 项目
<dependencies> <!--单元测试依赖--> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.11</version> <scope>test</scope> </dependency> <!--spring核心ioc--> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-context</artifactId> <version>5.2.5.RELEASE</version> </dependency> <!--spring做事务用到的--> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-tx</artifactId> <version>5.2.5.RELEASE</version> </dependency> <!--spring做事务用到的--> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-jdbc</artifactId> <version>5.2.5.RELEASE</version> </dependency> <!--mybatis依赖--> <dependency> <groupId>org.mybatis</groupId> <artifactId>mybatis</artifactId> <version>3.5.1</version> </dependency> <!--mybatis和spring集成的依赖--> <dependency> <groupId>org.mybatis</groupId> <artifactId>mybatis-spring</artifactId> <version>1.3.1</version> </dependency> <!--mysql驱动的依赖--> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>5.1.9</version> </dependency> <!--阿里公司的数据库连接池(实际开发常用)(替代mybatis自带的连接池)--> <dependency> <groupId>com.alibaba</groupId> <artifactId>druid</artifactId> <version>1.1.12</version> </dependency> </dependencies> <build> <!--目的是把crc/main/java目录中的.xml和.properties文件包含到输出结果中,输出到classes目录中--> <resources> <resource> <directory>src/main/java</directory><!--所在的目录--> <includes><!--包括目录下的.properties,.xml 文件都会扫描到--> <include>**/*.properties</include> <include>**/*.xml</include> </includes> <filtering>false</filtering> </resource> <resource> <directory>src/main/resources</directory><!--所在的目录--> <includes><!--包括目录下的.properties,.xml 文件都会扫描到--> <include>**/*.properties</include> <include>**/*.xml</include> </includes> <filtering>false</filtering> </resource> </resources> </build>
-
准备数据库测试用表,创建对应实体类
public class User { private Integer id; private String name; private String pwd; // 以下省略 }
-
创建 dao 接口和对应 mapper 文件
public interface UserDao { int insertUser(User user); List<User> selectUsers(); }
<?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd"> <mapper namespace="com.luis.dao.UserDao"> <insert id="insertUser"> insert into user values(#{id},#{name},#{pwd}) </insert> <select id="selectUsers" resultType="com.luis.domain.User"> select id,name,pwd from user order by id desc </select> </mapper>
-
创建 service 接口以及其实现类
public interface UserService { int addUser(User user); List<User> queryUsers(); }
public class UserServiceImpl implements UserService { private UserDao userDao; // 必须有此set方法,set注入会用到 public void setUserDao(UserDao userDao) { this.userDao = userDao; } @Override public int addUser(User user) { return userDao.insertUser(user); } @Override public List<User> queryUsers() { return userDao.selectUsers(); } }
-
创建并配置 mybatis 主配置文件
<?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0//EN" "http://mybatis.org/dtd/mybatis-3-config.dtd"> <configuration> <!--settings:控制mybatis全局行为--> <settings> <!--设置mybatis输出日志--> <setting name="logImpl" value="STDOUT_LOGGING"/> </settings> <!--设置别名--> <typeAliases> <!--这样设置后,实体类的类名就是该类全限定名称的别名,用别名代表了包名+类名--> <package name="com.luis.domain"/> </typeAliases> <!-- sql mapper(sql映射文件)的位置--> <mappers> <!--name:代表包名,这个包中所有mapper.xml一次都能加载,不用一个个配置了--> <package name="com.luis.dao"/> </mappers> </configuration>
-
创建并配置 spring 主配置文件
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd"> <!-- 把数据库的配置信息,写到一个独立的文件,编译修改数据库的配置内容。 让spring知道jdbc.properties文件的位置! --> <context:property-placeholder location="classpath:jdbc.properties"/> <!--声明数据源DateSource,作用是连接数据库的(替代mybatis中的数据源)--> <bean id="myDataSource" class="com.alibaba.druid.pool.DruidDataSource" init-method="init" destroy-method="close"> <!-- 使用属性配置文件的数据,语法: --> <!--set注入给DruidDataSource提供连接数据库信息--> <property name="url" value="${jdbc.url}"/><!--setUrl--> <property name="username" value="${jdbc.username}"/><!--setUsername--> <property name="password" value="${jdbc.password}"/><!--setPassword--> <property name="maxActive" value="${jdbc.max}"/><!--最大连接数20--> </bean> <!--声明mybatis中提供的SqlSessionFactoryBean类,这个类内部创建SqlSessionFactory对象--> <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean"> <!--set注入,把数据库连接池赋给了dateSource属性--> <property name="dataSource" ref="myDataSource"/> <!-- mybatis主配置文件的位置 configLocation属性是Resource类型,读取配置文件 它的赋值,使用value,指定文件的路径,使用classpath:表示文件的位置 --> <property name="configLocation" value="classpath:mybatis.xml"/> </bean> <!-- 创建dao对象,使用SqlSession的getMapper(StudentDao.class) MapperScannerConfigurer:在内部调用getMapper()生成每个dao接口的代理对象。 --> <bean class="org.mybatis.spring.mapper.MapperScannerConfigurer"> <!--指定SqlSessionFactory对象的id--> <property name="sqlSessionFactoryBeanName" value="sqlSessionFactory"/> <!-- 指定包名,包名是dao接口所在的包名。 MapperScannerConfigurer会扫描这个包中所有接口,把每个接口都执行 一次getMapper()方法,得到每个接口的dao对象。 创建好的dao对象放入到spring的容器中。 dao对象的默认名称:是接口名的首字母小写。 --> <property name="basePackage" value="com.luis.dao"/> </bean> <!--声明service--> <bean id="userService" class="com.luis.service.impl.UserServiceImpl"> <property name="userDao" ref="userDao"/> </bean> </beans>
-
创建连接数据库的属性配置文件
jdbc.url=jdbc:mysql://localhost:3306/mybatis jdbc.username=root jdbc.password=luis jdbc.max=20
-
创建测试类,测试
@Test public void test() { ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml"); UserService userService = (UserService) context.getBean("userService"); // 插入数据测试: int res = userService.addUser(new User(222, "luis", "223")); System.out.println(res); // 查询数据测试: // List<User> users = userService.queryUsers(); // for (User user : users) { // System.out.println(user); // } }
Spring 的事务处理
前言
回答问题
1.什么是事务
讲mysql的时候,提出了事务。 事务是指一组sql语句的集合, 集合中有多条sql语句
可能是insert , update ,select ,delete, 我们希望这些多个sql语句都能成功,
或者都失败, 这些sql语句的执行是一致的,作为一个整体执行。
2.在什么时候想到使用事务
当我的操作,涉及得到多个表,或者是多个sql语句的insert,update,delete。需要保证
这些语句都是成功才能完成我的功能,或者都失败,保证操作是符合要求的。
在java代码中写程序,控制事务,此时事务应该放在那里呢?
service类的业务方法上,因为业务方法会调用多个dao方法,执行多个sql语句
3.通常使用JDBC访问数据库, 还是mybatis访问数据库怎么处理事务
jdbc访问数据库,处理事务 Connection conn ; conn.commit(); conn.rollback();
mybatis访问数据库,处理事务, SqlSession.commit(); SqlSession.rollback();
hibernate访问数据库,处理事务, Session.commit(); Session.rollback();
4.3问题中事务的处理方式,有什么不足
1) 不同的数据库访问技术,处理事务的对象,方法不同,需要了解不同数据库访问技术使用事务的原理
2) 掌握多种数据库中事务的处理逻辑。什么时候提交事务,什么时候回顾事务
3) 处理事务的多种方法。
总结: 就是多种数据库的访问技术,有不同的事务处理的机制,对象,方法。
5.怎么解决不足
spring提供一种处理事务的统一模型, 能使用统一步骤,方式完成多种不同数据库访问技术的事务处理。
使用spring的事务处理机制,可以完成mybatis访问数据库的事务处理
使用spring的事务处理机制,可以完成hibernate访问数据库的事务处理。
6.处理事务,需要怎么做,做什么
spring处理事务的模型,使用的步骤都是固定的。把事务使用的信息提供给spring就可以了
1)spring内部提交,回滚事务,使用的事务管理器对象,代替你完成commit,rollback
事务管理器是一个接口和他的众多实现类。
接口:PlatformTransactionManager ,定义了事务重要方法 commit ,rollback
实现类:spring把每一种数据库访问技术对应的事务处理类都创建好了。
mybatis访问数据库---spring创建好的是DataSourceTransactionManager
hibernate访问数据库----spring创建的是HibernateTransactionManager
怎么使用:你需要告诉spring 你用是那种数据库的访问技术,怎么告诉spring呢?
声明数据库访问技术对应的事务管理器实现类, 在spring的配置文件中使用<bean>声明就可以了
例如,你要使用mybatis访问数据库,你应该在xml配置文件中
<bean id=“xxx" class="...DataSourceTransactionManager">
2)你的业务方法需要什么样的事务,说明需要事务的类型。
说明方法需要的事务:
1)事务的隔离级别:有4个值。
DEFAULT:采用 DB 默认的事务隔离级别。
MySql 的默认为 REPEATABLE_READ; Oracle默认为 READ_COMMITTED。
➢ READ_UNCOMMITTED:读未提交。未解决任何并发问题。
➢ READ_COMMITTED:读已提交。解决脏读,存在不可重复读与幻读。【oracle默认】
➢ REPEATABLE_READ:可重复读。解决脏读、不可重复读,存在幻读【mysql默认】
➢ SERIALIZABLE:串行化。不存在并发问题。
2) 事务的超时时间: 表示一个方法最长的执行时间,如果方法执行时超过了时间,事务就回滚。
单位是秒, 整数值, 默认是 -1. (-1表示没有时间限制)
3)事务的传播行为 : 控制业务方法是不是有事务的, 是什么样的事务的。
7个传播行为,表示你的业务方法调用时,事务在方法之间是如果使用的。
PROPAGATION_REQUIRED
PROPAGATION_REQUIRES_NEW
PROPAGATION_SUPPORTS
以上三个需要掌握的
--------------------------------------
以上三个的小写形式参考:
propagation_required
propagation_requires_new
propagation_supports
--------------------------------------------
PROPAGATION_MANDATORY
PROPAGATION_NESTED
PROPAGATION_NEVER
PROPAGATION_NOT_SUPPORTED
1) PROPAGATION_REQUIRED:
指定的方法必须在事务内执行。若当前存在事务,就加入到当前事务中;若当前没有事务,
则创建一个新事务。这种传播行为是最常见的选择,也是 Spring 默认的事务传播行为。
如该传播行为加在 doOther()方法上。若 doSome()方法在调用 doOther()方法时就是在事务内运行的,
则 doOther()方法的执行也加入到该事务内执行。若 doSome()方法在调用 doOther()方法时没有在事务内执行,
则 doOther()方法会创建一个事务,并在其中执行。
2) PROPAGATION_SUPPORTS
指定的方法支持当前事务,但若当前没有事务,也可以以非事务方式执行。
3)PROPAGATION_REQUIRES_NEW
总是新建一个事务,若当前存在事务,就将当前事务挂起,直到新事务执行完毕。
3)事务提交事务,回滚事务的时机
1)当你的业务方法,执行成功,没有异常抛出,当方法执行完毕,spring在方法执行后提交事务。
调用事务管理器的commit
2)当你的业务方法抛出运行时异常或ERROR, spring执行回滚,调用事务管理器的rollback
运行时异常的定义: RuntimeException 和他的子类都是运行时异常,
例如NullPointException , NumberFormatException
3) 当你的业务方法抛出非运行时异常, 主要是受查异常时,提交事务
受查异常:在你写代码中,必须处理的异常。例如IOException, SQLException
---------------------------------------------------------------------------------
总结spring的事务:
1.管理事务的是:事务管理器和他的实现类
2.spring的事务是一个统一模型
1)指定要使用的事务管理器实现类,使用<bean>
2)指定哪些类,哪些方法需要加入事务的功能
3)指定方法需要的隔离级别,传播行为,超时
你需要告诉spring,你的项目中类信息,方法的名称,方法的事务传播行为。
例子演示参见链接:https://www.bilibili.com/video/BV1nz4y1d7uy?p=85
下面直接写 Spring 中事务的处理步骤。
事务处理实现-注解方式
Spring 自己的事务处理——通过注解方式
注解方式使用场景:中小型项目
实际项目都比较大,则用的比较少:方法多,需要加的注解多,而且容易遗漏。
通过事务回滚可以撤销对数据库的操作,从主键设置成自动增长的,可以看出事务是否回滚。
它会生成你的代理对象,它的机制是aop的环绕通知。
spring使用aop机制,创建@Transactional所在的类代理对象,给方法加入事务的功能。
spring给业务方法加入事务:
在你的业务方法执行之前,先开启事务,在业务方法之后提交或回滚事务,使用aop的环绕通知
底层实现原理:
@Around("你要增加的事务功能的业务方法名称")
Object myAround(){
开启事务,spring给你开启
try{
buy(1001,10);
spring的事务管理器.commit();
}catch(Exception e){
spring的事务管理器.rollback();
}
}
============================重点=======================================
1、@Transactional需要加在公共方法public上
2、可以使用在类上,表示该类中所有公共方法都具有事务(但意义不大,一般开发中都是使用在需要有事务的public方法上)
2、直接写 @Transactional 即可,属性全使用默认值,最简单!!!(开发中常用此方法,简单快捷!)
3、加上@Transactional事务注解后,该注解所在的类就变成了代理了(spring内部实现)
4、适合中小项目
========================================================================
注解方式使用场景:中小型项目
使用步骤:
-
在 Spring 主配置中声明事务管理器对象
注意:
- 不同数据库需要声明的事务管理器对象不同,MySQL 需要声明 DataSourceTransactionManager 对象
- 在声明事务管理器中,还需要指定数据源
- IDEA 中可通过输入 DSTM,快速找到 DataSourceTransactionManager
-
在 Spring 主配置中开启事务注解驱动
注意:
- annotation-driven 注解来源有好几个,需要使用 tx 结尾的,即事务专用的!
- 需要配置 transaction-manager,事务管理器对象
<!-- 1.声明事务管理器对象:DataSourceTransactionManager 快捷简拼 DSTM -->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<!--需配置数据源-->
<property name="dataSource" ref="myDataSource"/>
</bean>
<!-- 2.开启事务注解驱动:注意使用 tx 结尾事务专用注解 -->
<tx:annotation-driven transaction-manager="transactionManager"/>
-
在需要使用事务处理的方法上添加
@Transactional
注解即可使用要求:该方法需要是
public
的
注意:直接在需要事务处理的方法上加@Transactional
注解即可,不用配置,使用默认参数即可!
下面附 @Transactional
配置格式,仅供参考,其实不用配置,直接添加注解即可,全部使用默认的配置。
@Transactional(
propagation = Propagation.REQUIRED, // 传播行为
isolation = Isolation.DEFAULT, // 隔离级别
readOnly = false, // 是否只读
rollbackFor = { // 遇到下列异常类自动回滚
NullPointerException.class,
NotEnoughException.class
}
)
public void buy(Integer goodsId, Integer nums) {
}
再附 Spring 主配置供参考:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:tx="http://www.springframework.org/schema/tx"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
https://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx.xsd">
<!--声明组件扫描器,扫描注解所在的包-->
<context:component-scan base-package="com.luis.service.impl"/>
<!--
把数据库的配置信息,写到一个独立的文件,编译修改数据库的配置内容。
让spring知道jdbc.properties文件的位置!
-->
<context:property-placeholder location="classpath:jdbc.properties"/>
<!--声明数据源DateSource,作用是连接数据库的(替代mybatis中的数据源)-->
<bean id="myDataSource" class="com.alibaba.druid.pool.DruidDataSource"
init-method="init" destroy-method="close">
<!--
使用属性配置文件的数据,语法:
-->
<!--set注入给DruidDataSource提供连接数据库信息-->
<property name="url" value="${jdbc.url}"/><!--setUrl-->
<property name="username" value="${jdbc.username}"/><!--setUsername-->
<property name="password" value="${jdbc.password}"/><!--setPassword-->
<property name="maxActive" value="${jdbc.max}"/><!--最大连接数20-->
</bean>
<!--声明mybatis中提供的SqlSessionFactoryBean类,这个类内部创建SqlSessionFactory对象-->
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
<!--set注入,把数据库连接池赋给了dateSource属性-->
<property name="dataSource" ref="myDataSource"/>
<!--
mybatis主配置文件的位置
configLocation属性是Resource类型,读取配置文件
它的赋值,使用value,指定文件的路径,使用classpath:表示文件的位置
-->
<property name="configLocation" value="classpath:mybatis.xml"/>
</bean>
<!--
创建dao对象,使用SqlSession的getMapper(StudentDao.class)
MapperScannerConfigurer:在内部调用getMapper()生成每个dao接口的代理对象。
-->
<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
<!--指定SqlSessionFactory对象的id-->
<property name="sqlSessionFactoryBeanName" value="sqlSessionFactory"/>
<!--
指定包名,包名是dao接口所在的包名。
MapperScannerConfigurer会扫描这个包中所有接口,把每个接口都执行
一次getMapper()方法,得到每个接口的dao对象。
创建好的dao对象放入到spring的容器中。
dao对象的默认名称:是接口名的首字母小写。
-->
<property name="basePackage" value="com.luis.dao"/>
</bean>
<!--声明service-->
<!-- <bean id="buyGoodsService" class="com.luis.service.impl.BuyGoodsServiceImpl">-->
<!-- <property name="saleDao" ref="saleDao"/>-->
<!-- <property name="goodsDao" ref="goodsDao"/>-->
<!-- </bean>-->
<!-- 1.声明事务管理器对象:DSTM -->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="myDataSource"/>
</bean>
<!-- 2.开启事务注解驱动:注意使用 tx 结尾事务专用注解 -->
<tx:annotation-driven transaction-manager="transactionManager"/>
</beans>
AspectJ 事务处理-XML
AspectJ 事务处理方式——通过配置文件实现
- 适合大型项目,有很多的类,方法,需要大量的配置事务,使用 aspectj 框架功能,在 spring 配置文件中声明类,方法需要的事务。
- 这种方式业务方法和事务配置完全分离。
此配置方式使用场景:大型项目
实际开发中,用得非常多!!!
使用步骤:
-
添加 aspectJ 依赖
<!--aspectj依赖--> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-aspects</artifactId> <version>5.2.5.RELEASE</version> </dependency>
-
声明事务管理器对象
-
声明事务方法的事务属性(隔离级别,传播行为,超时时间)
-
配置 aop
<!--声明式事务管理,和源代码完全分离的--> <!--1、声明事务管理器对象--> <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> <property name="dataSource" ref="myDataSource" /> </bean> <!-- 2、声明事务方法的事务属性(隔离级别,传播行为,超时时间) id:自定义名称,表示 <tx:advice> 和 </tx:advice> 之间的配置内容的。 transaction-manager:事务管理器对象的id --> <tx:advice id="myAdvice" transaction-manager="transactionManager"> <!--tx:attributes 配置事务属性--> <tx:attributes> <!-- tx:method 给具体的方法配置事务属性,method可以有多个,分别给不同的方法设置事务属性 name:写需要配置事务的方法名称 1)完整的方法名称,不带有包和类 2)方法可以使用通配符,* 表示任意字符 propagation:传播行为,枚举值 isolation:隔离级别 rollback-for:你指定的异常类名,全限定类名。发生异常一定会回滚。 --> <tx:method name="buy" propagation="REQUIRED" isolation="DEFAULT" rollback-for="java.lang.NullPointerException,com.linwei.excep.NotEnoughException" /> <!--使用通配符,可以指定很多的方法--> <!--指定添加方法--> <tx:method name="add*" propagation="REQUIRES_NEW" /> <!--指定修改方法--> <tx:method name="modify*" /> <!--指定删除方法--> <tx:method name="remove*" /> <!--指定查询方法,query,search,find--> <!--此处 * 是指定以上方法之外的方法--> <tx:method name="*" propagation="SUPPORTS" read-only="true" /> <!--spring中进行事务匹配优先级:先匹配有完全方法名的,再匹配带通配符的,再匹配只有* 的。--> </tx:attributes> </tx:advice> <!--3、配置aop 以上1和2步骤指定了哪些方法需要配置事务,并无法指定哪些类,哪些包,所以需要再配置以下内容--> <aop:config> <!-- 配置切入点表达式:指定哪些包中的类要使用事务 id:切入点表达式的名称,唯一值。 expression:切入点表达式,指定哪些类要使用事务,aspectj会创建代理对象 com.linwei.service com.crm.service com.service 。。。 --> <aop:pointcut id="servicePt" expression="execution(* *..service..*.*(..))" /> <!--配置增强器:关联advice和pointcut--> <aop:advisor advice-ref="myAdvice" pointcut-ref="servicePt" /> </aop:config>
Spring 中 web 项目处理
核心就是将 Spring 容器对象在全局作用域对象ServletContext
创建时,进行创建,并放入全局作用域中,实现只创建一次!
web 项目是运行在 Tomcat 服务器上的,Tomcat 一启动,项目就一直运行。
如果是 web 项目,则需要根据不同请求,由 Servlet 自动从 Spring 容器中获取相应对象,进行相关方法调用。
而每次请求都会调用一次 Servlet 中的方法,也就会使容器对象重复创建,所以需要实现容器对象只创建一次!
重点总结:web 项目中容器对象只需要创建一次,需要将容器对象放到全局作用域 ServletContext 中。
如何实现 ?
- 使用监听器,在全局作用域对象被创建的同时,创建容器对象,将其存入 ServletContext 中 !
监听器作用:
- 创建容器对象
- 将容器对象放入全局作用域对象
ServletContext
中监听器可以自己创建,也可以使用框架提供的
ContextLoderLister
。框架中的
ContextLoderLister
监听器对象已经实现了上述功能,它会在全局作用域对象被创建的同时,为我们创建容器对象,并将其存入ServletContext
中 !下面是配置和使用框架提供的
ContextLoderLister
监听器的步骤。
使用步骤:
-
添加使用监听器必要的依赖
<dependency> <groupId>org.springframework</groupId> <artifactId>spring-web</artifactId> <version>5.2.5.RELEASE</version> </dependency>
-
在 web.xml 中注册监听器
ContextLoderLister
<!--注册监听器ContextLoaderListener 监听器被创建对象后,会读取/WEB-INF/spring.xml 为什么要读取文件:因为在监听器中要创建ApplicationContext对象,需要加载配置文件。 /WEB-INF/applicationContext.xml就是监听器默认读取的spring配置文件路径 可以修改默认的文件位置,使用context-param重新指定文件的位置 配置监听器:目的是创建容器对象,创建了容器对象, 就能把spring.xml配置文件中的所有对象都创建好。 用户发起请求就可以直接使用对象了。 --> <context-param> <!--contextConfigLocation:表示配置文件的路径--> <param-name>contextConfigLocation</param-name> <!--自定义配置文件路径--> <param-value>classpath:applicationContext.xml</param-value> </context-param> <listener> <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class> </listener>
-
使用工具类获取容器对象
ServletContext sc = getServletContext(); // 获取全局作用域对象 // 通过工具类获取容器对象:WebApplicationContext extends ApplicationContext WebApplicationContext context = WebApplicationContextUtils.getRequiredWebApplicationContext(sc);
配置监听器:目的是创建容器对象,创建了容器对象, 就能把 spring.xml 配置文件中的所有对象都创建好, 用户发起请求就可以直接使用对象了。
标签:事务,对象,Spring,spring,切面,使用,方法 From: https://www.cnblogs.com/luisblog/p/16849564.html