框架怎么学习?
- 知道框架能做什么:Mybatis --> 访问数据库,对表中的数据进行增删改查
- 框架的语法:框架要完成一个功能,需要一定的步骤
- 框架的内部实现:框架的内部怎么做的,原理是什么
- 通过学习,可以实现一个框架
代码:https://gitee.com/qkmango/IdeaProjects/tree/master/Web/09_Spring
IoC控制反转
IoC (Inversion of Control) : 控制反转,是一个理论,概念,思想。
IoC介绍
ioc描述的是
把对象的创建,赋值,管理工作都交给代码之外的容器来实现,也就是对象的创建是由其他的外部资源来完成的
-
控制:创建对象,对象的属性赋值,对象之间的关系管理
-
反转:把原来开发人员管理、创建对象的工作转移给代码之外的容器实现,由容器代替开发人员创建管理对象
-
正转:由开发人员在代码中,使用 new 来创建对象,开发人员主动过管理对象
new Student()
容器:可以是一个服务器软件(Tomcat),一个框架(Spring)
为什么使用ioc
目的就是减少对代码的改动,异能实现不同的功能,实现解耦合
Java中创建对象有哪些方式?
- 构造方法
- 反射
- 序列化
- 克隆
- 动态代理
- ioc:容器创建对象
ioc的体现
servlet
- 继承HttpServlet
- 在web.xml中注册servlet
- 没有手动创建过servlet对象,却可是使用
servlet对象是由Tomcat服务器帮你创建的
IoC的技术实现
DI是ioc的技术实现
DI(Dependency Injection):依赖注入,只需要在程序中提供需要使用的对象名称就可以了,至于对象如何在容器中创建、赋值,查找 都由容器内部实现
Spring 是使用的 DI 实现了 ioc 的功能,Spring 底层创建对象最根本是使用的反射机制
Spring第一个例子
- 在创建spring容器时,会创建配置文件中的所有对象
- 在
beans.xml
配置文件中,一个bean标签就是一个对象 - spring创建对象默认调用的是无参构造方法
初步使用
-
在
resources
目录下创建beans.xml(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" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"> <!-- 告诉Spring创建对象 声明bean,就是告诉spring要创建某个类的对象 id:对象的自定义名称唯一值。spring通过这个名称找到这个对象 class:类的全限定名称 spring就完成 SomeService someService = new SomeServiceImpl() spring是把创建好的对象放入到map中,spring框架有一个map存放对象的 springMap.put(id值, 对象) 一个bean标签声明一个对象 --> <bean id="someService" class="cn.qkmango.service.impl.SomeServiceImpl" /> </beans> <!-- Spring的配置文件 beans:spring把Java对象称为bean -->
-
使用Spring容器创建对象并测试
- 指定spring容器配置文件创建对象
- 创建表示spring容器的对象,ApplicationContext
- 从容器中获对象
- 使用Spring创建好的对象
@Test public void test02() { //1. 指定spring容器配置文件创建对象 String config = "beans.xml"; //2. 创建表示spring容器的对象,ApplicationContext // ApplicationContext就表示spring容器,通过这个容器,就能获取对象了 // ClassPathXmlApplicationContext:表示从类路径中加载Spring配置文件 ApplicationContext app = new ClassPathXmlApplicationContext(config); //3. 从容器中获对象 // getBean("beans.xml配置文件中bean的id") SomeService someService = (SomeService) app.getBean("someService"); //使用Spring创建好的对象 someService.doSome(); }
spring默认创建对象的时间
spring默认创建对象的时间:在创建spring容器时,会创建配置文件中的所有对象
如何理解在创建spring容器时,会创建配置文件中的所有对象?
在beans.xml
配置文件中,一个bean标签就是一个对象,即使bean标签中存在相同的class
获取Spring容器中Java对象的信息
ac.getBeanDefinitionCount()
获取Spring容器中对象的数量
ac.getBeanDefinitionNames()
获取Spring容器总对象的名称(id)
@Test
public void test03() {
ClassPathXmlApplicationContext ac = new ClassPathXmlApplicationContext("beans.xml");
//使用spring提供的方法,获取容器中定义的对象数量
int nums = ac.getBeanDefinitionCount();
System.out.println("容器中定义的对象数量:"+nums);
//获取容器中定义的对象名称
String[] names = ac.getBeanDefinitionNames();
for (String name :names) {
System.out.println(name);
}
/**
* SomeServiceImpl:执行了构造方法
* SomeServiceImpl:执行了构造方法
* 容器中定义的对象数量:2
* someService
* someService1
*/
}
基于XML的DI
DI依赖注入,表示创建对象,给属性赋值
DI的实现语法有两种
- 在Spring的配置文件中,使用标签和属性完成,叫做基于XMl的DI实现
- 使用spring中的注解完成属性的赋值,叫做基于注解的DI实现
DI的语法分类
- set注入:spring调用类的set(),实现赋值
- 构造注入:spring调用类的有参构造方法,在构造方法中完成赋值xml
⚠️注意:通过set注入,Spring只是负责调用相应的setter方法而已,至于这个方法是否进行了赋值,这个方法是否有对应的属性,Spring是不管的。即Spring只是调用相关的方法,无关方法内的逻辑。
简单类型:spring中规定,Java的基本数据类型和String都是简单类型
1. set注入(set赋值):spring调用类的set()
- 简单类型的set注入(属性是简单类型的)
XML-set注入
使用在XML配置文件中,使用set方式注入属性
set注入依赖于类中的相关set(),<property name="age" value="20"/>
,就会根据命名规范调用setName()
进行赋值,所以要通过set注入属性值,一定要有对于的setter方法
简单类型set注入
给任意类的简单类型属性进行赋值,使用<property name="age" value="20"/>
📜applicationContext.xml配置文件(部分)
<bean id="Student" class="cn.qkmango.ba01.Student">
<property name="name" value="芒果小洛" />
<property name="age" value="20"/>
</bean>
🚀test
Student student = (Student) ac.getBean("Student");
System.out.println(student);
//Student{name='芒果小洛', age=20}
引用类型set注入
对象中属性有引用类型的时候时,使用<property name="school" ref="mySchool"/>
进行赋值,这个标签的ref
属性就是这个引用类型的bean标签的id,相对于嵌套关系
📜applicationContext.xml配置文件(部分)
<bean id="Student" class="cn.qkmango.ba02.Student">
<property name="name" value="芒果小洛" />
<property name="age" value="20"/>
<property name="school" ref="mySchool"/>
</bean>
<bean id="mySchool" class="cn.qkmango.ba02.School" >
<property name="name" value="axhu" />
<property name="address" value="hefei"/>
</bean>
🚀test
cn.qkmango.ba02.Student student = (cn.qkmango.ba02.Student) ac.getBean("Student");
System.out.println(student);
//Student{name='芒果小洛', age=20, school=School{name='axhu', address='hefei'}}
XML-构造注入
spring调用类的有参构造方法,在创建对象的同时,在构造方法中给属性赋值
使用 <constructor-arg>
标签,一个标签表示一个构造函数的参数
- name属性:构造函数中形参的名
- index:形参的位置,0,1,2,3…
- value:简单类型使用
- ref:引用类型使用,值为引用对象的bean标签的id
了解:大部分都使用name属性指定构造方法的形参名,也可以使用index来指定形参的位置,当<constructor-arg>
标签的顺序符合构造方法形参的顺序时,时可以省略index的
📜applicationContext.xml配置文件(部分)
<!-- 使用name指定构造方法形参名 -->
<bean id="Student1" class="cn.qkmango.ba03.Student">
<constructor-arg name="name" value="芒果小洛"/>
<constructor-arg name="age" value="20"/>
<constructor-arg name="school" ref="mySchool1" />
</bean>
<!-- 使用index指定构造方法形参位置 -->
<bean id="Student2" class="cn.qkmango.ba03.Student">
<constructor-arg index="0" value="芒果味的"/>
<constructor-arg index="1" value="19"/>
<constructor-arg index="2" ref="mySchool1" />
</bean>
<!-- 当 <constructor-arg> 顺序与构造方法形参循序一样时,可以省略index -->
<bean id="Student3" class="cn.qkmango.ba03.Student">
<constructor-arg value="柠檬味的"/>
<constructor-arg value="18"/>
<constructor-arg ref="mySchool1" />
</bean>
<bean id="mySchool1" class="cn.qkmango.ba03.School" >
<constructor-arg name="name" value="axhu"/>
<constructor-arg name="address" value="hefei"/>
</bean>
🚀test
@Test
public void test03() {
ClassPathXmlApplicationContext ac = new ClassPathXmlApplicationContext("ba03/applicationContext.xml");
Student student = null;
student = (Student) ac.getBean("Student1");
System.out.println(student);
// Student{name='芒果小洛', age=20, school=School{name='axhu', address='hefei'}}
student = (Student) ac.getBean("Student2");
System.out.println(student);
// Student{name='芒果味的', age=19, school=School{name='axhu', address='hefei'}}
student = (Student) ac.getBean("Student3");
System.out.println(student);
// Student{name='柠檬味的', age=18, school=School{name='axhu', address='hefei'}}
}
XMl-自动注入
引用类型自动注入:spring框架根据某些规则可以给引用类型赋值注入,不用你再给引用类型赋值了,使用的规则常用的是 byName、byType
byName(按名称注入)
类中引用类型的属性名和spring容器中(配置文件)<bean>
的id一样,且数据类型是一致的,这样的容器中的bean,spring能够赋值给引用类型
<bean id="xx" class="yyy" autowire="byName"></bean>
📜ApplicationContext配置文件(部分)
<!--指定 autowire="byName" ,会将类中的属性根据属性名,注入id与此属性名相同的bean对象-->
<bean id="student" class="cn.qkmango.ba04.Student" autowire="byName">
<property name="name" value="芒果小洛"/>
<property name="age" value="20" />
</bean>
<bean id="school" class="cn.qkmango.ba04.School" >
<constructor-arg name="name" value="新华学院"/>
<constructor-arg name="address" value="安徽合肥"/>
</bean>
🚀test
@Test
public void testByName(){
ApplicationContext context = new ClassPathXmlApplicationContext("ba04/applicationContext.xml");
Student student = (Student) context.getBean("student");
System.out.println(student);
//Student{name='芒果小洛', age=20, school=School{name='新华学院', address='安徽合肥'}}
}
byType(按类型注入)
类中引用类型的数据类型和spring容器中(配置文件)<bean>
的class属性是同源关系的,这样的bean能够赋值给引用类型
<bean id="xx" class="yyy" autowire="byType"></bean>
同源就是同一类的意思:
- 同一个类(将同一个类型的bean赋值给属性)
- 父子类关系(将子类赋值给父类属性引用:父类引用指向子类对象)
- 接口和实现类关系(将实现类赋值给接口类属性)
⚠️通过byType自动注入,注入的bean必须是唯一的(不能是多个同源),否则会报错:如多个子类(多个同源)、多个实现类(多个同源)
同源第1种情况:同一个类
📜ApplicationContext配置文件(部分)
<bean id="student" class="cn.qkmango.ba04.testByType01.Student" autowire="byType">
<property name="name" value="芒果小洛"/>
<property name="age" value="20" />
</bean>
<bean id="school" class="cn.qkmango.ba04.testByType01.School" >
<constructor-arg name="name" value="新华学院"/>
<constructor-arg name="address" value="安徽合肥"/>
</bean>
🚀test
@Test
//byType:同源第2种情况:父子类关系
public void testByType01() {
ApplicationContext context = new ClassPathXmlApplicationContext("ba04/testByType02/applicationContext.xml");
cn.qkmango.ba04.testByType02.Student student2 = (cn.qkmango.ba04.testByType02.Student) context.getBean("student2");
System.out.println(student2);
//Student{name='芒果味的', age=19, school=School{name='小学', address='合肥'}}
}
同源第2种情况:父子类关系
📜ApplicationContext配置文件(部分)
<bean id="student2" class="cn.qkmango.ba04.testByType02.Student" autowire="byType">
<property name="name" value="芒果味的"/>
<property name="age" value="19" />
</bean>
<!--
class School
class PrimarySchool extends School
-->
<bean id="primarySchool" class="cn.qkmango.ba04.testByType02.PrimarySchool" >
<property name="name" value="小学"/>
<property name="address" value="合肥"/>
</bean>
🚀test
@Test
//byType:同源第2种情况:父子类关系
public void testByType02() {
ApplicationContext context = new ClassPathXmlApplicationContext("ba04/testByType02/applicationContext.xml");
cn.qkmango.ba04.testByType02.Student student2 = (cn.qkmango.ba04.testByType02.Student) context.getBean("student2");
System.out.println(student2);
//Student{name='芒果味的', age=19, school=School{name='小学', address='合肥'}}
}
同源第3种情况:接口和实现类关系
📜ApplicationContext配置文件(部分)
<bean id="student3" class="cn.qkmango.ba04.testByType03.Student" autowire="byType">
<property name="name" value="芒果味的"/>
<property name="age" value="19" />
</bean>
<!--
interface School
class PrimarySchool implements School
-->
<bean id="primarySchool" class="cn.qkmango.ba04.testByType03.PrimarySchool" >
<property name="name" value="小学(接口)"/>
<property name="address" value="合肥"/>
</bean>
🚀test
@Test
//byType:同源第3种情况:接口
public void testByType03() {
ApplicationContext context = new ClassPathXmlApplicationContext("ba04/testByType03/applicationContext.xml");
cn.qkmango.ba04.testByType03.Student student3 = (cn.qkmango.ba04.testByType03.Student) context.getBean("student3");
System.out.println(student3);
//Student{name='芒果味的', age=19, school=School{name='小学(接口)', address='合肥'}}
}
Spring的注解
如何使用
-
加入Maven依赖 spring-context,在你加入spring-context的同时,间接的加入spring-aop的依赖了,使用注解必须使用spring-aop依赖
-
在类中加入spring的注解(多个不同功能的注解)
package cn.qkmango.ba01; import org.springframework.stereotype.Component; @Component(value="myStudent") public class Student { // }
-
在spring的配置文件中,加入一个组件扫描器的标签,说明注解在项目中的位置
<beans> <context:component-scan base-package="cn.qkmango.ba01" /> </beans>
组件扫描器 component-scan
- 组件就是Java对象
- base-package:指定包含注解的类所在的包
- component-scan工作方式:spring会扫描遍历 base-package 指定的包,把包中和子包中所有的类,找到类中的注解,按照注解的功能创建对象,给属性赋值
学习的注解
以下注解都能创建对象,不过创建的对象代表着不同的功能,给不同的类,赋予不同的角色
注解 | 功能 |
---|---|
@Component | 创建对象 |
@Repository | 持久层:放在dao实现类上,表示创建dao对象,dao对象就是能访问数据库的,持久层的 |
@Service | 业务层:放在service实现类上,表示创建service对象,service对象是做业务处理的,可以有事务等功能的 |
@Controller | 控制层:放在控制器类的上面,创建控制器对象的,能够接受用户提交的参数,响应请求的处理结果 |
@Value | |
@Autowired | |
@Resource |
@Controller、@Service、@Repository是给项目的对象分层的:控制层、业务层、数据访问层
通过注解创建Java对象
通过注解来创建普通Java对象(无特殊功能的,不是控制层、业务层、持久层 的Java对象)
@Component注解只有一个属性value,所以可以有一下写法
@Component(value="myStudent")//不省略value属性名
@Component("myStudent") //省略value属性名(常用)
@Component //spring默认指定value属性(默认为类名小写)
带有@Componen注解的类
@Component
public class Student {
//
}
📜applicationContext配置文件
<beans>
<context:component-scan base-package="cn.qkmango.ba01" />
</beans>
🚀test
@Test
public void test() {
ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
Student myStudent = (Student) context.getBean("myStudent");
System.out.println(myStudent);
//Student{name='null', age=0}
}
基于注解的DI
简单类型注入 @Value
使用
@Value
注解对简单类型的属性进行赋值
使用位置
- 在属性字段上使用:在定义的属性上使用 @Value 注解来对简单类型的属性进行赋值,无需set方法(推荐)
- 在set方法上使用:在属性对应的set方法上使用 @Value 注解来对简单类型的属性进行赋值
@Component("student")
public class Student {
@Value("芒果小洛")
private String name;
private int age;
public void setName(String name) {
this.name = name;
}
@Value("18")
public void setAge(int age) {
this.age = age;
}
}
byType引用类型自动注入 @Autowired
使用
@Autowired
注解对引用类性的赋值(byType)
使用位置
- 在属性的定义上面使用,无需set方法(推荐使用)
- 在set方法上面使用
使用案例
@Component("student03")
public class Student {
//自动注入,byType方式
@Autowired
private School school;
}
byName引用类型自动注入 @Autowired @Qualifier
自动注入,通过
@Qualifier
注解指定将哪个bean注入到属性,此时使用的是自动注入的byName模式
属性
@Autowired
有required属性,boolean值,默认为true
属性值 | 介绍 |
---|---|
@Autowired(required = true) | 当使用@autowired 指定的bean的id没找到时,则报错,并终止执行(推荐) |
@Autowired(required = false) | 当使用@autowired 指定的bean的id没找到时,程序正常执行,没有找到则不赋值(即为null) |
使用案例
@Component("student03")
public class Student {
//自动注入,byName方式,指定bean的id
//默认 @Autowired(required = true):当使用 @autowired 指定的bean的id没找到时,则报错,并终止执行(推荐)
@Autowired
@Qualifier("school03")
private School school;
}
JDK引用类型自动注入 @Resource
来自jdk中的注解,spring框架提供了对这个注解的功能支持,可以使用它给引用类型赋值,使用的也是自动注入的原理
使用位置
- 在属性的定义上面使用,无需set方法(推荐使用)
- 在set方法上面使用
属性
属性值 | 介绍 |
---|---|
@Resource | 先使用byName自动注入,如果byName赋值失败,再使用byType |
@Resource(name=“bean_id”) | 指定 name 属性,则只使用byName |
使用案例
import javax.annotation.Resource;
@Component("student04")
public class Student {
@Resource(name = "school04")
private School school;
//...
}
注解使用总结
简单类型赋值注解
@Value(“value”)
引用类型赋值注解
注解与属性 | 注入方式 | 介绍 |
---|---|---|
@Autowired | byType | |
@Autowired @Qualifier("bean_id") | byName | 没找到则报错 |
@Autowired(required = false) @Qualifier("bean_id") | byName | 没找到则不赋值 |
@Resource | byName > byType | JDK注解 |
@Resource(name="bean_id") | byName | JDK注解 |
多配置文件
spring 主配置文件,包含其他配置文件,主配置文件一般不定义对象
如何包含其他配置文件?
<import resource="classpath:spring-school.xml"/>
多个配置优势
- 每个文件的大小比一个文件要小很多,效率高
- 避免多人同时编辑带来的冲突
多文件的分配方式
- 按功能模块,一个模块一个配置文件
- 按类的功能,数据库相关的配置一个文件配置文件,做事务的功能一个配置文件,做service功能的一个配置文件等
多配置文件案例
resources
applicationContext.xml(主配置文件)
spring-school.xml(student相关类的配置文件)
spring-student.xml(school相关类的配置文件)
📜applicationcContext(部分)
<beans>
<!--每个文件分别包含-->
<import resource="classpath:spring-school.xml"/>
<import resource="classpath:spring-student.xml"/>
<!-- 使用通配符包含
注意:通配符包含的配置文件不能包含主配置文件(套娃)-->
<import resource="spring-*.xml"/>
</beans>
📜student相关类的配置文件(部分)
<!-- Spring student相关类的配置文件-->
<bean id="student" class="cn.qkmango.Student" >
<property name="name" value="芒果小洛"/>
<property name="age" value="20"/>
<property name="school" ref="school"/>
</bean>
📜school相关类的配置文件(部分)
<!-- Spring school相关类的配置文件-->
<bean id="school" class="cn.qkmango.School">
<property name="name" value="新华学院"/>
<property name="address" value="合肥"/>
</bean>
配置文件路径问题
classpath:
前缀,代表一个路径,相当于/WIN-INF/classes/
- 加上此前缀,则resource表示的是一个绝对路径,
- 不加此前缀,则resource表示的是一个相对路径,
⚠️当使用通配符引入文件时,且使用 classpath: 前缀,则被引入的文件必须在文件夹下(不能直接在根目录下)
⚠️当使用通配符引入文件时,且被包含的配置文件在根目录下,则不能加上 classpath: ,只能使用相对路径
AOP面向切面编程
Aspect Orient Programming 面向切面编程
面向切面编程,基于动态代理,可以使用jdk、cglib两种动态代理方式;AOP就是动态代理的规范化,把动态代理的实现步骤,方式都定义好了,让开发人员用一种统一的方式使用动态代理
动态代理
动态代理实现方式
-
jdk动态代理
使用jdk中的Proxy,Method,InvocationHandler创建代理对象。jdk动态代理要求目标类必须实现接口
-
cglib动态代理
第三方的工具库,创建代理对象,原理是继承。 通过继承目标类,创建子类。子类就是代理对象。 要求目标类不能是final的, 方法也不能是final的
动态代理的作用
- 在目标类源代码不改变的情况下,增加功能
- 减少代码的重复
- 专注业务逻辑代码
- 解耦合,让你的业务功能和日志,事务非业务功能分离
AOP术语
Aspect 切面
表示增强的功能,就是一堆代码,完成某一个非业务功能。常见的切面功能有日志、事务、统计信息、参数检查、权限验证
JoinPoint 连接点
连接业务方法和切面的位置,也就是指的是业务方法,业务方法被动态代理增强了功能,业务方法和增强的功能连接点就是业务方法
Pointcut 切入点
切面的执行位置,指多个连接点方法的集合,多个方法
目标对象
给哪个类的方法增加功能,这个类就是目标对象
Advice 通知
通知表示切面功能执行的时机
AOP的实现
aop是一个规范,是动态的一个规范化,一个标准
aop的技术实现框架
-
spring:spring在内部实现了aop规范,能做aop的工作;spring主要在事务处理时使用aop。我们在项目开发中很少用spring的aop实现,因为spring的aop比较笨重
-
aspectJ:来自于eclipse开源项目,一个专门做aop的框架。spring框架中已经集成了aspectj框架,通过spring就能使用aspectj的功能。
AspectJ框架
切面的执行时间, 这个执行时间在规范中叫做Advice(通知,增强);
在aspectj框架中使用注解表示的,也可以使用xml配置文件中的标签;
切面的执行时刻
@Before
@AfterReturning
@Around
@AfterThrowing
@After
AspectJ切入点表达式
execution([访问权限] 返回值类型 方法名(参数) [异常类型])
符号 | 意义 |
---|---|
* | 通配符,任意 |
.. | 在方法参数中,表示任意多个参数 用在包名后,表示当前包及其子包(或包中的所有类) |
+ | 用在类名(接口)后,表示当前类(接口)及其子类(实现类) |
示例
通过方法名定义切点
execution(public * *(..))
匹配所有目标类的public
方法,第一个*
代表方法返回值类型,第二个*
代表方法名,而..
代表任意入参的方法;
execution(* *To(..))
匹配目标类所有以To为后缀的方法。第一个*
代表任意方法返回类型,而*To
代表任意以To结尾的方法名。
通过类定义切点
execution(* com.taotao.Waiter.*(..))
匹配Waiter接口的所有方法,第一个*
代表任意返回类型,com.taotao.Waiter.*
代表Waiter接口中的所有方法。
execution(* com.taotao.Waiter+.*(..))
匹配Waiter接口及其所有实现类的方法
通过包名定义切点
注意:在包名模式串中,.*
表示包下的所有类,而..*
表示包、子孙包下的所有类。
execution(* com.taotao.*(..))
匹配com.taotao包下所有类的所有方法
execution(* com.taotao..*(..))
匹配com.taotao包及其子孙包下所有类的所有方法,如com.taotao.user.dao,com.taotao.user.service等包下的所有类的所有方法。
execution(* com..*.*Dao.find*(..))
匹配以com开头的任何包名下后缀为Dao的类,并且方法名以find为前缀,如com.taotao.UserDao#findByUserId()、com.taotao.dao.ForumDao#findById()的方法都是匹配切点。
通过方法入参定义切点
切点表达式中方法入参部分比较复杂,可以使用*
和..
通配符,其中*
表示任意类型的参数,而..
表示任意类型参数且参数个数不限。
* joke(String, *)
匹配目标类中joke()方法,该方法第一个入参为String类型,第二个入参可以是任意类型
execution(* joke(String, int))
匹配类中的joke()方法,且第一个入参为String类型,第二个入参 为int类型
execution(* joke(String, ..))
匹配目标类中joke()方法,该方法第一个入参为String,后面可以有任意个且类型不限的参数
实现步骤
-
加入依赖
- spring依赖
- aspectj依赖
<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>
-
创建目标类
-
创建切面类(普通的类)
-
在类的上面加入
@Aspect
注解 -
在类中定义方法,方法就是切面要执行的功能代码
-
在方法上加入aspectJ中的通知注解,入
@Before
-
在方法上指定切入点表达式
execution()
-
-
声明对象,将对象交给spring统一管理(注解
@Component
、xml<bean/>
)- 声明目标对象
- 声明切面类对象
-
声明aspectj框架中的自动代理生成器标签
完成代理对象自动创建功能的
<aop:aspectj-autoproxy/>
-
测试:
- 从spring容器中获取目标对象(实际上就是代理对象)
- 通过实行方法,实现aop的功能增强
创建切面类方法定义的要求
- 公共方法 public
- 方法没有返回值
- 方法可以有参数,也可以没有参数
- 如果有参数,参数不是自定义的,有几个参数类型可以使用
@Before
前置通知
前置通知注解,在目标方法之前执行
注解属性
- value:切入点表达式,@Before(“execution(* *…ba01…doSome(…))”)
方法参数
- 可选参数 JoinPoint
示例
<!--applicationContext.xml配置文件-->
<beans>
<!-- 目标对象-->
<bean id="someService" class="cn.qkmango.ba01.SomeServiceImpl"/>
<!-- 切面类对象-->
<bean id="myAspect" class="cn.qkmango.ba01.MyAspect"/>
<!-- 自动代理生成器:使用aspectj框架内部的功能,创建目标对象的代理对象。
创建代理对象是在内存中实现的,修改目标对象的内存中的结构,所以目标对象就是被修改后的代理对象 -->
<aop:aspectj-autoproxy/>
</beans>
//接口
public interface SomeService {
void doBefore(String name,Integer age);
}
//实现类,目标方法
public class SomeServiceImpl implements SomeService{
@Override
public void doBefore(String name,Integer age) {
//给doBefore增加一个方法:在方法执行前,输出方法的执行时间
System.out.println("目标方法doBefore()");
}
}
//通知
@Before("execution(* *..ba01..doBefore(..))")
public void myBefore(JoinPoint jp) {
System.out.println(jp.getSignature());
for (Object arg : jp.getArgs()) {
System.out.println("param:"+arg);
}
//切面要指定的功能代码
System.out.println("前置通知,切面增强功能,目标方法执行前输出时间:" + new Date());
}
Test
@Test
public void test01() {
ApplicationContext context = new ClassPathXmlApplicationContext("ba01/applicationContext.xml");
SomeService someService = context.getBean("someService", SomeService.class);
someService.doBefore("芒果小洛",18);
// void cn.qkmango.ba01.SomeService.doBefore(String,Integer)
// param:芒果小洛
// param:18
// 前置通知,切面增强功能,目标方法执行前输出时间:Mon Apr 12 22:16:04 CST 2021
// 目标方法doBefore()
}
通知方法JoinPoint参数
获取被增强的业务方法的相关信息
通知方法中的参数:JoinPoint
作用是可以 在通知方法中获取方法的执行信息,如方法名称、方法参数
如果你的 切面功能中需要用到方法的信息,就加入JoinPoint参数
这个JoinPoint参数的值是框架赋予的, 必须是第一个位置的参数
JoinPoint参数可以在任何通知(增强)方法中使用,注意:环绕通知@Around,方法可选参数ProceedingJoinPoint就是JoinPoint的子类
如下通知,使用了JoinPoint参数,获取业务方法的相关信息
@Before("execution(* *..ba01..doSome(..))")
public void myBefore(JoinPoint jp) {
//获取被增强的方法的定义
System.out.println(jp.getSignature());
//获取被增强的方法的参数
for (Object arg : jp.getArgs()) {
System.out.println("param:"+arg);
}
System.out.println("前置通知,切面增强功能,目标方法执行前输出时间:" + new Date());
}
测试
@Test
public void test01() {
ApplicationContext context = new ClassPathXmlApplicationContext("ba01/applicationContext.xml");
SomeService someService = context.getBean("someService", SomeService.class);
someService.doSome("芒果小洛",18);
// void cn.qkmango.ba01.SomeService.doSome(String,Integer)
// param:芒果小洛
// param:18
// 前置通知,切面增强功能,目标方法执行前输出时间:Sun Apr 11 21:30:53 CST 2021
// 目标方法doSome()
}
@AfterReturning
后置通知
后置通知,目标方法执行后再执行
能够获取到目标方法的返回值
注解属性
-
value:切入点表达式
-
returning:自定义的变量,指明通知方法的形参中,哪个参数代表着目标方法的执行结果;
如下示例通知方法
res
形参就是代表目标方法执行后返回值,通过注解returning
属性指定
方法参数
-
可选参数:JoinPoint
-
必选参数:目标方法的返回值引用
Object res
,类型自定义,推荐Object表示目标方法执行后,方法的返回值
示例
<!--applicationContext.xml配置文件-->
<bean>
<!-- 目标对象-->
<bean id="someService" class="cn.qkmango.ba02.SomeServiceImpl"/>
<!-- 切面类对象-->
<bean id="myAspect" class="cn.qkmango.ba02.MyAspect"/>
<!-- 自动代理生成器 -->
<aop:aspectj-autoproxy/>
</bean>
//实现类,目标方法
public class SomeServiceImpl implements SomeService {
@Override
public String doAfterReturning(String name, Integer age) {
//给doAfterReturning增加一个方法:在方法执行后,模拟提交事务
System.out.println("目标方法doAfterReturning()执行");
return name;
}
}
//通知方法
@AfterReturning(value = "execution(* *..SomeServiceImpl.doAfterReturning(..))",
returning = "res")
public void myAfterReturning(JoinPoint jp,Object res) {
System.out.println("@AfterReturning后置通知,目标方法返回值是:"+res);
System.out.println("方法的定义: "+jp.getSignature());
}
测试
@Test
public void test01() {
ApplicationContext context = new ClassPathXmlApplicationContext("ba02/applicationContext.xml");
SomeService someService = context.getBean("someService", SomeService.class);
someService.doAfterReturning("芒果小洛",18);
// 目标方法doAfterReturning()执行
// @AfterReturning后置通知,目标方法返回值是:芒果小洛
// 方法的定义: String cn.qkmango.ba02.SomeService.doAfterReturning(String,Integer)
}
@Around
环绕通知
通知内部控制目标方法的执行
- 她是功能最强的通知
- 可以在目标方法前和后都能增强功能
- 控制目标方法是否被调用执行(通过方法的参数 ProceedingJoinPoint 控制调用)
- 通知方法的返回值就是目标方法调用处的执行结果返回值
- 环绕通知经常做的是事务:目标方法执行前开启事务 > 执行目标方法 > 之后提交事务
注解属性
- value:切入点表达式
方法定义
- 通知方法必须有一个返回值,这个返回值就是目标方法的执行结果的返回值
方法参数
- ProceedingJoinPoint:可选参数, 控制目标方法的执行,继承JoinPoint,就相当于jdk动态代理的 Method
示例
<!--applicationContext.xml配置文件-->
<bean>
<!-- 目标对象-->
<bean id="someService" class="cn.qkmango.ba03.SomeServiceImpl"/>
<!-- 切面类对象-->
<bean id="myAspect" class="cn.qkmango.ba03.MyAspect"/>
<!-- 自动代理生成器 -->
<aop:aspectj-autoproxy/>
</bean>
//实现类,目标方法
public class SomeServiceImpl implements SomeService {
@Override
public String doAround(String name, Integer age) {
//给doOther增加一个方法:在方法执行后,模拟提交事务
System.out.println("目标方法doAround()执行");
return name;
}
}
//通知方法
@Around("execution(* *..*Impl.doAround(..))")
public Object myAfterReturning(ProceedingJoinPoint pjp) throws Throwable {
System.out.println("环绕通知:目标方法调用前");
//根据参数,控制调用目标方法的执行
Object result = null;
int age = (int)pjp.getArgs()[1];
if (age>=18) {
result = pjp.proceed();//相当于method.invoke()
}
System.out.println("环绕通知:目标方法调用后");
//修改返回值结果
return "Hello AspectJ: "+result;
}
测试
@Test
public void test01() {
ApplicationContext context = new ClassPathXmlApplicationContext("ba03/applicationContext.xml");
SomeService someService = context.getBean("someService", SomeService.class);
String result = null;
result = someService.doAround("芒果小洛", 17);
System.out.println(result);
result = someService.doAround("草莓味的", 18);
System.out.println(result);
// 环绕通知:目标方法调用前
// 环绕通知:目标方法调用后
// Hello AspectJ: null
// 环绕通知:目标方法调用前
// 目标方法doAround()执行
// 环绕通知:目标方法调用后
// Hello AspectJ: 草莓味的
}
@AfterThrowing
异常通知
- 在目标方法抛出异常时执行的
- 可以做异常的监控程序,监控目标方法执行时是否由异常,如果有异常,可以进行发邮件、发短信进行通知等
注解属性
- value:切入点表达式
- throwing:指定方法形参中,哪个形参是目标方法执行时抛出的异常对象(目标方法执行抛出的异常对象)
方法定义
- 无需返回值
方法参数
- 可选参数:JoinPoint
- 必选参数:Exception:目标方法执行时抛出的异常对象
模拟执行步骤
try {
someService.doIt(1,0);
//目标方法执行
} catch(Exception e) {
//异常通知方法执行
myAfterThrowing(e);
}
示例
<!--applicationContext.xml配置文件-->
<bean>
<!-- 目标对象-->
<bean id="someService" class="cn.qkmango.ba04.SomeServiceImpl"/>
<!-- 切面类对象-->
<bean id="myAspect" class="cn.qkmango.ba04.MyAspect"/>
<aop:aspectj-autoproxy/>
</bean>
//实现类,目标方法
public class SomeServiceImpl implements SomeService {
@Override
public void doAfterThrowing(int x, int y) {
System.out.println("目标方法doAfterThrowing执行:x / y = "+(x/y));
}
}
//异常通知方法
@AfterThrowing(value = "execution(* *..ba04.*Impl.doAfterThrowing(..))",
throwing = "e")
public void myAfterThrowing(JoinPoint jp,Exception e) throws Throwable {
System.out.println("异常通知:方法发生异常时执行,异常信息为:" + e.getMessage());
}
测试
@Test
public void test01() {
ApplicationContext context = new ClassPathXmlApplicationContext("ba04/applicationContext.xml");
SomeService someService = context.getBean("someService", SomeService.class);
someService.doAfterThrowing(1,1);
//正常执行:目标方法doAfterThrowing()执行:x / y = 1
someService.doAfterThrowing(1,0);
//执行异常通知 1/0:异常通知:方法发生异常时执行,异常信息为:/ by zero
}
@After
最终通知
- 总是会执行(无论是否有异常)
- 在目标方法之后执行
- 一般做资源清除工作的
注解属性
- value:切入点表达式
方法参数
- 可选参数 JoinPoint
模拟执行步骤
try {
//目标方法执行
someService.doAfter();
} catch(Exception e) {
//
} finally {
//最终通知方法执行
myAfter();
}
示例
<!--applicationContext.xml配置文件-->
<bean>
<!-- 目标对象-->
<bean id="someService" class="cn.qkmango.ba05.SomeServiceImpl"/>
<!-- 切面类对象-->
<bean id="myAspect" class="cn.qkmango.ba05.MyAspect"/>
<aop:aspectj-autoproxy/>
</bean>
//实现类,目标方法
public class SomeServiceImpl implements SomeService {
@Override
public void doAfter() {
System.out.println("目标方法doAfter()执行");
}
}
//最终通知方法
@After("execution(* *..ba05.*Impl.doAfter(..))")
public void myAfter(JoinPoint jp){
System.out.println("最终通知:在方法的最后执行");
}
Test
@Test
public void test01() {
ApplicationContext context = new ClassPathXmlApplicationContext("ba05/applicationContext.xml");
SomeService someService = context.getBean("someService", SomeService.class);
someService.doAfter();
// 目标方法doAfter()执行
// 最终通知:在方法的最后执行
}
@Pointcut
切入点表达式别名
使用
@Pointcut
注解对切入点表达式定义别名,已达到复用效果
如何使用
- 定义空的方法,在空方法上使用注解,方法名就是切入点表达式的别名
- 在需要使用切入点表达式的地方(各种通知注解的value熟悉值)设置为 上面定义的
空方法名()
注解属性
- value:切入点表达式
示例
以下示例,要在目标方法执行前添加前置通知,在目标方法执行后添加最终通知,
所以两个通知方法的切入点表达式是相同的,为了避免写重复代码,
可以将相同的切入点表达式定义别名,然后在两个通知注解中使用别名
//通知类
@Aspect
public class MyAspect {
//定义切入点表达式别名为:myPointcut()
@Pointcut("execution(* *..ba06.*Impl.doPointcut(..))")
public void myPointcut() {}
//使用切入点表达式别名
@Before("myPointcut()")
public void myBefore(JoinPoint jp){
System.out.println("前置通知执行");
}
//使用切入点表达式别名
@After("myPointcut()")
public void myAfter(JoinPoint jp){
System.out.println("最终通知执行");
}
}
Spring整合MyBatis
把Mybatis框架和Spring集成在一起,像一个框架一样使用
Mybatis使用Druid连接池,内置连接池性能功能较弱
spring整合Mybatis的想法:使用spring的ioc技术,把Mybatis框架中使用的对象交给spring统一创建和管理
本章主要讲Mybatis配置在spring,主要是applicationContext.xml配置文件
回顾Mybatis
使用步骤
-
配置Mybatis的主配置文件 mybatis.xml
-
定义Dao接口
-
配置mapper.xml文件(映射文件)
-
创建dao的代理对象
Student dao SqlSession.getMapper(Student.class); List<Student> students = dao.selectStudents();
Dao对象使用条件
要使用dao对象,需要使用getMapper()方法,怎么才能使用getMapper()方法,需要哪些条件?
- 获取SqlSession对象,需要使用SqlSessionFactory的openSession()方法
- 创建SqlSessionFactory对象,通过读取Mybatis的主配置文件,才能创建SqlSessionFactory对象
- 使用Druid数据库连接池来替换Mybatis内置的连接池
需要spring管理哪些对象
还需要**Druid连接池对象
**,来管理数据库的连接
需要**SqlSessionFactory
**对象,使用SqlSessionFactory对象能获取SqlSession对象
有了**SqlSession
**就能有Dao对象
有**Dao
**对象,就能操作数据库
所以本章需要学习的就是上面三个对象的创建语法(使用xml的bean标签)
使用Spring集成Mybatis
步骤
-
加入依赖
-
spring依赖
-
Mybatis依赖
-
MySQL驱动
-
spring事务依赖
-
Mybatis和spring继承的依赖:Mybatis官方提供的,用来在spring项目中创建Mybatis的SqlSessionFactory、Dao对象的
-
-
创建实体类
-
创建dao接口和mapper文件
-
创建Mybatis主配置文件
-
创建Service接口和实现类
-
创建spring的配置文件:声明Mybatis的对象交给spring创建
- 数据源Datasources
- SqlSessionFactory
- Dao对象
- 声明定义的service
-
测试:创建测试类,获取Service对象,通过service调用dao完成数据库的访问
实例
resources资源
【主要】applicationContext.xml
配置文件
声明数据库连接池对象
声明Mybatis的factory对象
声明Dao数据访问层对象
声明Service服务层对象
<beans>
<!--把数据库配置信息写在独立的文件中:属性配置文件-->
<context:property-placeholder location="classpath:jdbc.properties"/>
<!--DataSources 数据源
Druid连接池对象-->
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource" init-method="init" destroy-method="close">
<property name="url" value="${jdbc.url}" />
<property name="username" value="${jdbc.username}" />
<property name="password" value="${jdbc.password}" />
<property name="maxActive" value="${druid.maxActive}" />
</bean>
<!--Factory
声明的是Mybatis中提供的SqlSessionFactoryBean类,
这个类内部创建SqlSessionFactory的-->
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
<!--设置连接池对象-->
<property name="dataSource" ref="dataSource" />
<!--Mybatis主配置文件的位置-->
<property name="configLocation" value="classpath:mybatis.xml" />
</bean>
<!--Dao对象
使用SqlSession的getMapper(StudentDao.class)
MapperScannerConfigurer:在内部调用getMapper()生成每个Dao接口的代理对象,
创建好的dao对象放入spring容器中
(指定多个包,通过逗号 , 分隔)-->
<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
<!--指定SqlSessionFactory对象的id-->
<property name="sqlSessionFactoryBeanName" value="sqlSessionFactory"/>
<!--指定包名,包名是dao接口所在的包名
会扫描这个包中的所有接口,会把每个接口都执行一个getMapper()方法,
得到每个接口的dao对象-->
<property name="basePackage" value="cn.qkmango.dao" />
</bean>
<!--service对象-->
<bean id="studentService" class="cn.qkmango.service.impl.StudentServiceImpl">
<property name="studentDao" ref="studentDao" />
</bean>
</beans>
Mybatis.xml
<configuration>
<settings>
<!--控制台输出日志-->
<setting name="logImpl" value="STDOUT_LOGGING"/>
</settings>
<!--设置实体类别名-->
<typeAliases>
<package name="cn.qkmango.domain"/>
</typeAliases>
<!-- sql mapper(SQL映射文件)的位置 -->
<mappers>
<package name="cn.qkmango.dao"/>
</mappers>
</configuration>
jdbc.properties
数据库配置文件:将连接数据库的信息写在独立的文件中
jdbc.url=jdbc:mysql://localhost:3306/spring?useUnicode=true&characterEncoding=utf8
jdbc.username=root
jdbc.password=admin
druid.maxActive=5
Dao
cn.qkmango.dao.StudentDao.java
public interface StudentDao {
List<Student> selectStudents();
}
cn.qkmango.dao.StudentDao.xml
<mapper namespace="cn.qkmango.dao.StudentDao">
<select id="selectStudents" resultType="cn.qkmango.domain.Student">
select id,name,email,age from student order by id desc
</select>
</mapper>
Service
cn.qkmango.service.impl.StudentServiceImpl.java
public class StudentServiceImpl implements StudentService {
private StudentDao studentDao;
public void setStudentDao(StudentDao studentDao) {
this.studentDao = studentDao;
}
@Override
public List<Student> selectStudents() {
List<Student> studentList = studentDao.selectStudents();
return studentList;
}
}
总结
交给Spring的Mybatis对象
- 数据源DataSource:阿里巴巴的Druid
- SqlSessionFactoryBean:使用的是SqlSessionFactoryBean在内部创建SqlSessionFactory
- MapperScannerConfigurer:内部使用SqlSessionFactory.getMapper()获取创建所有Dao类对象,对象名是类名首字母小写(小驼峰)
- service对象
Spring事务
spring处理事务的模型,使用的步骤都是固定的。把事务使用的信息提供给spring就可以了
事务管理器对象
内部提交、回滚事务,使用的为事务管理器对象,代替你完成commit,rollback
事务管理器是一个接口和他的众多实现类。
- 接口:PlatformTransactionManager ,定义了事务重要方法 commit ,rollback
- 实现类:spring把每一种数据库访问技术对应的事务处理类都创建好了。
mybatis访问数据库:spring创建好的是 DataSourceTransactionManager
hibernate访问数据库:spring创建的是 HibernateTransactionManager - 怎么使用:你需要告诉spring 你用是那种数据库的访问技术,怎么告诉spring呢?
声明数据库访问技术对于的事务管理器实现类, 在spring的配置文件中使用<bean>
声明就可以了;例如,你要使用mybatis访问数据库,你应该在xml配置文件中<bean id="xxx" class="...DataSourceTransactionManager">
事务的隔离级别、超时、传播行为
事务的隔离级别
有4个值
DEFAULT:采用 DB 默认的事务隔离级别。MySql 的默认为 REPEATABLE_READ; Oracle默认为 READ_COMMITTED。
- READ_UNCOMMITTED:读未提交。未解决任何并发问题。
- READ_COMMITTED:读已提交。解决脏读,存在不可重复读与幻读。
- REPEATABLE_READ:可重复读。解决脏读、不可重复读,存在幻读
- SERIALIZABLE:串行化。不存在并发问题。
事务的超时时间
表示一个方法最长的执行时间,如果方法执行时超过了时间,事务就回滚;单位是秒,整数值,默认是 -1.
事务的传播行为
控制业务方法是不是有事务的, 是什么样的事务的。
7个传播行为,表示你的业务方法调用时,事务在方法之间是如果使用的。
PROPAGATION_REQUIRED
PROPAGATION_REQUIRES_NEW
PROPAGATION_SUPPORTS
以上三个需要掌握的
PROPAGATION_MANDATORY
PROPAGATION_NESTED
PROPAGATION_NEVER
PROPAGATION_NOT_SUPPORTED
public void methodA() {
methodB();
}
public void methodB() {
//...
}
传播行为 | 解释 |
---|---|
PROPAGATION_REQUIRED | 表示当前方法 必须运行在事务中,如上methodA() 如果有事务,则methodB() 会在该事务中运行,如果没有事务,则会启动一个新的事务 |
PROPAGATION_REQUIRES_NEW | 总是新建一个事务,若当前存在事务,就将当前事务挂起,直到新的事务执行完毕 |
PROPAGATION_SUPPORTS | 是否存在事务都可以,如:select查询语句 |
提交、回滚事务的时机
commit
- 当你的业务方法,执行成功,没有异常抛出,spring在方法执行后提交事务
- 当你的业务方法抛出非运行时异常, 主要是受查异常时,提交事务
受查异常:在你写代码中,必须处理的异常,例如IOException, SQLException
rollback
- 当你的业务方法抛出运行时异常(RuntimeException)或ERROR, spring执行回滚,调用事务管理器的rollback
例NullPointException , NumberFormatException
总结spring事务
- 管理事务的是 事务管理器和它的实现类
- spring的事务是一个统一模型
- 指定要使用的事务管理器实现类:
<bean>
- 指定哪些类,哪些方法需要加入事务的功能
- 指定方法需要的隔离级别,传播行为,超时
- 指定要使用的事务管理器实现类:
你需要告诉spring,你的项目中类信息,方法的名称,方法的事务传播行为。
spring事务
@Transactional
配置事务(注解)
适合中小项目使用,注解方案
spring框架自己用aop实现给业务方法增加事务的功能,使用 @Transactional注解增加事务
属性
属性 | 解释 |
---|---|
propagation | 事务传播行为,该属性类型为Propagation枚举类型,默认值为Propagation.REQUIRED |
isolation | 事务隔离级别,属性类型为Isolation枚举类型,默认值为Isolaton.DEFAULT |
readOnly | 对数据库操作是否是只读的。属性为boolean,默认值为false |
timeOut | 连接超时时间:点位秒,int类型,默认值为-1,即没有时限 |
rollbackFor | 让受检查异常回滚:即让本来不应该回滚的进行回滚操作,参数Class[] |
rollbackForClassName | 让受检查异常回滚:即让本来不应该回滚的进行回滚操作,参数String[] |
noRollbackFor | 忽略非检查异常:即让本来应该回滚的不进行回滚操作,参数Class[] |
noRollbackForClassName | 忽略非检查异常:即让本来应该回滚的不进行回滚操作,参数String[] |
使用
放在方法的上面,表示当前方法具有的事务
-
需要声明事务管理器对象(applicationContext.xml)
-
开启事务注解驱动,告诉spring框架需要使用注解方式管理事务(applicationContext.xml)
spring使用AOP机制,创建@Transactional所在的类代理对象,给方法加入事务的功能
-
在方法上面加入@Transactional注解(class)
示例
- applicationContext.xml
<beans>
<context:property-placeholder location="classpath:jdbc.properties"/>
<!--DataSources 数据源
Druid连接池对象-->
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource"
init-method="init" destroy-method="close">
<property name="url" value="${jdbc.url}" />
<property name="username" value="${jdbc.username}" />
<property name="password" value="${jdbc.password}" />
<property name="maxActive" value="${druid.maxActive}" />
</bean>
<!--Factory-->
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
<property name="dataSource" ref="dataSource" />
<property name="configLocation" value="classpath:mybatis.xml" />
</bean>
<!--Dao对象 -->
<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
<property name="sqlSessionFactoryBeanName" value="sqlSessionFactory"/>
<property name="basePackage" value="cn.qkmango.dao" />
</bean>
<!--service对象-->
<bean id="buyGoodsService" class="cn.qkmango.service.impl.BuyGoodsServiceImpl">
<property name="saleDao" ref="saleDao" />
<property name="goodsDao" ref="goodsDao" />
</bean>
<!-- ########## 使用spring的事务处理 ##############-->
<!--1. 声明事务管理器-->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager" >
<!--指定数据源-->
<property name="dataSource" ref="dataSource"/>
</bean>
<!--2. 开启事务注解驱动,告诉spring使用注解管理事务,创建代理对象,
属性值 transaction-manager 代表事务管理器
注意,同名的标签有多个,要选择 http://www.springframework.org/schema/tx 这个
-->
<tx:annotation-driven transaction-manager="transactionManager"/>
</beans>
- class
BuyGoodsServiceImpl
下面类中的buy()
方法上使用@Transactional
注解添加事务,当抛出运行时异常时会回滚事务
下面@Transactional
注解设置的属性值都是默认值,所以等同于不设置属性的注解
public class BuyGoodsServiceImpl implements BuyGoodsService {
//商品库存表、销售记录表Dao对象,省略get set方法
private SaleDao saleDao;
private GoodsDao goodsDao;
@Transactional(
propagation = Propagation.REQUIRED, //传播行为:默认必须运行在事务中
isolation = Isolation.DEFAULT, //隔离级别,默认
readOnly = false, //是否为只读的,默认
rollbackFor = { //让哪些异常类型回滚(默认RuntimeException回滚)
NullPointerException.class,
NotEnoughException.class
}
)
@Override
public void buy(Integer goodsId, Integer nums) {
//1. 记录销售信息,向销售表添加记录
//省略...
//2. 更新库存
//查询商品信息
Goods goods = goodsDao.selectGoods(goodsId);
if (goods==null) {
//商品不存在
throw new NullPointerException("商品不存在:id= "+goodsId);
} else if (goods.getAmount() < nums) {
throw new NotEnoughException("商品库存不足:id= "+goodsId);
}
//修改库存
//省略...
}
}
- test
添加事务注解的类会被动态代理
@Test
public void test() {
ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
BuyGoodsService buyGoodsService = context.getBean("buyGoodsService",BuyGoodsService.class);
//JDK动态代理:com.sun.proxy.$Proxy16
System.out.println(buyGoodsService.getClass().getName());
//会抛出异常,spring事务会进行回滚
buyGoodsService.buy(1001,200);
}
aspectJ配置文件配置事务(声明式)
适合大型项目,有很多类、方法,需要大量配置事务,使用aspectJ框架功能,在spring配置文件中声明类方法需要的事务;这种方式业务方法和事务配置完全分离
实现步骤
都是在xml配置文件中实现的
-
加入aspectJ依赖
<dependency> <groupId>org.springframework</groupId> <artifactId>spring-aspects</artifactId> <version>5.2.5.RELEASE</version> </dependency>
-
声明事务管理器
<bean id="transactionManager" class="XX.DataSourceTransactionManager"> <property name="dataSource" ref="dataSource"/> </bean>
-
配置事务属性(配置方法的事务属性【隔离级别、传播行为、超时】)
http://www.springframework.org/schema/tx
<tx:method>
:(标签相关参数和 @Transactional注解属性 相同)-
name:方法名(不包含包、类),可以使用通配符 *
-
propagation:传播行为
-
isolation:隔离级别
-
timeout:超时
-
rollback-for:一定回滚的异常类名(完整类名)
-
read-only:只读
<tx:advice id="myAdvice" transaction-manager="transactionManager"> <!--配置事务属性--> <tx:attributes> <!--配置不同方法的事务类型(需要结合下面)<aop:config>才能关联起来--> <tx:method name="buy" propagation="REQUIRED" isolation="DEFAULT" rollback-for="java.lang.NullPointerException, cn.qkmango.exception.NotEnoughException"/> <tx:method name="add*" propagation="REQUIRES_NEW"/> <tx:method name="del*" propagation="REQUIRED"/> <tx:method name="*" propagation="SUPPORTS" read-only="true"/> </tx:attributes>
spring对方法的事务进行匹配时有优先级,
先进行全匹配(如name="buy"
),
然后进行通配符匹配(如:name="add*"
),
最后进行仅通配符匹配(如name="*"
) -
-
配置AOP,将事务类型和需要加入事务的方法关联起来
<aop:pointcut>
:切入点表达式-
id:切入点表达式名称
-
expression:切入点表达式,指定哪些类需要使用事务,aspectJ会创建代理对象
<aop:advisor>
:关联事务类型和切入点表达式- advice-ref:通知,上面的
<tx:advice>
配置的id - pointcut-ref:切入点表达式id
<aop:config> <!--配置切入点表达式:指定哪些包中类需要使用事务--> <aop:pointcut id="servicePt" expression="execution(* cn.qkmango.service.impl.*.*(..))"/> <!--配置增强器:关联advice 和 pointcut 将 切入点表达式指定的方法 与 事务属性配置<tx:advice>关联起来--> <aop:advisor advice-ref="myAdvice" pointcut-ref="servicePt"/> </aop:config>
-
实例
applicationContext.xml
<beans>
<!--把数据库配置信息写在独立的文件中:属性配置文件-->
<context:property-placeholder location="classpath:jdbc.properties"/>
<!--DataSources 数据源 Druid连接池对象-->
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource" init-method="init" destroy-method="close">
<property name="url" value="${jdbc.url}" />
<property name="username" value="${jdbc.username}" />
<property name="password" value="${jdbc.password}" />
<property name="maxActive" value="${druid.maxActive}" />
</bean>
<!--Factory-->
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
<property name="dataSource" ref="dataSource" />
<property name="configLocation" value="classpath:mybatis.xml" />
</bean>
<!--Dao对象 -->
<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
<property name="sqlSessionFactoryBeanName" value="sqlSessionFactory"/>
<property name="basePackage" value="cn.qkmango.dao" />
</bean>
<!--service对象-->
<bean id="buyGoodsService" class="cn.qkmango.service.impl.BuyGoodsServiceImpl">
<property name="saleDao" ref="saleDao" />
<property name="goodsDao" ref="goodsDao" />
</bean>
<!-- ##### 声明式事务处理:和源代码完全分离的 #####-->
<!--1. 声明事务管理器对象-->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"/>
</bean>
<!--2. 配置事务的属性(隔离级别、传播行为、超时时间) -->
<tx:advice id="myAdvice" transaction-manager="transactionManager">
<!--配置事务属性-->
<tx:attributes>
<tx:method name="buy" propagation="REQUIRED" isolation="DEFAULT"
rollback-for="java.lang.NullPointerException,
cn.qkmango.exception.NotEnoughException"/>
<!--添加方法、删除方法 事务配置-->
<tx:method name="add*" propagation="REQUIRES_NEW"/>
<tx:method name="del*" propagation="REQUIRED"/>
<!--查询方法 事务配置-->
<tx:method name="*" propagation="SUPPORTS" read-only="true"/>
</tx:attributes>
</tx:advice>
<!--配置AOP:将方法和事务属性关联起来-->
<aop:config>
<!--配置切入点表达式:指定哪些包中类需要使用事务 -->
<aop:pointcut id="servicePt"
expression="execution(* cn.qkmango.service.impl.*.*(..))"/>
<!--配置增强器:关联advice 和 pointcut
将 切入点表达式指定的方法 与 事务属性配置<tx:advice>关联起来 -->
<aop:advisor advice-ref="myAdvice" pointcut-ref="servicePt"/>
</aop:config>
</beans>
web项目中使用容器对象
spring在web项目中的使用
步骤
-
写项业务相关类、切面类
-
加入依赖
servlet Spring核心ioc spring事务 aspect Mybatis Mybatis和spring集成的依赖 MySQL驱动 Druid连接池 spring-web:为了使用监听器
-
配置web.xml
<!--配置监听器,在容器启动时创建好spring容器内的对象--> <context-param> <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>
-
配置Mybatis.xml
-
配置applicationContext.xml
- 配置事务相关
- 切面相关
spring容器获取
如何在web项目总获取spring容器对象
使用监听器自动在项目启动时初始化spring容器,在每次使用spring容器对象时直接获取就行了,需要有以下步骤
配置监听器
在项目启动时,就进行spring容器的初始化,创建bean,则需要使用监听器
-
加入依赖(pom.xml)
<dependency> <groupId>org.springframework</groupId> <artifactId>spring-web</artifactId> <version>5.1.6.RELEASE</version> </dependency>
-
配置监听器(web.xml)
<context-param> <param-name>contextConfigLocation</param-name> <!--spring配置文件路径--> <param-value>classpath:applicationContext.xml</param-value> </context-param> <listener> <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class> </listener>
监听器是如何工作的,做了什么事
在项目启动时,将spring容器对象创建并放到servlet全局作用域中
(这个监听器时spring提供的)
//创建容器对象
ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
//把容器对象放入到ServletContext
ServletContext.setAttribute(key,ctx)
获取spring容器对象
根据监听器是如何工作的,所以获取spring容器对象,可以通过下面方式获取
ApplicationContext context = (ApplicationContext)getServletContext().getAttribute("key");
其实也可以并推荐使用spring提供的方式获取
WebApplicationContext context = WebApplicationContextUtils.getRequiredWebApplicationContext(getServletContext());
End 2021-04-23 23:36 by 芒果小洛(qkmango)