一、IOC控制反转
1.1 概述
控制反转(IoC,Inversion of Control),是一个概念,是一种思想。指将传统上由程序代码直接操控的对象调用权交给容器,通过容器来实现对象的装配和管理。控制反转就是对对象控制权的转移,从程序代码本身反转到了外部容器。通过容器实现对象的创建,属性赋值,依赖的管理
- IoC 是一个概念,是一种思想,其实现方式多种多样。当前比较流行的实现方式是依赖
注入。应用广泛 - 依赖:classA 类中含有 classB 的实例,在 classA 中调用 classB 的方法完成功能,即 classA
对 classB 有依赖。
1.2 IOC的实现
依赖注入:DI(Dependency Injection),程序代码不做定位查询,这些工作由容器自行完成。
- 依赖注入 DI 是指程序运行过程中,若需要调用另一个对象协助时,无须在代码中创建
被调用者,而是依赖于外部容器,由外部容器创建后传递给程序。 - Spring 的依赖注入对调用者与被调用者几乎没有任何要求,完全支持对象之间依赖关系
的管理。 - Spring 容器是一个大工厂,负责创建、管理所有的 Java 对象,这些 Java 对象被称为 Bean。
- Spring 容器管理着容器中 Bean 之间的依赖关系,Spring 使用“依赖注入”的方式来管理 Bean 之间的依赖关系。使用 IoC 实现对象之间的解耦和。
1.3 关于bean标签
- bean标签的配置
<!--告诉spring创建对象
声明bean , 就是告诉spring要创建某个类的对象
id:对象的自定义名称,唯一值。 spring通过这个名称找到对象
class:类的全限定名称(不能是接口,因为spring是反射机制创建对象,必须使用类)
spring就完成 SomeService someService = new SomeServiceImpl();
spring是把创建好的对象放入到map中, spring框架有一个map存放对象的。
springMap.put(id的值, 对象);
例如 springMap.put("someService", new SomeServiceImpl());
一个bean标签声明一个对象。
-->
<bean id="someService" class="com.jjh.service.impl.SomeServiceImpl" />
<bean id="someService1" class="com.jjh.service.impl.SomeServiceImpl" scope="prototype"/>
- 测试类(1)使用
@Test
public void test01(){
SomeService service = new SomeServiceImpl();
service.doSome();
}
- 测试类(2)使用
@Test
public void test03(){
String config="beans.xml";
ApplicationContext ac = new ClassPathXmlApplicationContext(config);
//使用spring提供的方法, 获取容器中定义的对象的数量
int nums = ac.getBeanDefinitionCount();
System.out.println("容器中定义的对象数量:"+nums);
//容器中每个定义的对象的名称
String names [] = ac.getBeanDefinitionNames();
for(String name:names){
System.out.println(name);
}
}
1.4 基于XML的DI
1.4.1 set注入(重点)
set 注入也叫设值注入是指,通过 setter 方法传入被调用者的实例。这种注入方式简单、直观,因而在 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">
<!--声明student对象
注入:就是赋值的意思
简单类型: spring中规定java的基本数据类型和String都是简单类型。
di:给属性赋值
1. set注入(设值注入) :spring调用类的set方法, 你可以在set方法中完成属性赋值
1)简单类型的set注入
<bean id="xx" class="yyy">
<property name="属性名字" value="此属性的值"/>
一个property只能给一个属性赋值
<property....>
</bean>
2) 引用类型的set注入 : spring调用类的set方法
<bean id="xxx" class="yyy">
<property name="属性名称" ref="bean的id(对象的名称)" />
</bean>
-->
<bean id="myStudent" class="com.jjh.ba02.Student" >
<property name="name" value="李四" />
<property name="age" value="26" />
<!--引用类型-->
<property name="school" ref="mySchool" /><!--setSchool(mySchool)-->
</bean>
<!--声明School对象-->
<bean id="mySchool" class="com.jjh.ba02.School">
<property name="name" value="北京大学"/>
<property name="address" value="北京的海淀区" />
</bean>
</beans>
1.4.2 构造注入
构造注入是指,在构造调用者实例的同时,完成被调用者的实例化。即使用构造器设置依赖关系。
<!--
2.构造注入:spring调用类有参数构造方法,在创建对象的同时,在构造方法中给属性赋值。
构造注入使用 <constructor-arg> 标签
<constructor-arg> 标签:一个<constructor-arg>表示构造方法一个参数。
<constructor-arg> 标签属性:
name:表示构造方法的形参名
index:表示构造方法的参数的位置,参数从左往右位置是 0 , 1 ,2的顺序
value:构造方法的形参类型是简单类型的,使用value
ref:构造方法的形参类型是引用类型的,使用ref
-->
<!--使用name属性实现构造注入-->
<bean id="myStudent" class="com.jjh.ba03.Student" >
<constructor-arg name="myage" value="20" />
<constructor-arg name="mySchool" ref="myXueXiao" />
<constructor-arg name="myname" value="周良"/>
</bean>
<!--使用index属性-->
<bean id="myStudent2" class="com.jjh.ba03.Student">
<constructor-arg index="1" value="22" />
<constructor-arg index="0" value="李四" />
<constructor-arg index="2" ref="myXueXiao" />
</bean>
<!--省略index-->
<bean id="myStudent3" class="com.jjh.ba03.Student">
<constructor-arg value="张强强" />
<constructor-arg value="22" />
<constructor-arg ref="myXueXiao" />
</bean>
1.4.3 引用类型自动注入
- byName(按名称注入)
java类中引用类型的属性名和spring容器中(配置文件)
<bean>
的id名称一样,且数据类型是一致的,这样的容器中的bean,spring能够赋值给引用类型。
<!--
语法:
<bean id="xx" class="yyy" autowire="byName">
简单类型属性赋值
</bean>
-->
<bean id="myStudent" class="com.jjh.ba04.Student" autowire="byName">
<property name="name" value="李四" />
<property name="age" value="26" />
<!--引用类型-->
<!--<property name="school" ref="mySchool" />-->
</bean>
<!--声明School对象-->
<bean id="school" class="com.jjh.ba04.School">
<property name="name" value="清华大学"/>
<property name="address" value="北京的海淀区" />
</bean>
- byType(按类型注入)
java类中引用类型的数据类型和spring容器中(配置文件)的class属性是同源关系的,这样的bean能够赋值给引用类型
<!--
语法:
<bean id="xx" class="yyy" autowire="byType">
简单类型属性赋值
</bean>
注意:在byType中, 在xml配置文件中声明bean只能有一个符合条件的,
多余一个是错误的
-->
<!--byType-->
<bean id="myStudent" class="com.jjh.ba05.Student" autowire="byType">
<property name="name" value="张飒" />
<property name="age" value="26" />
<!--引用类型-->
<!--<property name="school" ref="mySchool" />-->
</bean>
<!--声明School对象-->
<bean id="mySchool" class="com.jjh.ba05.School">
<property name="name" value="人民大学"/>
<property name="address" value="北京的海淀区" />
</bean>
1.4.4 具有关联关系的配置
- student类的配置文件
<!--student 模块bean声明-->
<bean id="myStudent" class="com.jjh.domain.entity.Student" autowire="byType">
<property name="name" value="张三"/>
<property name="age" value="23"/>
<!--引用类型数据-->
</bean>
- School类的配置文件
<bean id="mySchool" class="com.jjh.domain.entity.School">
<property name="address" value="松潘"/>
<property name="name" value="工大"/>
</bean>
- total配置文件
<!--
包含关系的配置文件:
spring-total表示主配置文件 : 包含其他的配置文件的,主配置文件一般是不定义对象的。
语法:<import resource="其他配置文件的路径" />
关键字:"classpath:" 表示类路径(class文件所在的目录),
在spring的配置文件中要指定其他文件的位置, 需要使用classpath,告诉spring到哪去加载读取文件。
-->
<!--加载的是文件列表-->
<!--
<import resource="classpath:ba01/applicationContext01.xml" />
<import resource="classpath:ba01/applicationContext02.xml" />
-->
<!--
在包含关系的配置文件中,可以通配符(*:表示任意字符)
注意: 主的配置文件名称不能包含在通配符的范围内(不能叫做spring-total.xml)
-->
<!--导入配置文件-->
<import resource="classpath:ba01/applicationContext*"/>
1.5 基于注解的DI
- 注解配置的约束文件
<?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 创建容器时要扫描的包 @ComponentScan -->
<context:component-scan base-package="com.jjh">
</context:component-scan>
</beans>
- 实体类
@Data
@Component("myStudent")
public class Student {
private String name;
private int age;
//引用类型
private School school;
}
- 测试类中调用
@Test
public void demo01(){
String config = "applicationContext.xml";
ApplicationContext app = new ClassPathXmlApplicationContext(config);
Student myStudent = (Student)app.getBean("myStudent");
System.out.println(myStudent);
}
1.5.1 使用注解的步骤
1.加入maven的依赖
spring-context
,在你加入spring-context
的同时, 间接加入spring-aop
的依赖。使用注解必须使用spring-aop
依赖
2.在类中加入spring的注解(多个不同功能的注解)
3.在spring的配置文件中,加入一个
component-scan
组件扫描器的标签,说明注解在你的项目中的位置
<!--声明组件扫描器(component-scan),组件就是java对象
base-package:指定注解在你的项目中的包名。
component-scan工作方式: spring会扫描遍历base-package指定的包,
把包中和子包中的所有类,找到类中的注解,按照注解的功能创建对象,或给属性赋值。
加入了component-scan标签,配置文件的变化:
1.加入一个新的约束文件spring-context.xsd
2.给这个新的约束文件起个命名空间的名称
-->
<context:component-scan base-package="com.jjh.ba01" />
1.5.2 多注解项目分层
1.
@Component
: 创建对象的, 等同于<bean>
的功能
- 属性:value 就是对象的名称,也就是bean的id值,value的值是唯一的,创建的对象在整个spring容器中就一个
2.
@Repository
(用在持久层类的上面) : 放在dao的实现类上面,表示创建dao对象,dao对象是能访问数据库的。
3.@Service
(用在业务层类的上面):放在service的实现类上面,创建service对象,service对象是做业务处理,可以有事务等功能的。
4.@Controller
(用在控制器的上面):放在控制器(处理器)类的上面,创建控制器对象的,控制器对象,能够接受用户提交的参数,显示请求的处理结果
- 以上三个注解的使用语法和
@Component
一样的。 都能创建对象,但是这三个注解还有额外的功能。
1.5.3 简单类型的赋值
@Value
: 简单类型的属性赋值
- 属性: value 是String类型的,表示简单类型的属性值
- 使用位置:1.在属性定义的上面,无需set方法,推荐使用。
2.在set方法的上面
- 配置文件
<context:component-scan base-package="com.jjh"/>
<!--配置属性的properties文件-->
<context:property-placeholder location="classpath:student.properties"/>
- properties文件
name=Dick
age=20
@Component("myStudent")
public class Student {
//@Value("李四" )
@Value("${myname}") //使用属性配置文件中的数据
private String name;
@Value("${myage}") //使用属性配置文件中的数据
private Integer age;
}
1.5.4 引用类型的赋值
- 默认方式
1.
@Autowired
: spring框架提供的注解,实现引用类型的赋值
2.spring中通过注解给引用类型赋值,使用的是自动注入原理 ,支持byName, byType
3.@Autowired
:默认使用的是byType自动注入
4.使用位置:
- 在属性上面使用
- 在set方法上面使用
@Component("myStudent")
public class Student {
@Value("李四" )
private String name;
@Value("20")
private Integer age;
@Autowired
private School school;
}
- 通过名称赋值
如果要使用byName方式:
- 在属性上面加入
@Autowired
- 属性上面加入
@Qualifier(value="bean的id")
:表示使用指定名称的bean完成赋值在
@Component("myStudent")
public class Student {
@Value("李四" )
private String name;
@Value("20")
private Integer age;
@Autowired
@Qualifier("mySchool")
private School school;
}
1.5.5 Autowired的required属性
- required :是一个boolean类型的,默认true
- required=true:表示引用类型赋值失败,程序报错,并终止执行
- required=false:引用类型如果赋值失败, 程序正常执行,引用类型是null
1.5.6 JDK注解@Resource自动注入
1.
@Resource
: 来自jdk中的注解,spring框架提供了对这个注解的功能支持,可以使用它给引用类型赋值
2.使用的也是自动注入原理,支持byName,byType默认是byName
3.使用位置:
- 在属性定义的上面,无需set方法,推荐使用
- 在set方法的上面
4.默认是byName:先使用byName自动注入,如果byName赋值失败,再使用byType
5.@Resource
只使用byName方式,需要增加一个属性name
,name
的值是bean的id(名称)
- 指定name
@Component("myStudent")
public class Student {
@Value("李四" )
private String name;
private Integer age;
//只使用byName
@Resource(name = "mySchool")
private School school;
- 默认配置
@Component("myStudent")
public class Student {
@Value("李四" )
private String name;
private Integer age;
//只使用byName
@Resource
private School school;
二、AOP面向切面编程
2.1 概述
AOP(Aspect Orient Programming)
。面向切面编程是从动态角度考虑程序运行过程- AOP 底层,就是采用动态代理模式实现的。采用了两种代理:JDK 的动态代理,与 CGLIB的动态代理,AOP就是动态代理的规范化, 把动态代理的实现步骤,方式都定义好了, 让开发人员用一种统一的方式,使用动态代理
Aspect
: 切面,给你的目标类增加的功能,就是切面。 像上面用的日志,事务都是切面。切面的特点:一般都是非业务方法,独立使用的Orient
:面向, 对着Programming
:编程
2.2 相关术语
1.Aspect:切面,表示增强的功能, 就是一堆代码,完成某个一个功能。非业务功能,常见的切面功能有日志, 事务, 统计信息, 参数检查, 权限验证,切面用于组织多个Advice,Advice放在切面中定义,实际就是对主业务逻辑的一种增强
*
2.JoinPoint:连接点 ,连接业务方法和切面的位置。就某类中的业务方法,程序执行过程中明确的点,如方法的调用,或者异常的抛出。在Spring AOP中,连接点总是方法的调用
*
3.Pointcut:切入点 ,指多个连接点方法的集合。多个方法。可以插入增强处理的连接点。简而言之,当某个连接点满足指定要求时,该连接点将被添加增强处理,该连接点也就变成了切入点
*
4.Advice:AOP框架在特定的切入点执行的增强处理。处理有"around"、"before"和"after"等类型,能表示切面功能执行的时间,切入点定义切入的位置,通知定义切入的时间
*
5.Target:目标对象,目 标 对 象 指 将 要 被 增 强 的 对 象 。 即 包 含 主 业 务 逻 辑 的 类 的 对 象
2.3 AspectJ
2.3.1 概述
AspectJ是一个基于Java语言的AOP框架,提供了强大的AOP功能,其主要包括两个部分:
- 一个部分定义了如何表达、定义AOP编程中的语法规范;
- 另一个部分是工具部分,包括编译、调试工具等
aspectJ框架实现aop的两种方式:
- 使用xml的配置文件 : 配置全局事务
- 使用注解,我们在项目中要做aop功能,一般都使用注解,aspectj有5个注解
- @Before
- @AfterReturning
- @Around
- @AfterThrowing
- @After
2.3.2 AspectJ的切入点表达式
表达式原型:
execution(modifiers-pattern? ret-type-pattern declaring-type-pattern?name-pattern(param-pattern) throws-pattern?)
相关解释:
modifiers-pattern?
访问权限类型- ret-type-pattern 返回值类型
declaring-type-pattern?
包名类名- name-pattern(param-pattern) 方法名(参数类型和参数个数)
throws-pattern
抛出异常类型?
表示可选的部分
- 以上表达式一共4个部分
execution(访问权限 方法返回值 方法声明(参数) 异常类型)
符号 | 意义 |
---|---|
* | 0至多个任意字符 |
. . | 用在方法参数中,表示任意多个参数;用在包名后,表示当前包与子包路径 |
+ | 用在类名后,表示当前类及其子类;用在接口后,表示当前接口及其实现类 |
相关实例:
execution(public * *(..))
:任意公共方法execution(* set*(..))
:任何一个以“set”开始的方法execution(* com.xyz.service.*.*(..))
:定义在 service 包里的任意类的任意方法execution(* com.xyz.service..*.*(..))
:定义在 service 包或者子包里的任意类的任意方法。“..”
出现在类名中时,后面必须跟“*”
,表示包、子包下的所有类execution(* *..service.*.*(..))
:指定所有包下的 serivce 子包下所有类(接口)中所有方法为切入点execution(* com.xyz.service.IAccountService+.*(..))
:IAccountService 若为接口,则为接口中的任意方法及其所有实现类中的任意方法;若为类,则为该类及其子类中的任意方法execution(* joke(String,int)))
:所有的 joke(String,int)方法,且 joke()方法的第一个参数是String
,第二个参数是int
;如果方法中的参数类型是 java.lang 包下的类,可以直接使用类名,否则必须使用全限定类名,如 joke( java.util.List, int)
2.3.3 前置通知:@Before
1.方法的定义要求:
- 公共方法 public
- 方法没有返回值
- 方法名称自定义
- 方法可以有参数,也可以没有参数
2.
@Before
: 前置通知注解
- 属性:value ,是切入点表达式,表示切面的功能执行的位置
- 位置:在方法的上面
1.配置依赖
<!--spring依赖-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.2.5.RELEASE</version>
</dependency>
<!--aspectj依赖-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aspects</artifactId>
<version>5.2.5.RELEASE</version>
</dependency>
2.创建业务接口与实现类对象
- Service.interface
public interface Service {
public void doSome();
}
- ServiceImpl.java
@org.springframework.stereotype.Service("myService")
public class ServiceImpl implements Service {
@Override
public void doSome() {
System.out.println("这是我的业务方法!!!");
}
}
3.创建切面类:MyAspect.java
@Aspect
@Component("myAspect")
public class MyAspect {
/**
*指定通知方法中的参数:JoinPoint
*
*/
@Before(value = "execution(void *..doSome(..))")
public void before(){
System.out.println("这是前置通知");
}
}
4.配置applicationContext.xml文件
<!--扫描文件-->
<context:component-scan base-package="com.jjh.*"/>
<!--声明自动代理生成器-->
<aop:aspectj-autoproxy/>
5.测试类调用
@org.junit.Test
public void demo01(){
String config = "applicationContext.xml";
ApplicationContext app = new ClassPathXmlApplicationContext(config);
Service proxy = (Service) app.getBean("myService");
//输出当前类的信息:com.sun.proxy.$Proxy17
//证明其使用的是JDK动态代理
System.out.println(proxy.getClass().getName());
proxy.doSome();
}
2.3.4 JoinPoint
- 指定通知方法中的参数 : JoinPoint
- JoinPoint:业务方法,要加入切面功能的业务方法
- 作用:可以在通知方法中获取方法执行时的信息, 例如方法名称,方法的实参
- 如果需要切面功能中方法的信息,就加入JoinPoint
- JoinPoint参数的值是由框架赋予, 必须是第一个位置的参数
- 不止前置通知的方法,可以包含一个 JoinPoint 类型参数,所有的通知方法均可包含该参数
- MyAspect.java
@Aspect
@Component("myAspect")
public class MyAspect {
@Before(value = "execution(void *..doSome(..))")
public void before(JoinPoint joinPoint){
//获取方法的定义
System.out.println("方法的签名(定义):" + joinPoint.getSignature());
System.out.println("方法的名称:" + joinPoint.getSignature().getName());
//获取方法的实参
Object[] args = joinPoint.getArgs();
for (Object arg : args) {
System.out.println("参数:" + arg);
}
System.out.println("这是前置通知!!!");
}
}
- 测试类
@org.junit.Test
public void demo01(){
String config = "applicationContext.xml";
ApplicationContext app = new ClassPathXmlApplicationContext(config);
Service proxy = (Service) app.getBean("myService");
proxy.doSome("张三",20);
}
- 打印结果
2.3.5 后置通知:@AfterReturning
- 在目标方法执行之后执行
- 由于是目标方法之后执行,所以可以获取到目标方法的返回值
- 该注解的returning属性就是用于指定接收方法返回值的变量名的
- 所以,被注解为后置通知的方法,除了可以包含 JoinPoint 参数外,还可以包含用于接收返回值的变量
- 该变量最好为 Object 类型,因为目标方法的返回值可能是任何类型
- 业务方法
@Component("myService2")
public class SomeServiceImpl implements SomeService {
@Override
public void doSome() {
System.out.println("这是业务方法!!!");
}
@Override
public String doOther(String str, int i) {
return "业务方法doOther的返回值!!!";
}
}
- 后置通知
1.该注解的 returning 属性就是用于指定接收方法返回值的变量名的
2.除了可以包含 JoinPoint 参数外,还可以包含用于接收返回值的变量
3.该变量最好为Object 类型,因为目标方法的返回值可能是任何类型
4.方法的定义要求:
- 公共方法 public
- 方法没有返回值
- 方法名称自定义
- 方法有参数的,推荐是Object ,参数名自定义
5.
@AfterReturning:后置通知
value
:切入点表达式returning
:自定义的变量,表示目标方法的返回值的,自定义变量名必须和通知方法的形参名一样- 可以根据业务方法的返回值做出相应的操作
@AfterReturning(value = "execution(String *..doOther(..))",returning = "res")
public void myAfterReturning(JoinPoint joinPoint,Object res){
//获取方法的签属(定义)
System.out.println(joinPoint.getSignature());
//获取方法的参数
Object[] args = joinPoint.getArgs();
for (Object arg : args) {
System.out.println("目标方法参数:" + arg);
}
//目标方法的返回值
System.out.println(res);
//后置通知
System.out.println("后置通知!!!");
}
- 测试类
@Test
public void demo02(){
String config = "applicationContext.xml";
ApplicationContext app = new ClassPathXmlApplicationContext(config);
SomeService proxy = (SomeService)app.getBean("myService2");
proxy.doOther("Dick",20);
}
2.3.6 环绕通知:@Around
- 在目标方法执行之前之后执行。被注解为环绕增强的方法要有返回值
- 被注解为环绕增强的方法要有返回值,Object 类型。并且方法可以包含一个ProceedingJoinPoint类型的参数
- 接口ProceedingJoinPoint其中有一个proceed() 方法,用于执行目标方法
- 若目标方法有返回值,则该方法的返回值就是目标方法的返回值。最后,环绕增强方法将其返回值返回。该增强方法实际是拦截了目标方法的执行
- 切面类
1.环绕通知方法的定义格式:
- public
- 必须有一个返回值,推荐使用Object
- 方法名称自定义
- 方法有参数,固定的参数 ProceedingJoinPoint
2.特点
- 在目标方法的前和后都能增强功能
- 控制目标方法是否被调用执行
- 修改原来的目标方法的执行结果,影响最后的调用结果
- 它是功能最强的通知
3.环绕通知等同于jdk动态代理的InvocationHandler接口
4.参数:ProceedingJoinPoint
就等同于Method
,用于执行目标方法
5.返回值: 就是目标方法的执行结果,可以被修改
6.环绕通知:经常做事务, 在目标方法之前开启事务,执行目标方法, 在目标方法之后提交事务
@Around(value = "execution(String *..do(..))")
public Object myAround(ProceedingJoinPoint p) throws Throwable {
Object result = null;
//前置功能增强
System.out.println("前置功能增强!!");
//等同于method.invoke(); Object result = doFirst();
result = p.proceed();
//后置功能增强
System.out.println("后置功能增强");
return result;
}
- 测试类
@Test
public void demo01(){
ApplicationContext app = new ClassPathXmlApplicationContext("applicationContext.xml");
SomeService proxy = (SomeService)app.getBean("Service3");
String result = proxy.doFirst("Dick", 99);
//输出返回结果
System.out.println(result);
}
2.3.7 异常通知:@AfterThrowing
- 在目标方法抛出异常后执行
- 该注解的 throwing 属性用于指定所发生的异常类对象
- 被注解为异常通知的方法可以包含一个参数 Throwable,参数名称为 throwing 指定的名称,表示发生的异常对象
- 业务方法
@Override
public void doSecond() {
System.out.println("执行业务方法doSecond()" + (10/0));
}
- 切面类
1.异常通知方法的定义格式:
- 访问权限public
- 没有返回值
- 方法名称自定义
- 方法有个一个Exception, 也可以使用JoinPoint
2.
@AfterThrowing
:异常通知
- 属性:
- value 切入点表达式
- throwinng 自定义的变量,表示目标方法抛出的异常对象,变量名必须和方法的参数名一样
- 特点:
- 在目标方法抛出异常时执行的
- 可以做异常的监控程序, 监控目标方法执行时是不是有异常,如果有异常,可以发送邮件,短信进行通知
@AfterThrowing(value = "execution(* *..SomeServiceImpl.doSecond(..))",
throwing = "ex")
public void myAfterThrowing(Exception ex) {
System.out.println("异常通知:方法发生异常时,执行:"+ex.getMessage());
//发送邮件,短信,通知开发人员
}
2.3.8 最终通知:@After
无论目标方法是否抛出异常,该增强均会被执行
- 业务方法
@Override
public void doThird() {
System.out.println("执行业务方法doThird()");
}
- 切面类
1.最终通知的定义格式:
- 访问权限public
- 没有返回值
- 方法名称自定义
- 方法没有参数,但是可以使用JoinPoint
2.
@After
:最终通知特点
- 总是会执行
- 在目标方法之后执行
//等同以下执行方式
try{
SomeServiceImpl.doThird(..)
}catch(Exception e){
}finally{
myAfter()
}
@After(value = "execution(* *..SomeServiceImpl.doThird(..))")
public void myAfter(){
System.out.println("执行最终通知,总是会被执行的代码");
//一般做资源清除工作的。
}
2.3.9 @Pointcut 定义切入点
- 当较多的通知增强方法使用相同的 execution 切入点表达式时,编写、维护均较为麻烦;AspectJ 提供了@Pointcut 注解,用于定义 execution 切入点表达式
- 将@Pointcut 注解在一个方法之上,以后所有的 execution 的 value 属性值均可使用该方法名作为切入点
- 代表的就是
@Pointcut
定义的切入点。这个使用@Pointcut
注解
的方法一般使用 private 的标识方法,即没有实际作用的方法
- 切面类
1.
@Pointcut
: 定义和管理切入点, 如果你的项目中有多个切入点表达式是重复的,可以复用的。
2.特点:
- 当使用@Pointcut定义在一个方法的上面 ,此时这个方法的名称就是切入点表达式的别名
- 其它的通知中,value属性就可以使用这个方法名称,代替切入点表达式了
@After(value = "mypt()")
public void myAfter(){
System.out.println("执行最终通知,总是会被执行的代码");
//一般做资源清除工作的。
}
@Before(value = "mypt()")
public void myBefore(){
System.out.println("前置通知,在目标方法之前先执行的");
}
@Pointcut(value = "execution(* *..SomeServiceImpl.doThird(..))" )
private void mypt(){
//无需代码,
}
2.4 代理方式更换
- 如果目标类有接口,默认使用jdk动态代理,如果目标类没有接口,则使用CGlib动态代理
- 如果想让具有接口的目标类使用CGlib的代理方式,需要以下配置文件
<aop:aspectj-autoproxy proxy-target-class="true"/>
三、spring整合MyBatis
- 将 MyBatis 与 Spring 进行整合,主要解决的问题就是将SqlSessionFactory 对象交由 Spring来管理
- 只需要将 SqlSessionFactory 的对象生成器 SqlSessionFactoryBean 注册在 Spring 容器中,再将其注入给 Dao 的实现类即可完成整合
整合思路
- 需要有需要有Dao接口的代理对象,例如
studentDao
需要一个他的代理对象,使用SqlSession.getMapper(StudentDao.class)
,得到dao
代理对象 - 需要有
SqlSessionFactory
,创建一个SqlSessionFactory
对象,使用SqlSessionFactory.open()
得到SqlSession
对象 - 数据源
DataSource
对象,使用连接池对象替换mybatis自己的PooledDataSource
3.1 maven依赖
- maven依赖
<dependencies>
<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-tx</artifactId>
<version>5.3.8</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>5.3.8</version>
</dependency>
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.5.7</version>
</dependency>
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis-spring</artifactId>
<version>2.0.6</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.25</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.2.1</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.20</version>
</dependency>
</dependencies>
<build>
<resources>
<resource>
<directory>src/main/java</directory><!--所在的目录-->
<includes><!--包括目录下的.properties,.xml 文件都会扫描到-->
<include>**/*.properties</include>
<include>**/*.xml</include>
</includes>
<filtering>false</filtering>
</resource>
</resources>
</build>
3.2 实体类
- 定义实体类
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Student {
private Integer id;
private String name;
private String email;
private Integer age;
}
3.3 Dao接口与mapper文件
- Dao接口
public interface StudentDao {
int insertStudent(Student student);
List<Student> selectStudentList();
}
- mapper文件
<?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.jjh.dao.StudentDao">
<insert id="insertStudent">
insert into student values (#{id},#{name},#{email},#{age})
</insert>
<select id="selectStudentList" resultType="com.jjh.domain.entity.Student">
select * from student order by id desc
</select>
</mapper>
3.4 service接口与实现类
- service接口
public interface StudentService {
int addStudent(Student student);
List<Student> queryAllStudents();
}
- service实现类
@Service("myStudentService")
public class StudentServiceImpl implements StudentService {
@Autowired
private StudentDao studentDao;
@Override
public int addStudent(Student student) {
int result = studentDao.insertStudent(student);
return result;
}
@Override
public List<Student> queryAllStudents() {
List<Student> students = studentDao.selectStudentList();
return students;
}
}
3.5 MyBatis主配置文件
- 主配置文件中不再需要数据源的配置了,因为数据源要交给 Spring 容器来管理了
- 这里对 mapper 映射文件的注册,使用
<package/>
标签,即只需给出 mapper 映射文件所在的包即可,因为 mapper 的名称与 Dao 接口名相同,可以使用这种简单注册方式。这种方式的好处是,若有多个映射文件,这里的配置也是不用改变的。当然,也可使用原来的<resource/>
标签方式
<?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:实体类的包名
-->
<package name="com.jjh.domain.entity"/>
</typeAliases>
<!-- sql mapper(sql映射文件)的位置-->
<mappers>
<!--
package:指定Dao接口包的位置,表示将包下面的sql映射文件找到
name:Dao接口的包名
使用package指定映射文件的要求:
1.sql映射的文件名与Dao接口名一致
2.sql映射文件和Dao接口在同一目录
-->
<package name="com.jjh.dao"/>
</mappers>
</configuration>
3.6 spring的配置文件
- jdbc.properties文件
jdbc.driver=com.mysql.cj.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/mybatis01?serverTimezone=Asia/Shanghai
jdbc.username=root
jdbc.password=123456
- 该属性文件若要被 Spring 配置文件读取,其必须在配置文件中进行注册。使用
<context>
标签<context:property-placeholder/>
标签中有一个属性 location,用于指定属性文件的位置
- 注册 SqlSessionFactoryBean
<!--声明的是mybatis中提供的SqlSessionFactoryBean类,这个类内部创建SqlSessionFactory的
SqlSessionFactory sqlSessionFactory = new ..
-->
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
<!--set注入,把数据库连接池付给了dataSource属性-->
<property name="dataSource" ref="myDataSource" />
<!--mybatis主配置文件的位置
configLocation属性是Resource类型,读取配置文件
它的赋值,使用value,指定文件的路径,使用classpath:表示文件的位置
-->
<property name="configLocation" value="classpath:mybatis.xml" />
</bean>
- 定义 Mapper 扫描配置器 MapperScannerConfigurer
Mapper 扫描配置器 MapperScannerConfigurer会自动生成指定的基本包中 mapper 的代理对象 。该 Bean无需设置 id 属性。basePackage 使用分号或逗号设置多个包
<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.jjh.dao"/>
</bean>
3.7 向service注入接口名
向 Service 注入 Mapper 代理对象时需要注意,由于通过 Mapper 扫描配置器MapperScannerConfigurer生成的 Mapper 代理对象没有名称,所以在向 Service 注入 Mapper代理时,无法通过名称注入。但可通过接口的简单类名注入,因为生成的是这个 Dao 接口的对象。
- 全部配置文件
<context:component-scan base-package="com.jjh.service"/>
<!--
把数据库的配置信息,写在一个独立的文件,编译修改数据库的配置内容
spring知道jdbc.properties文件的位置
-->
<context:property-placeholder location="classpath:jdbc.properties"/>
<!--声明数据源DataSource,作用是连接数据库-->
<bean id="myDataSource" class="com.alibaba.druid.pool.DruidDataSource"
init-method="init" destroy-method="close">
<!--set注入-->
<property name="url" value="${jdbc.url}"/>
<property name="username" value="${jdbc.username}"/>
<property name="password" value="${jdbc.password}"/>
<property name="maxActive" value="20"/>
</bean>
<!--声明的是mybatis中提供的SqlSessionFactoryBean类,这个类内部创建SqlSessionFactory的
SqlSessionFactory sqlSessionFactory = new ..
-->
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
<!--set注入,把数据库连接池付给了dataSource属性-->
<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.jjh.dao"/>
</bean>
</beans>
四、Spring事务
4.1 概述
事务就是一些sql序列的集合, 是多条sql, 作为一个整体执行
- 事务原本是数据库中的概念,在 Dao 层。但一般情况下,需要将事务提升到业务层,即 Service 层。这样做是为了能够使用事务的特性来管理具体的业务
- 在 Spring 中通常可以通过以下两种方式来实现对事务的管理:
- (1)使用 Spring 的事务注解管理事务
- (2)使用 AspectJ 的 AOP 配置管理事务
4.2 spring的事务管理
- 使用spring的事务管理器,管理不同数据库访问技术的事务处理。 开发人员只需要掌握spring的事务处理一个方案, 就可以实现使用不同数据库访问技术的事务管理
- 管理事务面向的是spring, 有spring管理事务,做事务提交,事务回顾
4.3 Spring事务管理API
Spring 的事务管理,主要用到两个事务相关的接口
4.3.1 事务管理器接口
事务管理器是
PlatformTransactionManager
接口对象。其主要用于完成事务的提交、回滚,及获取事务的状态信息
- 常用的两个实现类
DataSourceTransactionManager:
使用JDBC
或MyBatis
进行数据库
操作时使用HibernateTransactionManager:
使用Hibernate
进行持久化数据时使
用
- Spring的回滚方式
Spring
事务的默认回滚方式是:发生运行时异常和error
时回滚,发生受查(编译)异常时提交。不过,对于受查异常,程序员也可以手工设置其回滚方式
4.3.2 事务定义接口
事务定义接口
TransactionDefinition
中定义了事务描述相关的三类常量:事务隔离级别、事务传播行为、事务默认超时时限,及对它们的操作
4.3.2.1 五个事务隔离级别常量
这些常量均是以
ISOLATION_
开头。即形如ISOLATION_XXX
- DEFAULT : 采 用
DB
默 认 的 事 务 隔 离 级 别 。MySql
的默认为REPEATABLE_READ
;Oracle
默认为READ_COMMITTED
- READ_UNCOMMITTED:读未提交。未解决任何并发问题
- READ_COMMITTED:读已提交。解决脏读,存在不可重复读与幻读
- REPEATABLE_READ:可重复读。解决脏读、不可重复读,存在幻读
- SERIALIZABLE:串行化。不存在并发问题
4.3.2.2 七个事务传播行为常量
所谓事务传播行为是指,处于不同事务中的方法在相互调用时,执行期间事务的维护情况。如,A 事务中的方法 doSome()调用 B 事务中的方法doOther(),在调用执行期间事务的维护情况,就称为事务传播行为。事务传播行为是加在方法上的
- 事务传播行为常量都是以
PROPAGATION_
开头
PROPAGATION_REQUIRED
PROPAGATION_REQUIRES_NEW
PROPAGATION_SUPPORTS
PROPAGATION_MANDATORY
PROPAGATION_NESTED
PROPAGATION_NEVER
PROPAGATION_NOT_SUPPORTED
1.PROPAGATION_REQUIRED
指定的方法必须在事务内执行。若当前存在事务,就加入到当前事务中;若当前没有事务,则创建一个新事务。这种传播行为是最常见的选择,也是Spring默认的事务传播行为
2.PROPAGATION_SUPPORTS
指定的方法支持当前事务,但若当前没有事务,也可以以非事务方式执
3.PROPAGATION_REQUIRES_NEW
- 总是新建一个事务,若当前存在事务,就将当前事务挂起,直到新事务执行完毕
- 常量 TIMEOUT_DEFAULT 定义了事务底层默认的超时时限,sql 语句的执行时长。注意,事务的超时时限起作用的条件比较多,且超时的时间计算点较复杂。所以,该值一般就使用默认值即可