Spring 笔记
1. Spring 基本概念
这里介绍一下,spring的基本概念,简单的说一下ioc是什么,ioc容器是什么,ioc怎么实现的。
spring默认是单例的
1.1 IOC
ioc是一种思想叫控制反转,以前需要自己创建new对象,然后将对象赋值给某个对象。现在控制反转,可以不需要new对象,不关心对象是如何创建的,只需要使用即可。
1.2 IOC 容器
ioc创建的对象会全部放在,IOC容器中,要使用只需要去IOC容器中去取。
1.3 IOC容器在spring中的实现
这个可以看了源码补充,目前现在看到视频太简单了,只是说了用了什么类
2. 安装与HelloWorld
2.1 使用maven安装
只需要在maven中引入依赖即可
<dependencies>
<!-- 基于Maven依赖传递性,导入spring-context依赖即可导入当前所需所有jar包 -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.3.1</version>
</dependency>
<!-- junit测试 -->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>test</scope>
</dependency>
</dependencies>
2.2 创建spring配置文件和测试需要的类
第一步:先创建测试的类
package com.lin.spring.helloworld;
public class HelloWorld {
public void test1(){
System.out.println("你好,Spring");
}
}
第二步:创建spring配置文件
在resources创建spring配置文件,名字可以随便起,推荐叫applicationContext.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标签:将一个bean对象,将对象给IOS容器管理
id:bean的唯一标识(名称),不能重复
class:需要创建对象的类全类名
-->
<bean id="helloworld" class="com.lin.spring.helloworld.HelloWorld"></bean>
</beans>
2.3 创建测试类
package com.lin.spring.helloworld;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class HelloWorldTest {
@Test
public void test(){
//ioc 容器
ApplicationContext ioc = new ClassPathXmlApplicationContext("applicationContext.xml");
//根据name获取
HelloWorld baneName = (HelloWorld) ioc.getBean("helloworld");
System.out.println(baneName);
}
}
3. 使用XML管理Bean
3.1 获取bean的三种方式
- 第一种:根据bean标签的id获取
- 第二种:根据bean的类型获取
- 注意:根据类型获取bean,IOC容器中必须只有一个类型匹配bean,否则会报错
- 第三种:根据bean标签的id获取和类型获取(常用,这里也不能叫常用,在后面都是通过注解自动装置)
实例:
@Test
public void testIocGetBean(){
//获取ioc容器
ApplicationContext ioc = new ClassPathXmlApplicationContext("spring-ioc.xml");
//根据bean标签的id获取
Student studentOne = (Student) ioc.getBean("studentOne");
//根据类型获取,ioc容器中的类不能重复
Student studentTow = ioc.getBean(Student.class);
//根据bean标签的id获取和类型获取
Student student = ioc.getBean("studentOne",Student.class);
}
问题1:获取类型是否可以根据接口获取?
答:可以,但是类在spring ioc容器中必须是唯一的,并且类实现了该接口。
问题2:如果接口被多个类实现,并且都交给spring ioc管理了,可以根据类型获取吗?
答:不可以,多个spring也不知道使用那个会抛出异常
3.2 依赖注入之set注入
property标签:通过bean对象的属性注入(必须要创建set方法)
name:设置属性名称
value:设置属性赋值
<bean id="studentTow" class="com.lin.spring.ioc.xml.pojo.Student">
<property name="sid" value="1001"></property>
<property name="name" value="张三"></property>
<property name="age" value="23"></property>
<property name="gender" value="男"></property>
</bean>
3.3 依赖注入之构造器注入
- constructor-arg标签:根据构造器注入
- name属性:设置构造器参数名称
- value属性:设置值
注意:如果constructor-arg标签不写name属性,会按照构造器顺序填充
<bean id="studentThree" class="com.lin.spring.ioc.xml.pojo.Student">
<!-- 不设置name属性,会根据构造器顺序填充属性 -->
<!--<constructor-arg value="1002"></constructor-arg>
<constructor-arg value="李四"></constructor-arg>
<constructor-arg value="女"></constructor-arg>
<constructor-arg value="24"></constructor-arg>-->
<!-- 设置name,name属性不会管顺序,会按需名称添加(name的值就是构造器参数名称) -->
<constructor-arg name="sid" value="1002"></constructor-arg>
<constructor-arg name="name" value="李四"></constructor-arg>
<constructor-arg name="gender" value="女"></constructor-arg>
<constructor-arg name="age" value="24"></constructor-arg>
</bean>
3.4 特殊字符处理
记录一下null还有像<>等特殊字符字符如何处理
3.4.1 null
正确写法:
<bean id="studentFour" class="com.lin.spring.ioc.xml.pojo.Student">
<!-- 给一个属性赋值为null -->
<property name="gender">
<null></null>
</property>
</bean>
错误写法:
<bean id="studentFour" class="com.lin.spring.ioc.xml.pojo.Student">
<!-- 如果这样写,会将null作为字符串赋值给gender变量 -->
<property name="gender" value="null">></property>
</bean>
3.4.2 特殊字符
因为xml是标签标记语言,像<>等特殊字符,有两种方式,可以解决
方式一:使用转移字符(这里可以百度XML中常用转义符,查找)
<bean id="studentFour" class="com.lin.spring.ioc.xml.pojo.Student">
<!-- <相当于< >相当于> -->
<property name="name" value="<王五>"></property>
</bean>
方式二:使用xml语法定义的纯文本标签:
不管写什么都会直接赋值,不过不能写在属性里面,必须写在标签里面
<bean id="studentFour" class="com.lin.spring.ioc.xml.pojo.Student">
<property name="name">
<!-- <![CDATA[<>]]> 不好记,也不好写,可以使用idea快捷键,输入CD回车即可-->
<value><![CDATA[<王五>]]></value>
</property>
</bean>
3.5 依赖注入之Bean注入
bean注入可以定义使用IOC容器中的bean,也可以直接使用匿名类的方式
value标签或者属性都是用来填写普通类型的,比如string、int、long之类的
ref标签或者或者属性都是用来应用ioc容器中的bean,也就是对象
3.5.1 使用IOC容器的bean
只要在其他地方声明过的bean存在ioc容器中,都可以直接拿来使用,ref属性专门引用Ioc容器中的Bean
<bean id="clazz" class="com.lin.spring.ioc.xml.pojo.Clazz">
<property name="cId" value="1"></property>
<property name="cname" value="大班"></property>
</bean>
<bean id="studentFive" class="com.lin.spring.ioc.xml.pojo.Student">
<!-- ref属性:引用被spring ioc管理的对象,值对应id -->
<property name="clazz" ref="clazz"></property>
</bean>
3.5.2 使用级联方式
注意:级联方式必须要保证属性必须提前被初始化,new一个空对象,或者有一个对象,不能是null,否则会报错
<bean id="studentFive" class="com.lin.spring.ioc.xml.pojo.Student">
<property name="clazz.cId" value="2"></property>
<property name="clazz.cname" value="小班"></property>
</bean>
3.5.3 使用内部匿名bean的方式
使用内部匿名bean进行依赖注入,内部匿名bean只能在内部使用,不能通过ioc容器获取
<bean id="studentFive" class="com.lin.spring.ioc.xml.pojo.Student">
<property name="clazz">
<bean id="clazz" class="com.lin.spring.ioc.xml.pojo.Clazz">
<property name="cId" value="3"></property>
<property name="cname" value="中班"></property>
</bean>
</property>
</bean>
3.5.4 数组注入
public class Student{
private String[] hobby;
//隐藏get/set方法
}
<bean id="studentFive" class="com.lin.spring.ioc.xml.pojo.Student">
<!-- 数组注入 -->
<property name="hobby">
<array>
<!-- 使用ref 标签可以引入对象 -->
<value>唱</value>
<value>跳</value>
<value>Rap</value>
<value>篮球</value>
</array>
</property>
</bean>
3.5.5 list注入
public class Student {
private List<String> hobbyList;
//隐藏get/set方法
}
<bean id="studentFive" class="com.lin.spring.ioc.xml.pojo.Student">
<!-- List -->
<property name="hobbyList">
<list>
<!-- 使用ref 标签可以引入对象 -->
<value>唱</value>
<value>跳</value>
<value>Rap</value>
<value>篮球</value>
</list>
</property>
</bean>
3.5.6 map对象
public class Student implements Person{
private Map<String,Teacher> teacherMap;
//隐藏get/set方法
}
<bean id="studentFive" class="com.lin.spring.ioc.xml.pojo.Student">
<property name="teacherMap">
<map>
<!-- value-ref属性改成value,就是赋值基本类型了 -->
<entry key="1" value-ref="teacherOne"></entry>
<entry key="2" value-ref="teacherTwo"></entry>
</map>
</property>
</bean>
3.6 bean的单例和多例
在spring中默认是单例的,如果想要配置多例只需要在bean标签中加上scope属性值写成prototype(多例)。多例的时候在调用的时候才会创建bean,而单例启动就会创建bean
scope属性值有两个值:
- singleton:单利(默认就是这个)
- prototype:多利
示例:
<bean id="student" class="com.lin.spring.ioc.xml.pojo.Student" scope="singleton">
<property name="sid" value="1001"></property>
<property name="name" value="张三"></property>
</bean>
3.7 bean的生命周期
生命周期步骤:实例化-> 依赖注入-> 初始化-> 销毁
-
实例化:创建bean的时候执行
-
依赖注入:给属性赋值的时候
-
初始化:由bean标签的init-method属性调用
-
销毁:由bean标签的idestroy-method属性调用
3.7.1 单例的情况
实例:打印一个bean的生命周期
实体类:
package com.lin.spring.ioc.xml.pojo;
public class User {
private Integer id;
private String username;
private String password;
private Integer age;
public User() {
System.out.println("生命周期1:实例化");
}
public Integer getId() {
return id;
}
public void setId(Integer id) {
System.out.println("生命周期2:依赖注入");
this.id = id;
}
public void initMethod(){
System.out.println("生命周期3:初始化");
}
public void destroyMethod(){
System.out.println("生命周期4:销毁");
}
}
spring的xml配置文件:
<!--
init-method属性:用来指定对象初始化的时候调用(里面填写方法名)
destroy-method属性:用来指定对象销毁的时候调用(里面填写方法名)
-->
<bean id="user" class="com.lin.spring.ioc.xml.pojo.User" init-method="initMethod" destroy-method="destroyMethod">
<property name="id" value="1"></property>
<property name="username" value="admin"></property>
<property name="password" value="123456"></property>
<property name="age" value="23"></property>
</bean>
测试方法:
@Test
public void test(){
ClassPathXmlApplicationContext ioc = new ClassPathXmlApplicationContext("spring-lifecycle.xml");
User user = ioc.getBean("user", User.class);
System.out.println(user);
/**
* spring ioc容器关闭的时候,才会销毁bean,也就是ioc容器关闭的时候才会调用销毁代码
* ClassPathXmlApplicationContext实现了ApplicationContext接口扩展了这个接口刷新和关闭的方法
* ApplicationContext接口里面是没有销毁的方法的,ClassPathXmlApplicationContext类里面有销毁代码
*/
ioc.close();
}
3.7.2 多例的情况
多例和单例没什么太大的区别,这里的就记录了,就是bean不会提前创建而是使用的时候才进行创建
4.使用注解管理Bean
注解和xml作用一样可以管理Bean,只是把xml的bean标签换成了注解
常用的注解,等同于xml的bean标签
-
@Component 将类标记为普通组件
-
@Controller 将类标记为控制层组件
-
@Service 将类标识为业务组件
-
@Repository 将类标识为持久层组件
这四个组件在spring ioc中,作用是一模一样的,存在的目的只是给开发人员看,提高代码的可读性
4.1 组件的扫描Bean
在spring ioc配置文件中,只需要使用context:component-scan标签配置包名,只要配置了包名,就是扫描这个包下面的所有注解
注:需要导入context命名空间,提示
4.1.1 配置基本扫描
<!-- 扫描注解:扫描是扫描这个包路径下全部类 -->
<context:component-scan base-package="com.lin.spring.ioc.annotation"></context:component-scan>
<!-- 多个可以用,隔开 -->
<context:component-scan base-package="com.lin.spring.ioc.annotation.controller,com.lin.spring.ioc.annotation.dao.impl"></context:component-scan>
4.1.2 配置不扫描谁
使用context:exclude-filter标签,可以将不想扫描的排除出去。它里面有个type属性,常用值的:annotation和assignable
-
type属性:类型
-
annotation:通过注解方式,expression(排除规则)属性填写需要排除的注解全类名
-
assignable:根据类的类型进行排除,expression(排除规则)属性填写需要排除类的全类名
-
-
expression属性:排除规则
示例一:根据注解排除
<context:component-scan base-package="com.lin.spring.ioc.annotation">
<!-- 表示com.lin.spring.ioc.annotation包下面的所有类排除使用@Controller注解的 -->
<context:exclude-filter type="annotation" expression="org.springframework.stereotype.Controller"/>
</context:component-scan>
示例二:根据类排除
<context:component-scan base-package="com.lin.spring.ioc.annotation">
<!-- 表示com.lin.spring.ioc.annotation.controller.UserController类进行排除 -->
<context:exclude-filter type="assignable" expression="com.lin.spring.ioc.annotation.controller.UserController"/>
</context:component-scan>
4.1.3 配置只扫描谁
使用context:include-filter标签,这个没有什么实际的用处,因为用普通扫描的包已经将需要扫描的类全部扫描进去了,没有必要在进行扫描。如果实在需要使用需要在父标签context:component-scan加上use-default-filters="false"属性
-
type属性:类型
-
annotation:通过注解方式,expression(排除规则)属性填写需要排除的注解全类名
-
assignable:根据类的类型进行排除,expression(排除规则)属性填写需要排除类的全类名
-
-
expression属性:排除规则
示例一:根据注解配置扫描
<context:component-scan base-package="com.lin.spring.ioc.annotation" use-default-filters="false">
<!-- 表示com.lin.spring.ioc.annotation包下面只扫描使用@Controller注解的 -->
<context:include-filter type="annotation" expression="org.springframework.stereotype.Controller"/>
</context:component-scan>
示例二:根据类配置扫描
<context:component-scan base-package="com.lin.spring.ioc.annotation" use-default-filters="false">
<!-- 表示只扫描com.lin.spring.ioc.annotation.controller.UserController类 -->
<context:include-filter type="assignable" expression="com.lin.spring.ioc.annotation.controller.UserController"/>
</context:component-scan>
4.2 问:使用创建bean注解的id是什么?
默认是使用类名开头字母小写,比如UserController默认的bean id就是userController
4.1.1 自定义bean的id
只需要在注解的value参数中传入值,就是bean的id
例如:
@Controller("controller")
public class UserController {
}
4.2 使用自动装配
只需要在属性名加上@Autowired注解,就会自动将对象赋值过去。不需要提供set方法,当然也可以不加在属性上面,可以在set方法或者构造器上面。
默认通过type进行查找,如果type有多个的情况使用type+name的方式进行自动装配,如果都查找不到就抛出异
常:NoSuchBeanDefinitionException
4.2.1 加在属性上面
实例:
@Controller
public class UserController {
@Autowired
private UserService userService;
}
4.2.2 加在set方法上面
@Service
public class UserServiceImpl implements UserService {
private UserDao userDao;
@Autowired
public void setUserDao(UserDao userDao) {
this.userDao = userDao;
}
}
4.2.3 加在构造器上面
如果加在构造器上面,spring会优先使用带@Autowired注解的类进行处理
@Service
public class UserServiceImpl implements UserService {
private UserDao userDao;
@Autowired
public UserServiceImpl(UserDao userDao) {
this.userDao = userDao;
}
}
5. AOP
5.1 AOP 概念
AOP挺重要的,而且概率不是特别好理解,所以这里单独开个板块,记录一下自己理解的AOP概念。
aop目的就是在不改变其他人的代码的情况,前面或者后面加上自己的代码,比如某个源码或者其他同事的代码是不能改变别人代码,这里可以使用AOP在方法执行前或者执行后加上我们希望执行的东西。再比如日志,不可能每个业务都进行日志记录,这样子工作量很大,如果使用AOP只需要配置切入点,就利用前置通知和后置通知,记录用户请求参数与返回结果
简单解释:AOP就是不改变代码的情况执行前或者执行后加上我们想要的代码。
在本人看的教程中把AOP说的过于复杂,并没有什么意义,过于抽象,现在本人还是不知道哪些相关术语代表着什么,以后知道了在补充,这里全部记录但是只补充本人理解的专业术语。
- 横切关注点
- 通知
- 通知比较重要:其实就是前置通知、后置通知、返回通知、异常通知、环绕通知。后面会详细补充
- 切面
- 封装乱七八糟的前置通知、后置通知、返回通知、异常通知、环绕通知的类
- 目标
- 代理
- 被代理的目标类,不是自己实例化的类,是AOP用接口再次实例的那个类
- 连接点
- 切入点
- spring aop就是根据切入点来定位,哪些类需要进行切面(也可以理解成哪些类需要通知)
5.2 AOP 入门
第一步:添加依赖
除了ioc所需要的依赖还需要单独引入spring-aspects
<dependencies>
<!-- ioc基础依赖 -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.3.1</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>test</scope>
</dependency>
<!-- 在IOC所需依赖基础上再加入spring-aspects依赖 -->
<!-- spring-aspects会帮我们传递过来aspectjweaver -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aspects</artifactId>
<version>5.3.1</version>
</dependency>
</dependencies>
第二步:配置扫描注解
除了要配置扫描bean,还需要单独加入aop:aspectj-autoproxy标签
<!-- 扫描注解:扫描是扫描这个包路径下全部类 -->
<context:component-scan base-package="com.lin.spring.aop.annotation"></context:component-scan>
<!-- 开启基于注解的AOP -->
<aop:aspectj-autoproxy />
第三步:创建目标类
@Component
public class Calculator {
public int add(int i, int j) {
int result = i + j;
System.out.println("方法内部 result = " + result);
return result;
}
public int sub(int i, int j) {
int result = i - j;
System.out.println("方法内部 result = " + result);
return result;
}
public int mul(int i, int j) {
int result = i * j;
System.out.println("方法内部 result = " + result);
return result;
}
public int div(int i, int j) {
int result = i / j;
System.out.println("方法内部 result = " + result);
return result;
}
}
第四步:创建切面类
前面类需要加上@Aspect注解
package com.lin.spring.aop.annotation;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;
import java.util.Arrays;
/**
* 切面类
* 在切面中,需要加入指定的注解,将其标记为通知方法
*/
@Component
@Aspect
public class LoggerAspect {
/**
* 这里用代理接口或者目标类方法都可以
* 切入点:
* execution()固定写法
* public int 与代理接口的访问修饰符和返回值一致
* com.lin.spring.aop.annotation.Calculator 全类名,明确表示是那个
* .add(int,int) 方法名和参数,参数只需要传类型即可,在java中参数类型已经可以明确表示是那个方法了
* .
*/
@Before("execution(public int com.lin.spring.aop.annotation.Calculator.add(int,int))")
public void beforeAdviceMethod(){
System.out.println("LoggerAspect,前置通知");
}
}
第四步:创建测试类
import com.lin.spring.aop.annotation.Calculator;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class AopTest {
@Test
public void testAPPByAnnotation(){
ApplicationContext ioc = new ClassPathXmlApplicationContext("aop-annotation.xml");
/**
* 使用AOP不能直接获取目标类的对象,必须使用代理的接口的对象,IOC也不会存在目标类的接口
* 所以这里获取bean要填写获取代理对象的接口,让它自动创建代理接口和实现类
*/
Calculator calculator = ioc.getBean(Calculator.class);
calculator.div(1,1);
}
}
5.3 AOP 切入点基本配置
5.3.1 切入点表达式
用*可以代表包也可以代表类也可以代表包
/**
* 使用表达式切入点
* execution()固定写法
* * 表示代理接口所有的访问修饰符和返回值
* com.lin.spring.aop.annotation.Calculator.* 表示类下面的所有方法
* - 类也可以用*代替,表示这个包下面的所有类:com.lin.spring.aop.annotation.*.*
* - 包也可以用*代替,表示这个包同级的下面的所有包:com.lin.spring.*.annotation.*.*
* (..) 表示所有参数,我不关心你是什么参数,都进行标记切入点
*/
@Before("execution(* com.lin.spring.aop.annotation.Calculator.*(..))")
public void beforeAdviceMethod(){
System.out.println("LoggerAspect,前置通知");
}
5.3.2 公共的切入点
很多时候有个一个切面类的规则是一样的,这样子可以定义一个公共的切人点,其他切入点都引用公共的。
只需要使用@Pointcut注解声明公共的切入点,其他切入点都引用次方法名即可
/**
* 声明公共的切入点表达式
*/
@Pointcut("execution(* com.lin.spring.aop.annotation.Calculator.*(..))")
public void pointcut(){}
/**
* 使用公共的切入点表达式
* @param joinPoint
*/
@Before("pointcut()")
public void beforeAdviceMethod(JoinPoint joinPoint){
//获取连接点所对应的签名信息,通过签名信息.getName可以获取方法名
Signature signature = joinPoint.getSignature();
//获取连接点所对应方法参数
Object[] args = joinPoint.getArgs();
System.out.println("LoggerAspect,前置通知 方法:"+signature.getName()+",参数:"+ Arrays.toString(args));
}
5.4 AOP通知-注解版本
aop核心就是通知,分别有前置通知,后置通知、返回通知、异常通知、环绕通知,它们分别有对应的注解,用于标识各种通知。
它们的执行顺序是:前置通知-->被切面的方法-->-->[异常通知]-->返回通知-->后置通知,如果异常通知被执行,返回通知不会被执行,其他都会执行
- @Before:前置通知,在目标对象方法执行之前执行
- @After:后置通知,在目标对象方法的finally中执行
- @AfterReturning:返回通知,在目标对象方法返回值之后执行
- @AfterThrowing:异常通知,目标对象方法执行发送异常后执行
- @Around:环绕通知
5.4.1 前置通知:
@Before("pointcut()")
public void beforeAdviceMethod(JoinPoint joinPoint){
//获取连接点所对应的签名信息,通过签名信息.getName可以获取方法名
Signature signature = joinPoint.getSignature();
//获取连接点所对应方法参数
Object[] args = joinPoint.getArgs();
System.out.println("LoggerAspect,前置通知 方法:"+signature.getName()+",参数:"+ Arrays.toString(args));
}
5.4.2 后置通知
@After("pointcut()")
public void afterAdviceMethod(JoinPoint joinPoint){
//获取连接点所对应的签名信息,通过签名信息.getName可以获取方法名
Signature signature = joinPoint.getSignature();
System.out.println("LoggerAspect,后置通知 方法:"+signature.getName());
}
5.4.3 返回通知
只需要result属性的值和参数的名一致,AOP就会自动将返回值赋值给result属性值与参数名称一致的参数
@AfterReturning(value = "pointcut()",returning = "result")
public void afterReturningAdviceMethod(JoinPoint joinPoint,Object result){
//获取连接点所对应的签名信息,通过签名信息.getName可以获取方法名
Signature signature = joinPoint.getSignature();
System.out.println("LoggerAspect,返回通知 方法:"+signature.getName()+",结果:"+ result);
}
5.4.4 异常通知
只需要throwing属性的值和参数的名一致,AOP就会自动将异常赋值给result属性值与参数名称一致的参数,接收类型必须是Throwable的子类
@AfterThrowing(value = "pointcut()",throwing = "ex")
public void afterThrowingAdviceMethod(JoinPoint joinPoint,Throwable ex){
Signature signature = joinPoint.getSignature();
System.out.println("LoggerAspect,异常通知 方法:"+signature.getName()+",异常:"+ ex);
}
5.4.5 环绕通知
环绕通知:相当于前置通知+后置通知+返回通知+异常通知,正常情况使用环绕通知也就不会在使用其他通知了。
@Around("pointcut()")
public Object aroundAdviceMethod(ProceedingJoinPoint joinPoint){
Object result = null;
//表示目标对象方法的执行
try {
System.out.println("环绕通知-->前置通知");
result = joinPoint.proceed();
System.out.println("环绕通知-->返回通知");
} catch (Throwable e) {
e.printStackTrace();
System.out.println("环绕通知-->异常通知");
} finally {
System.out.println("环绕通知-->后置通知");
}
return result;
}
5.5 AOP通知-XML版本
xml版本使用的概率很低,如果要使用可以结合注解的方式看,看一眼就明白了。
<aop:config>
<!-- 设置一个公共的切入点表达式 -->
<aop:pointcut id="pointCut" expression="execution(* com.lin.spring.aop.xml.Calculator.*(..))"/>
<!-- 将IOC容器中的某个bean设置成切面 -->
<aop:aspect ref="loggerAspect">
<!-- 前置通知 -->
<aop:before method="beforeAdviceMethod" pointcut-ref="pointCut"></aop:before>
<!-- 后置通知 -->
<aop:after method="afterAdviceMethod" pointcut-ref="pointCut"></aop:after>
<!-- 返回通知 -->
<aop:after-returning method="afterReturningAdviceMethod" pointcut-ref="pointCut" returning="result"></aop:after-returning>
<!-- 异常通知 -->
<aop:after-throwing method="afterThrowingAdviceMethod" pointcut-ref="pointCut" throwing="ex"></aop:after-throwing>
<!-- 环绕通知 -->
<aop:around method="aroundAdviceMethod" pointcut-ref="pointCut"></aop:around>
</aop:aspect>
<!-- 引入第二个切面 -->
<aop:aspect ref="validateAspect" order="1">
<aop:before method="beforeMethod" pointcut-ref="pointCut"></aop:before>
</aop:aspect>
</aop:config>
6. 声明式事务
事务的作用,就是当某段代码发生了异常,操作的数据进行回滚
事务有两种,一种是编译式事务,一种是声明式事务,简单来说编译式事务就是要自己写代码处理,而声明式事务通过配置让框架来实现
6.1 开启事务
第一步:导入需要的maven
<dependencies>
<!-- 基于Maven依赖传递性,导入spring-context依赖即可导入当前所需所有jar包 -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.3.1</version>
</dependency>
<!-- Spring 持久化层支持jar包 -->
<!-- Spring 在执行持久化层操作、与持久化层技术进行整合过程中,需要使用orm、jdbc、tx三个 jar包 -->
<!-- 导入 orm 包就可以通过 Maven 的依赖传递性把其他两个也导入 -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-orm</artifactId>
<version>5.3.1</version>
</dependency>
<!-- Spring 测试相关 -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>5.3.1</version>
</dependency>
<!-- junit测试 -->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>test</scope>
</dependency>
<!-- MySQL驱动 -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.16</version>
</dependency>
<!-- 数据源 -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.0.31</version>
</dependency>
</dependencies>
第二步:先在spring配置的xml里面引入开启事务注解驱动
<!--
开启事务注解的驱动
将使用@Transactional注解所标识的方法活类中所有的方法使用事务管理
如果DataSourceTransactionManager的bean id就是叫transactionManager,可以直接简写为:<tx:annotation-driven />,属性不写,只写标签
-->
<!-- 这里导入命名空间的时候idea会提示有多个必须要选择tx: xmlns:tx="http://www.springframework.org/schema/tx"-->
<tx:annotation-driven transaction-manager="transactionManager"/>
第三步:在类上面或者方法上面加上@Transactional注解,表示开启事务
@Transactional
6.2 事务属性
事务属性分为只读、超时、休眠、回滚策略,只要发送对应的操作就会进行事务回滚
6.2.1 只读
给上readOnly属性设置成true(默认是false),该事务中只能出现查询,不能出现增删改,否则会抛出异常
Caused by: java.sql.SQLException: Connection is read-only. Queries leading to data modification
are not allowed
@Transactional(readOnly = true)
6.2.2 超时
超时,默认是-1,表示不超时,如果超时会进行事物回滚,整个事务方法必须在x秒内执行完,单位是秒
@Transactional(timeout = 3)
6.2.3 回滚策略
spring事务有四个回滚策略,常用就一个
-
noRollbackFor属性:无视某个异常,让事务正常提交,多个用逗号隔离(此属性用class对象)
-
noRollbackForClassName属性:无视某个异常,让事务正常提交,多个用逗号隔离(此属性用字符串全类名)
//class对象
@Transactional(noRollbackFor = {ArithmeticException.class})
//字符串全类名
@Transactional(noRollbackForClassName = "java.lang.ArithmeticException")
6.3 事务隔离级别
事务有四个特性:一致性、原子性、隔离性、持久性。
事物的隔离级别,用于就是解决对应的问题
mysql 默认隔离级别是:可重复读脏读、幻读、不可重复读
- 读未提交:
- 举例:A事务的隔离级别是:读未提交,B事务的隔离级别是:可重复读
- A事务可以读到,B事务未提交的数据,如果最终B回滚了,A读取到B的数据也就没任何意义 也叫脏读
- 读已提交:
- A事务的隔离级别是:读已提交,B事务的隔离级别是:可重复读
- A事务只能读到,B事务提交的数据,未提交的数据读取不到
- 可重复读(可重复读的隔离级别,在操作某行数据的时候,会给这行数据加锁)
- A事务的隔离级别是:可重复读,B事务的隔离级别是:可重复读
- 比如A事务在操作id为1的数据,这个时候B也想操作id未1的数据,这个时候b会堵塞,必须等A操作完并且提交了数据,B才能操作
- 如果A没有提交只操作了id为1的数据,B可以操作其他表的其他数据,加锁只是针对某一行
- 串行化(串行化会给整个表加锁)
- A事务的隔离级别是:串行化,B事务的隔离级别是:可重复读
- 串行化会给整个表加锁,如果A事务,没有进行提交,B事务是整个表都不能操作,会进行堵塞状态,必须等A提交完事务
各个隔离级别存在的问题:
名称 | 隔离级别 | 脏读 | 不可重复读 | 幻读 |
---|---|---|---|---|
读未提交 | READ UNCOMMITTED | 有 | 有 | 有 |
读已提交 | READ COMMITTED | 无 | 有 | 有 |
可重复读 | REPEATABLE READ | 无 | 无 | 有 |
串行化 | SERIALIZABLE | 无 | 无 | 无 |
种数据库产品对事务隔离级别的支持:
名称 | 隔离级别 | Oracle | MySQL |
---|---|---|---|
读未提交 | READ UNCOMMITTED | 不支持 | 支持 |
读已提交 | READ COMMITTED | 支持(默认) | 支持 |
可重复读 | REPEATABLE READ | 不支持 | 支持(默认) |
串行化 | SERIALIZABLE | 支持 | 支持 |
@Transactional(isolation = Isolation.DEFAULT)//使用数据库默认的隔离级别
@Transactional(isolation = Isolation.READ_UNCOMMITTED)//读未提交
@Transactional(isolation = Isolation.READ_COMMITTED)//读已提交
@Transactional(isolation = Isolation.REPEATABLE_READ)//可重复读
@Transactional(isolation = Isolation.SERIALIZABLE)//串行化
6.4 事物的传播行为
事物的传播行为是指,比如我在方法外面有一个事物了,我在方法里面调用了某一个方法也有事物,在默认情况,spring会使用最外层的事物。也就是说默认情况下,只需要发生异常最外面那一层代码是事物都会进行回滚;
举例:使用伪代码的方式演示事物
import org.springframework.transaction.annotation.Transactional;
public class Test {
@Transactional
public void purchase(){
System.out.println("购买");
}
@Transactional
public void addIntegral(){
System.out.println("增加积分");
//故意制造异常
System.out.println(1/0);
}
@Transactional
public void checkout(){
purchase();
addIntegral();
System.out.println("结账");
}
public static void main(String[] args) {
new Test().checkout();
}
}
这段代码可以看出,增加积分的时候,发生了异常,这个时候整个结账方法都会进行回滚。
@Transactional(propagation = Propagation.REQUIRED)//默认传播行为
@Transactional(propagation = Propagation.REQUIRES_NEW)//加在方法中,表示次方法每次调用都会创建一个新的事物,只要这个方法成功就提交
用上面的例子如果将purchase()方法上面的事物改成@Transactional(propagation = Propagation.REQUIRES_NEW),在运行这段代码,购买是成功的,但是到增加积分那就会抛出异常。
6.5 使用xml操作事物
基于xml操作事物需要导入aspectJ的依赖
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aspects</artifactId>
<version>5.3.1</version>
</dependency>
<aop:config>
<!-- 配置事务通知和切入点表达式 -->
<aop:advisor advice-ref="txAdvice" pointcut="execution(* com.atguigu.spring.tx.xml.service.impl.*.*(..))"></aop:advisor>
</aop:config>
<!-- tx:advice标签:配置事务通知 -->
<!-- id属性:给事务通知标签设置唯一标识,便于引用 -->
<!-- transaction-manager属性:关联事务管理器 -->
<tx:advice id="txAdvice" transaction-manager="transactionManager">
<tx:attributes>
<!-- tx:method标签:配置具体的事务方法 -->
<!-- name属性:指定方法名,可以使用星号代表多个字符 -->
<tx:method name="get*" read-only="true"/>
<tx:method name="query*" read-only="true"/>
<tx:method name="find*" read-only="true"/>
<!-- read-only属性:设置只读属性 -->
<!-- rollback-for属性:设置回滚的异常 -->
<!-- no-rollback-for属性:设置不回滚的异常 -->
<!-- isolation属性:设置事务的隔离级别 -->
<!-- timeout属性:设置事务的超时属性 -->
<!-- propagation属性:设置事务的传播行为 -->
<tx:method name="save*" read-only="false" rollback- for="java.lang.Exception" propagation="REQUIRES_NEW"/>
<tx:method name="update*" read-only="false" rollback- for="java.lang.Exception" propagation="REQUIRES_NEW"/>
<tx:method name="delete*" read-only="false" rollback- for="java.lang.Exception" propagation="REQUIRES_NEW"/>
</tx:attributes>
</tx:advice>
7. Spring的测试类
Spring有提供专门用于测试使用的注解,这样子就不用每次都去引入xml,然后在调用bean,可以直接指定测试环境,然后在引入xml(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"
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">
<!-- 扫描组件 -->
<context:component-scan base-package="com.lin.spring.transaction"></context:component-scan>
<!-- 引入properties文件 -->
<context:property-placeholder location="classpath:jdbc.properties"></context:property-placeholder>
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
<property name="url" value="${jdbc.url}"></property>
<property name="driverClassName" value="${jdbc.driver}"></property>
<property name="username" value="${jdbc.user}"></property>
<property name="password" value="${jdbc.password}"></property>
</bean>
<bean class="org.springframework.jdbc.core.JdbcTemplate">
<property name="dataSource" ref="dataSource"></property>
</bean>
</beans>
package com.lin.spring.transaction.test;
import org.junit.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import java.util.List;
//设置测试类运行环境,这样就可以直接通过注入的方式直接获取ioc容器中的bean
@RunWith(SpringJUnit4ClassRunner.class)
//指定spring测试环境的配置文件 classpath:表示类路径下面
@ContextConfiguration("classpath:spring-jdbc.xml")
public class JdbcTemplateTest {
@Autowired
private JdbcTemplate jdbcTemplate;
@Test
public void testInsert(){
String sql = "INSERT INTO t_user values(null,?,?,?,?,?)";
jdbcTemplate.update(sql,"root","123",23,"女","[email protected]");
System.out.println("插入语成功");
}
}
标签:Spring,笔记,学习,bean,通知,spring,println,ioc,public
From: https://www.cnblogs.com/lin168/p/17052063.html