一、什么是Spring?
1、介绍
2、Spring的特点
二、控制反转IOC
1、什么是IOC
2、 IOC理解,创建一个模块由Spring管理创建对象,另一个由程序员自己创建对象,两则进行对比。
基于xml的IOC
a>使用setter注入依赖
b>使用构造器注入依赖
基于注解的IOC
1)创建对象的注解
2)依赖注入的注解
添加包扫描的多种方式
三层架构项目的分析
普通的三层项目
基于xml的IOC三层架构项目(Spring接管对象)
基于注解的IOC三层架构项目(Spring接管对象)
Spring核心配置文件的拆分策略
拆分基于注解的IOC的三层项目的配置文件
三、面向切面编程AOP
手写AOP框架
手写AOP框架第一个版本
手写AOP框架第二个版本
手写AOP框架第三个版本
手写AOP框架第四个版本
手写AOP框架第五个版本
Spring原生的AOP
AOP相关术语
面向切面编程的AspectJ框架
AspectJ常见通知类型
AspectJ 的切入点表达式(掌握)
1.AspectJ的前置通知@Before
2.AspectJ的前置通知@AfterReturning
3.AspectJ的环绕通知@Around
4.AspectJ的最终通知@After
5、@PointCut给切入点表达式起别名
四、Spring整合MyBatis
五、事务
Spring中添加事务的两种方式
在Spring中通过注解添加事务
声明式事务的实现
设置事务处理的优先级
事务中指定某些错误类型不回滚
事务的隔离级别
Spring事务的传播特性
事务传播特性解析
Spring学习
业精于勤,荒于嬉。
一、什么是Spring?
1、介绍
Spring它是一个容器,也是一个框架,它是整合其它框架的框架。Spring看到其他什么框架好用,它就会把它整合进来,比如MyBatis框架就被Spring看上了,然后就整合进Spring里面。
Spring的核心是 控制反转IOC 和 面向切面编程AOP。它由20多个模块构成.它在很多领域都提供优秀的解决方案。
Spring 根据代码的功能特点,使用 Ioc 降低业务对象之间耦合度。IoC 使得主业务在相互调用过程中,不用再自己维护关系了,即不用再自己创建要使用的对象了。而是由 Spring 容器统一管理,自动“注入”,注入即赋值。 而 AOP 使得系统级服务得到了最大复用,且不用再由程序员手工将系统级服务“混杂”到主业务逻辑中了,而是由 Spring 容器统一完成“织入”。
2、Spring的特点
1)轻量级
由20多个模块构成,每个模块jar包都很小,小于1M,Spring核心包也就3M左右.
编写代码比较自由,不用像MyBatis那样一定要写什么什么配置文件。
2)面向接口编程
使用接口,就是面向灵活,项目的可扩展性好,可维护性都极高.接口不关心实现类的类型.使用时接口指向实现类,只要切换实现类即可切换整个功能.
3)AOP:面向切面编程
就是将公共的,通用的,重复的代码单独开发在一个切面中,在需要的时候再从切面中反织回去.底层的原理是动态代理.
4)整合其它框架
它整合后使其它框架更易用,比如整合了MyBatis框架
二、控制反转IOC
1、什么是IOC
控制反转IOC(Inversion of Control)是一个概念,是一种思想。由Spring容器进行对象的创建和依赖注入.程序员在使用时直接取出使用。由容器集中进行对象和创建和依赖管理。
Spring 根据代码的功能特点,使用 Ioc 降低业务对象之间耦合度。IoC 使得主业务在相互调用过程中,不用再自己维护关系了,即不用再自己创建要使用的对象了。而是由 Spring 容器统一管理,自动“注入”,注入即赋值。
正转:由程序员进行对象的创建和依赖注入称为正转.程序员说了算.
Student stu = new Student(); ===>程序员自己创建对象
stu.setName(“张三”); ===>程序员自己进行赋值(依赖注入)
stu.setAge(22);
反转:由Spring容器进行创建对象和依赖注入称为反转,将控制权从程序员手中夺走,由Spring容器进行控制,称为反转。 容器说了算.
===>Spring容器负责对象的创建
===>Spring容器依赖注入值
2、 IOC理解,创建一个模块由Spring管理创建对象,另一个由程序员自己创建对象,两则进行对比。
创建一个空项目,然后在其中创建一个maven子模块:
其pom.xml文件:
<?xml version="1.0" encoding="UTF-8"?>
4.0.0
<groupId>com.lin.spring</groupId>
<artifactId>Spring-demo1</artifactId>
<version>1.0-SNAPSHOT</version>
<properties>
<maven.compiler.source>11</maven.compiler.source>
<maven.compiler.target>11</maven.compiler.target>
</properties>
<dependencies>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.13.2</version>
<scope>test</scope>
</dependency>
<!--添加Spring框架依赖-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.2.5.RELEASE</version>
</dependency>
</dependencies>
<build>
<resources>
<!--
"**" 表示任意级目录
"*" 表示任意文件
意思就是把所有 /src/main/java 中所有 .xml和.properties 文件也打包进包中
会打包到target目录中,这些文件也会加载到其中,在运行的时候使用。
如果是jar/war包,则这些xml会原样放到包的相应目录下。
-->
<resource>
<directory>./src/main/java</directory>
<includes>
<include>**/*.xml</include>
<include>**/*.properties</include>
</includes>
</resource>
<resource>
<directory>./src/main/resources</directory>
<includes>
<include>**/*.xml</include>
<include>**/*.properties</include>
</includes>
</resource>
</resources>
</build>
Spring的依赖配置添加完成,接下来就是创建Spring的核心配置文件起名applicationContext.xml:
基于xml的IOC
对象的创建和注入依赖都在applicationContext.xml文件中的指定设置好。创建Spring容器的时候指明这个xml文件,Spring就会自动创建这个xml文件的对象并且注入好值。
a>使用setter注入依赖
a>使用setter注入依赖
第一种:注入基本数据类型,使用value属性
<!--创建学校对象-->
<bean id="schoolBySpring" class="com.lin.spring.bean.School">
<property name="schoolName" value="莆田学院"></property>
<property name="address" value="莆田城厢区"></property>
</bean>
注意:此方法需要提供相应属性的setter方法,他会调用对象的setXXX()方法
第二种:注入引用数据类型的数据,使用ref属性
<property name="school" ref="schoolBySpring"></property>
此引用数据类型必须在此配置文件.xml种配置过bean,在此文件中的bean实例对象。
applicationContext.xml:
<?xml version="1.0" encoding="UTF-8"?><!--
基于xml的IOC:
1.创建对象 和 依赖注入,交给Spring容器管理完成
id:就是指明创建对象的名称
class:就是对象对应的全类名,Spring底层会通过反射创建对象(只能是类,不能是接口)
2.当启动Spring容器的时候,
默认就会调用无参构造器创建此xml文件中的所有对象,所以必须提供无参构造器。
相当于Student stuBySpring = new Student();
3.同时会给创建的对象赋值
a>使用setter注入依赖
第一种:注入基本数据类型,使用value属性
<property name="stuName" value="由spring注入的stuName"></property>
<property name="age" value="12"></property>
注意:此方法需要提供相应属性的setter方法
第二种:注入引用数据类型的数据,使用ref属性
<property name="school" ref="schoolBySpring"></property>
此引用数据类型必须在此配置文件.xml种配置过bean,在此文件中的bean实例对象。
b>使用构造器注入依赖
-->
<!--<bean />:用于定义一个实例对象。一个实例对应一个 bean 元素。-->
<bean id="stuBySpring" class="com.lin.spring.bean.Student">
<property name="stuName" value="由spring注入的stuName"></property>
<property name="age" value="12"></property>
<!--注入引用数据类型数据,真正的注入依赖,依赖学校对象的创建-->
<property name="school" ref="schoolBySpring"></property>
</bean>
<!--创建学校对象-->
<bean id="schoolBySpring" class="com.lin.spring.bean.School">
<property name="schoolName" value="莆田学院"></property>
<property name="address" value="莆田城厢区"></property>
</bean>
测试代码:
public class createInstanceTest {
// 由程序员自己创建对象,并注入依赖
@Test
public void testCreateInstanceByMe(){
Student stu = new Student();
System.out.println(stu);
}
// 由Spring自己创建对象并注入依赖
@Test
public void testCreateInstanceBySpring(){
// 创建Spring容器对象并启动容器
(要传入Spring的核心配置文件applicationContext.xml的相对路径,这个是从resources目录下读取)
// 创建Spring容器对象的同时会创建Spring管理的所有对象并自动注入依赖
// 会创建并注入依赖Spring配置文件applicationContext.xml中的所有bean实例对象
ApplicationContext ac = new ClassPathXmlApplicationContext("applicationContext.xml");
System.out.println("-----------------");
// 取出Spring管理的对象
Student student = (Student) ac.getBean("stuBySpring");
System.out.println(student);
}
}
b>使用构造器注入依赖
第一种:使用构造方法的形参名称注入值
public class Dept {
private Integer did;
private String dName;
public Dept() {
System.out.println("Dept的wu参构造器执行。。。");
}
public Dept(Integer did, String dName) {
System.out.println("Dept的有参构造器执行。。。");
this.did = did;
this.dName = dName;
}
@Override
public String toString() {
return "Dept{" +
"did=" + did +
", dName='" + dName + '\'' +
'}';
}
}
@Test
public void testDept(){
ApplicationContext ac = new ClassPathXmlApplicationContext(“applicationContext.xml”);
Dept dept = (Dept) ac.getBean("deptBySpring");
System.out.println(dept);
}
第二种:使用构造方法的参数的下标注入值
package com.bjpowernode.pojo3;
/**
*
*/
public class Student {
private String name;
private int age;
//引用类型的成员变量
private School school;
// 有参构造方法的参数是有下标的:0,1,2
public Student(String name, int age, School school) {
this.name = name;
this.age = age;
this.school = school;
}
@Override
public String toString() {
return "Student{" +
"name='" + name + '\'' +
", age=" + age +
", school=" + school +
'}';
}
}
<?xml version="1.0" encoding="UTF-8"?> 第三种:使用构造方法参数默认顺序package com.bjpowernode.pojo3;
/**
*
*/
public class Student {
private String name;
private int age;
//引用类型的成员变量
private School school;
// 有参构造方法的参数的顺序:name,age,school
public Student(String name, int age, School school) {
this.name = name;
this.age = age;
this.school = school;
}
@Override
public String toString() {
return "Student{" +
"name='" + name + '\'' +
", age=" + age +
", school=" + school +
'}';
}
}
基于注解的IOC 之前是在xml文件中,指明要创建的对象,现在是使用注解指明要创建对象的类。 基于注解的IOC也称为DI(Dependency Injection),它是IOC(思想)的具体实现的技术. 基于注解的IOC,必须要在Spring的核心配置文件中添加包扫描,这样Spring才会去这个包下去扫描,一旦扫描到某个类有用创建对象的注解,就创建对象。 1)创建对象的注解 @Component:可以创建任意对象。创建的对象的默认对象名称是类名的驼峰命名法(Student类==》student)。也可以指定对象的名称@Component("指定名称"). @Controller:专门用来创建界面层的控制器的对象(Servlet),这种对象可以接收用户的请求,可以返回处理结果给客户端. @Service:专门用来创建业务逻辑层的对象(UserService),负责向下访问数据访问层,处理完毕后的结果返回给界面层. @Repository:专门用来创建数据访问层的对象(UserMapper),负责数据库中的增删改查所有操作。 使用方法:在要创建对象的类上使用注解标识。package com.bjpowernode.s01;
/**
1.在要创建对象的类上面使用创建对象的注解
2.交给Spring去创建对象,就是在容器启动时创建
3.然后在Spring核心配置文件中的包扫描中指明这个要创建对象类所在的包,
这样Spring在启动的时候就会去这些包中扫描有没有创建对象的注解,有则创建。
*/
@Component(“stu”)
public class Student {
private String name;
private int age;
public Student() {
System.out.println("学生对象的无参构造方法.........");
}
@Override
public String toString() {
return "Student{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}
<?xml version="1.0" encoding="UTF-8"?><context:component-scan base-package=“com.bjpowernode.s01”></context:component-scan>
public class MyTest01 {
@Test
public void testStudent(){
//创建容器对象并启动
ApplicationContext ac = new ClassPathXmlApplicationContext("s01/applicationContext.xml");
//取出对象
Student stu = (Student) ac.getBean("stu");
System.out.println(stu);
}
}
//学生对象的无参构造方法…
//Student{name=‘null’, age=0}
2)依赖注入的注解
给上面创建好的对象中的属性进行赋值。
简单类型(8种基本类型+String)数据的注入
@Value:用来给简单类型注入值
引用类型的数据注入
A.@Autowired:翻译成自动装配,使用类型注入值,Spring会从整个Bean工厂中搜索同源类型的对象进行注入。
同源类型也可注入.
什么是同源类型:
a.被注入的类型(Student中的school)与注入的类型是完全相同的类型
b.被注入的类型(Student中的school父)与注入的类型(子)是父子类
c.被注入的类型(Student中的school接口)与注入的类型(实现类)是接口和实现类的类型
注意: 当要注入的引用类型对象有父子类对象的情况下,如果使用按类型注入,就意味着有多个可注入的对象。此时Spring会按照名称进行二次筛选,选则与被注入对象相同名称的对象进行注入。
比如:
已知bean工厂中有一个School对象(默认名称school)和subSchool(默认名称SubSchool)对象,按同源类型注入的话,school和subSchool都可以注入,然后会按照名称进行二次筛选,就会选父类的School对象,因为它的名称和要被注入的对象名称相同,所以有这种情况最好使用名称注入。
@Autowired
private School school;
B.@Autowired
@Qualifier("名称")
两个标签一起使用,采用名称注入值,Spring会从整个Bean工厂中搜索指定名称的对象进行注入。
注意:如果有父子类的情况下,建议直接按名称进行注入值.
A.@Autowired案例:
// 创建School对象
@Component
public class School {
// 简单类型的数据注入
@Value(“莆田学院”)
private String schoolName;
@Value(“莆田”)
private String address;
public School() {
System.out.println("School的无参构造器");
}
public School(String schoolName, String address) {
System.out.println("School的you参构造器");
this.schoolName = schoolName;
this.address = address;
}
@Override
public String toString() {
return "School{" +
"schoolName='" + schoolName + '\'' +
", address='" + address + '\'' +
'}';
}
}
package com.lin.spring.bean2;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
/**
-
@author shkstart
-
@create 2022-06-06 16:34
*/
@Component()
public class Student {
@Value(“测试”)
private String stuName;
private int age;/*
- 引用数据类型的数据注入
- Spring会自动去bean工厂中找同源类型的对象进行注入,赋值给school=xxx同源类型对象
- 而我们已经使用注解创建了School对象。
- */
@Autowired
private School school;
public Student() {
System.out.println(“Student的无参构造方法执行了。。。”);
}public Student(String stuName, int age) {
System.out.println(“Student的you参构造方法执行了。。。”);
this.stuName = stuName;
this.age = age;
}@Override
public String toString() {
return “Student{” +
“stuName='” + stuName + ‘’’ +
“, age=” + age +
“, school=” + school +
‘}’;
}
}
<!--在Spring核心配置文件中添加包扫描
届时Spring就会扫描com.lin.spring.bean1这个包下有没有创建对象的注解,有则创建对象。
–>
<context:component-scan base-package=“com.lin.spring.bean1”></context:component-scan>
<context:component-scan base-package=“com.lin.spring.bean2”></context:component-scan>
@Test
public void testCreateObjectByAnno2(){
ApplicationContext ac = new ClassPathXmlApplicationContext(“applicationContext.xml”);
Student stu = (Student) ac.getBean("student");
System.out.println(stu);
}
B.@Autowired
@Qualifier(“名称”):案例:
// 创建School对象
@Component(“schoolNew”)
public class School {
// 简单类型的数据注入
@Value(“清华大学”)
private String schoolName;
@Value(“北京”)
private String address;
public School() {
System.out.println("School的无参构造器");
}
public School(String schoolName, String address) {
System.out.println("School的you参构造器");
this.schoolName = schoolName;
this.address = address;
}
@Override
public String toString() {
return "School{" +
"schoolName='" + schoolName + '\'' +
", address='" + address + '\'' +
'}';
}
}
@Component
public class Student {
@Value(“测试”)
private String stuName;
private int age;
/*
* 引用数据类型的数据注入采用@Autowired和@Qualifier("名称")注解
* Spring会自动去bean工厂中找@Qualifier("名称")指定名称的对象进行注入,赋值给school引用
* 我们已经使用注解创建了School对象。
* */
@Autowired
@Qualifier("schoolNew")
private School school;
public Student() {
System.out.println("Student的无参构造方法执行了。。。");
}
public Student(String stuName, int age) {
System.out.println("Student的you参构造方法执行了。。。");
this.stuName = stuName;
this.age = age;
}
@Override
public String toString() {
return "Student{" +
"stuName='" + stuName + '\'' +
", age=" + age +
", school=" + school +
'}';
}
}
添加包扫描的多种方式
当我们使用基于注解的IOC时,需要在applicationContext.xml文件中指定Spring要去扫描的包,这样它就会去扫描指定包下的类,看有没有要创建对象的注解。
指定包扫描的多种方式:
1)单个包扫描(推荐使用)
要创建哪些对象,就只指定这些对象所在的包。
<context:component-scan base-package=“com.bjpowernode.controller”></context:component-scan>
<context:component-scan base-package=“com.bjpowernode.service.impl”></context:component-scan>
<context:component-scan base-package=“com.bjpowernode.dao”></context:component-scan>
2)多个包扫描,多个包之间以逗号或空格或分号分隔
<context:component-scan base-package=“com.bjpowernode.controller com.bjpowernode.service ,com.bjpowernode.dao”></context:component-scan>
3)扫描根包(不推荐)
<context:component-scan base-package=“com.bjpowernode”></context:component-scan>
会降低容器启动的速度,导致多做无用功.
三层架构项目的分析
界面层view和controller
业务逻辑层service
数据持久层bean和dao/mapper
普通的三层项目
/**
-
三层架构之界面层的controller
-
@author shkstart
-
@create 2022-06-06 9:42
*/
public class UserServlet {
// 面向接口编程
private UserService userService = new UserServiceImpl();public void doPost(){
System.out.println(“获取要注册的用户信息”);
Integer newUId = 1;
String newUName = “纯氧”;
User newUser = new User(newUId, newUName);// 调用业务逻辑层进行用户注册的业务处理 int result = userService.userRegister(newUser); if (result == 1) { System.out.println(newUName + "注册成功"); }
}
}
/** -
三层架构之业务逻辑层
-
@author shkstart
-
@create 2022-06-06 9:47
*/
public class UserServiceImpl implements UserService{
private UserMapper userMapper = new UserMapperImpl();@Override
public int userRegister(User user) {
// 调用数据持久层将注册的信息插入到底层数据库return userMapper.insertUser(user);
}
}
/** -
三层架构之数据访问层
-
@author shkstart
-
@create 2022-06-06 9:49
*/
public class UserMapperImpl implements UserMapper{
@Override
public int insertUser(User user) {
System.out.println(user.getuName() + “用户信息已经保存到数据库中”);return 1;
}
}
测试:@Test
public void test1(){
UserServlet userServlet = new UserServlet();
userServlet.doPost();
}
基于xml的IOC三层架构项目(Spring接管对象)
由Spring管理三层架构项目中的对象创建,包含界面层、业务逻辑层和数据访问层的对象创建。要创建的对象以及依赖信息在applicationContext.xml文件中配置。
<!--创建项目中需要的各种对象-->
<!--创建数据访问层的对象-->
<bean id="userMapperBySpring" class="com.lin.sanceng.mapper.UserMapperImpl"></bean>
<!--创建业务逻辑层的对象,实现类对象赋给接口引用(多态)-->
<bean id="userServiceBySpring" class="com.lin.sanceng.service.UserServiceImpl">
<!--注入业务逻辑层对象所需的依赖-->
<property name="userMapper" ref="userMapperBySpring"></property>
</bean>
<!--创建界面层的对象-->
<bean id="uerController" class="com.lin.sanceng.controller.UserServlet">
<!--注入userServlet对象所需的依赖,实现类对象赋给接口引用(多态)-->
<property name="userService" ref="userServiceBySpring"></property>
</bean>
/** * 三层架构之数据访问层 * * @author shkstart * @create 2022-06-06 9:49 */ public class UserMapperImpl implements UserMapper{ @Override public int insertUser(User user) { System.out.println(user.getuName() + "用户信息已经保存到数据库中");
return 1;
}
}
/**
-
三层架构之业务逻辑层
-
@author shkstart
-
@create 2022-06-06 9:47
*/
public class UserServiceImpl implements UserService{// 这个数据访问层的对象userMapper交给Spring创建了,因此这个依赖也交给Spring注入(需要提供setXXX方法)
// private UserMapper userMapper = new UserMapperImpl();
private UserMapper userMapper;public void setUserMapper(UserMapper userMapper) {
this.userMapper = userMapper;
}@Override
public int userRegister(User user) {
// 调用数据持久层将注册的信息插入到底层数据库return userMapper.insertUser(user);
}
}
/** -
三层架构之界面层的controller
-
@author shkstart
-
@create 2022-06-06 9:42
*/
public class UserServlet {
// 面向接口编程
// 这个业务逻辑层的对象userService交给Spring创建了,因此这个依赖也交给Spring注入(需要提供setXXX方法)
// private UserService userService = new UserServiceImpl();private UserService userService;
public void setUserService(UserService userService) {
this.userService = userService;
}public void doPost(){
System.out.println(“获取要注册的用户信息”);
Integer newUId = 1;
String newUName = “纯氧”;
User newUser = new User(newUId, newUName);// 调用业务逻辑层进行用户注册的业务处理 int result = userService.userRegister(newUser); if (result == 1) { System.out.println(newUName + "注册成功"); }
}
}
测试:@Test
public void test1(){
// 创建Spring容器对象同时启动容器,创建里面的所有对象
ApplicationContext ac = new ClassPathXmlApplicationContext(“applicationContext.xml”);// 从Spring容器中获取界面控制层的userController对象 UserServlet uerController = (UserServlet) ac.getBean("uerController"); uerController.doPost();
}
基于注解的IOC三层架构项目(Spring接管对象)
由Spring管理三层架构项目中的对象创建,包含界面层、业务逻辑层和数据访问层的对象创建。要创建的对象以及依赖信息都直接使用注解配置即可。
/**
-
三层架构之数据访问层
-
@author shkstart
-
@create 2022-06-06 9:49
*/
@Repository // 此注解说明交给Spring用来创建数据访问层的对象
public class UserMapperImpl implements UserMapper{
@Override
public int insertUser(User user) {
System.out.println(user.getuName() + “用户信息已经保存到数据库中”);return 1;
}
public UserMapperImpl() {
System.out.println(“UserMapperImpl的无参构造方法。。。。”);
}
}
/** -
三层架构之业务逻辑层
-
@author shkstart
-
@create 2022-06-06 9:47
/
@Service // 此注解说明交给Spring来专门创建Service业务逻辑层的对象
public class UserServiceImpl implements UserService{
/- 依赖注入也交给Spring
- */
@Autowired
private UserMapper userMapper;
// private UserMapper userMapper = new UserMapperImpl();
@Override
public int userRegister(User user) {
// 调用数据持久层将注册的信息插入到底层数据库return userMapper.insertUser(user);
}
public UserServiceImpl() {
System.out.println(“UserServiceImpl的无参构造方法。。。。”);
}
}
/** -
三层架构之界面层的controller
-
@author shkstart
-
@create 2022-06-06 9:42
*/
@Controller // 此注解说明交给Spring专门用来创建界面层之控制层的对象
public class UserServlet {
// 面向接口编程@Autowired
private UserService userService;
// private UserService userService = new UserServiceImpl();public void doPost(){
System.out.println(“获取要注册的用户信息”);
Integer newUId = 1;
String newUName = “纯氧”;
User newUser = new User(newUId, newUName);// 调用业务逻辑层进行用户注册的业务处理 int result = userService.userRegister(newUser); if (result == 1) { System.out.println(newUName + "注册成功"); }
}
public UserServlet() {
System.out.println(“UserServlet的无参构造方法。。。。”);
}
}
<!--指明要Spring扫描的包-->
<context:component-scan base-package="com.lin.sanceng"></context:component-scan>
测试:
@Test
public void test1(){
// 创建Spring容器的对象同时启动容器
ApplicationContext ac = new ClassPathXmlApplicationContext("applicationContext.xml");
// 取出容器中的UserServlet对象,调用方法执行业务
UserServlet userServlet = (UserServlet) ac.getBean("userServlet");
userServlet.doPost();
}
Spring核心配置文件的拆分策略
当项目越来越大,需要多人合作开发,一个配置就存在很大隐患.
拆分配置文件的策略
A.按层拆
三层之控制层:applicationContext_controller.xml
三层之业务逻辑层:applicationContext_service.xml
三层之数据访问层:applicationContext_mapper.xml
B.按功能拆
和用户相关的功能:applicationContext_users.xml
<bean id="uController" class="com.bjpowernode.controller.UsersController">
<bean id="uService" class="com.bjpowernode.controller.UsersService">
<bean id="uMapper" class="com.bjpowernode.controller.UsersMapper">
和图书相关的功能:applicationContext_book.xml
<bean id="bController" class="com.bjpowernode.controller.BookController">
<bean id="bService" class="com.bjpowernode.controller.BookService">
<bean id="bMapper" class="com.bjpowernode.controller.BookMapper">
拆分基于注解的IOC的三层项目的配置文件
<?xml version="1.0" encoding="UTF-8"?><!--指明要Spring扫描的包-->
<!--数据访问层对象所在的包-->
<context:component-scan base-package="com.lin.sanceng.mapper"></context:component-scan>
<?xml version="1.0" encoding="UTF-8"?>
<!--指明要Spring扫描的包-->
<!--业务逻辑层对象所在的包-->
<context:component-scan base-package="com.lin.sanceng.service"></context:component-scan>
<?xml version="1.0" encoding="UTF-8"?>
<!--指明要Spring扫描的包-->
<!--界面层之控制层中的对象所在的包-->
<context:component-scan base-package="com.lin.sanceng.controller"></context:component-scan>
<?xml version="1.0" encoding="UTF-8"?>
<!--整合所有的Spring核心配置文件-->
<!--一个一个导入整合-->
<!--批量导入-->
<import resource="applicationContext_*.xml"></import>
// 创建Spring容器的对象同时启动容器,只需要将那个整合的配置文件作为参数就可以。 ApplicationContext ac = new ClassPathXmlApplicationContext("total.xml"); 三、面向切面编程AOP AOP(Aspect Orient Programming),面向切面编程。
切面:存放公共的,通用的,一些固定的,重复的代码。
面向切面编程开发就是将公共的,通用的,固定的,重复的代码单独开发在一个切面中,在需要的时候再从切面中反织回去。底层的原理是动态代理.
/**
-
事务切面,做一些和事务相关的重复操作
-
事务的处理,就都是这些操作:事务开启、事务提交、事务回滚。
所以可以将事务的处理单独开发在一个切面中。 -
@author shkstart
-
@create 2022-06-09 16:09
*/
public class TransactionAspect implements Aspect{@Override
public void before() {
System.out.println(“事务开启…”);
}@Override
public void after() {
System.out.println(“事务提交…”);
}@Override
public void exception() {
System.out.println(“事务回滚…”);
}
}
/** -
日志切面
-
做一些和日志相关的重复操作
-
@author shkstart
-
@create 2022-06-09 16:28
*/
public class LogAspect implements Aspect{
@Override
public void before() {
System.out.println(“日志输出…”);
}
}
public class ProxyFactory {
/**
* 使用动态代理,获取代理对象。
*
* @param target 被代理对象,目标业务对象
* @param aspect 切面对象。需要哪个切面中的功能就传哪个切面的实现类(反织)
* @return
*/
public static Object getAgent(Service target, Aspect aspect){
Object proxyInstance = Proxy.newProxyInstance(
// 类加载器,用于加载代理类对象
target.getClass().getClassLoader(),
// 目标对象就是被代理对象实现的所有接口(通过反射获取)
target.getClass().getInterfaces(),
// 调用处理器(使用匿名实现类)
new InvocationHandler() {
// proxy生成的代理类对象,method代理对象调用的方法buy()show(),args方法的参数
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
Object returnValue = null;
try {
// 切面功能
aspect.before();
// 执行被代理对象的业务方法
returnValue = method.invoke(target, args);
// 切面功能
aspect.after();
} catch (Exception e) {
aspect.exception();
}
return returnValue;// 业务方法的返回值
}
}
);
return proxyInstance;
}
}
手写AOP框架
业务:图书购买业务
切面:事务(此处事务的处理代码就是重复的?)
1)第一个版本:业务和切面紧耦合在一起,没有拆分.
2)第二个版本:使用子类代理的方式拆分业务和切面.
3)第三个版本:使用静态代理拆分业务和切面。业务和业务接口已拆分。此时切面紧耦合在业务中.
4)第四个版本:使用静态代理拆分业务和业务接口,切面和切面接口.
5)第五个版本:使用动态代理完成第四个版本的优化.
手写AOP框架第一个版本
package com.lin.spring.service;
/**
-
此时图书购买业务和事务切面耦合在一起.
-
@author shkstart
-
@create 2022-06-06 22:22
*/
public class BookServiceImpl {// 购买图书业务
public void buyBook(){
try {
System.out.println(“事务开启…”);System.out.println("购买图书功能代码实现....."); System.out.println("事务提交........."); } catch (Exception e) { System.out.println("事务回滚........."); e.printStackTrace(); }
}
}
手写AOP框架第二个版本
/** -
手写AOP框架的第二个版本
-
使用子类代理的方式拆分图书业务和事务切面,解耦合
-
@author shkstart
-
@create 2022-06-06 22:22
*/
public class BookServiceImpl {// 在父类中只有购买图书业务
public void buyBook(){
System.out.println(“购买图书功能代码实现…”);}
}
/** -
子类代理类,代理类负责做一些公共的工作,实际的核心工作还是由被代理对象自己做
-
这里,代理类负责事务的切面
-
@author shkstart
-
@create 2022-06-07 15:54
*/
public class SubBookServiceImpl extends BookServiceImpl{@Override
public void buyBook() {
try {
// 事务切面
System.out.println(“事务开启…”);// 调用父类被代理对象执行购买图书业务 super.buyBook(); // 事务切面 System.out.println("事务提交......."); } catch (Exception e) { System.out.println("事务回滚.........."); e.printStackTrace(); }
}
}
@Test
public void test1(){
BookServiceImpl service = new SubBookServiceImpl();service.buyBook();
}
手写AOP框架第三个版本
以上版本业务固定了。第三个版本,我们采用业务接口,在接口中定义业务的方法。这样只要面向业务接口编程,只需切换业务实现类对象,即可切换业务,让业务灵活起来
public interface Service {
// 购买业务
void buy();
}
/**
-
手写AOP框架的第三个版本
-
目标对象. 也就是被代理对象
-
实现具体业务
-
@author shkstart
-
@create 2022-06-07 16:26
*/
// 图书业务
public class BookService implements Service {@Override
public void buy() {
System.out.println(“图书购买业务实现…”);
}
}
/** -
代理类.
-
@author shkstart
-
@create 2022-06-07 16:28
*/
public class Agent implements Service {
// 要被代理的目标对象, 设置成接口,只要切换目标对象即可实现灵活切换.
private Service target;// 在代理对象创建的时候传入 被代理对象
public Agent(Service target){
this.target = target;
}// 代理购买业务实现,
// 其实是代理做一些比如事务的开启、事务的提交以及事务的回滚一些不用被代理对象做的,
// 实际业务还是要被代理对象自己做.
@Override
public void buy() {
// 事务切面
// 此时有个问题,这个切面是写死的,要想切换切面为日志或者权限验证都要手动修改代码.
System.out.println(“事务开启…”);// 实际业务功能 target.buy(); // 事务切面 System.out.println("事务提交..............");
}
}
@Test
public void test1(){
// 传入被代理对象
// Service agent = new Agent(new BookService());
// 只要切换被代理对象,即可代理其他目标对象.
Service agent = new Agent(new ClothService());
agent.buy();
}
手写AOP框架第四个版本
第三个版本,有个问题,这个切面是写死的,要想切换切面为日志或者权限验证都要手动修改代码。
可以就切面定义为一个接口,这样就可以提供不同的切面实现类,实现不同的切面功能。
/**
-
切面,定义各种重复的操作.
-
@author shkstart
-
@create 2022-06-09 16:07
*/
public interface Aspect {// 在主业务执行之前执行
void before();// 在主业务执行之后执行
void after();// 在主业务执行出错后执行
void exception();
}
/** -
事务切面,做一些和事务相关的重复操作
-
@author shkstart
-
@create 2022-06-09 16:09
*/
public class TransactionAspect implements Aspect{@Override
public void before() {
System.out.println(“事务开启…”);
}@Override
public void after() {
System.out.println(“事务提交…”);
}@Override
public void exception() {
System.out.println(“事务回滚…”);
}
}
/** -
日志切面
-
做一些和日志相关的重复操作
-
@author shkstart
-
@create 2022-06-09 16:28
*/
public class LogAspect implements Aspect{
@Override
public void before() {
System.out.println(“日志输出…”);
}@Override
public void after() {
}@Override
public void exception() {
}
}
/** -
代理类.
-
@author shkstart
-
@create 2022-06-07 16:28
*/
public class Agent implements Service{
// 要被代理的目标对象, 设置成接口,只要切换目标对象即可实现灵活切换.
private Service target;// 切面,设置成接口,只要切换切面实现对象即可实现灵活切换想要用的切面,不用写死代码.
private Aspect aspect;// 在代理对象创建的时候传入 被代理对象 和 切面对象
public Agent(Service target,Aspect aspect){
this.target = target;
this.aspect = aspect;
}@Override
public void buy() {
try {
// 切面功能
aspect.before();// 实际业务功能 target.buy(); // 切面功能 aspect.after(); } catch (Exception e) { aspect.exception(); }
}
}
一个业务中有多个切面功能
@Test
public void test3(){
Service agent = new Agent(new BookService(),new TransactionAspect());
Service agent2 = new Agent(agent,new LogAspect());
agent2.buy();
}
手写AOP框架第五个版本
实现传哪个被代理类对象就创建相应的代理类对象,并返回
public class ProxyFactory {
/**
* 使用动态代理,获取代理对象。
*
* @param target 被代理对象,目标业务对象
* @param aspect 切面对象。
* @return
*/
public static Object getAgent(Service target, Aspect aspect){
Object proxyInstance = Proxy.newProxyInstance(
// 类加载器,用于加载代理类对象
target.getClass().getClassLoader(),
// 目标对象就是被代理对象实现的所有接口(通过反射获取)
target.getClass().getInterfaces(),
// 调用处理器(使用匿名实现类)
new InvocationHandler() {
// proxy生成的代理类对象,method代理对象调用的方法buy()show(),args方法的参数
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
Object returnValue = null;
try {
// 切面功能
aspect.before();
// 执行被代理对象的业务方法
returnValue = method.invoke(target, args);
// 切面功能
aspect.after();
} catch (Exception e) {
aspect.exception();
}
return returnValue;// 业务方法的返回值
}
}
);
return proxyInstance;
}
}
@Test
public void test(){
// Service agent = (Service) ProxyFactory.getAgent(new ClothService(), new TransactionAspect());
// 只需切换业务实现类 以及 切面实现类,即可完成自己想要的功能
Service agent = (Service) ProxyFactory.getAgent(new BookService(), new TransactionAspect());
// 可以任意切换业务功能
agent.buy();
String show = agent.show();
System.out.println(show);
/*Service agent2 = (Service) ProxyFactory.getAgent(agent, new LogAspect());
agent2.buy();*/
}
Spring原生的AOP
这个了解即可,重要的是AspectJ框架。
Spring支持AOP的编程,常用的有以下几种:
1)Before通知:在目标方法被调用前调用,涉及接口org.springframework.aop.MethodBeforeAdvice;
2)After通知:在目标方法被调用后调用,涉及接口为org.springframework.aop.AfterReturningAdvice;
3)Throws通知:目标方法抛出异常时调用,涉及接口org.springframework.aop.ThrowsAdvice;
4)Around通知:拦截对目标对象方法调用,涉及接口为org.aopalliance.intercept.MethodInterceptor。
<!--创建业务对象-->
<bean name="BookServiceImpl" class="com.lin.spring.proxy3.BookService"></bean>
<!--创建切面对象-->
<bean name="LogAspect" class="com.lin.spring.proxy3.aspect.LogAspect"></bean>
<!--绑定业务和切面-->
<!--spring中的通过动态代理创建代理对象的工厂-->
<bean name="bookServiceAgent" class="org.springframework.aop.framework.ProxyFactoryBean">
<!--配置业务接口-->
<property name="interfaces" value="com.lin.spring.proxy3.Service"></property>
<!--配置切面-->
<property name="interceptorNames">
<list>
<!--上面那个切面对象-->
<value>LogAspect</value>
</list>
</property>
<!--目标对象,就是被代理类对象-->
<property name="target" ref="BookServiceImpl"></property>
</bean>
/** * 日志切面 * 做一些和日志相关的重复操作 * * @author shkstart * @create 2022-06-09 16:28 */ public class LogAspect implements MethodBeforeAdvice {
/**
*
* @param method 目标对象方法
* @param objects 方法参数
* @param o 目标对象
* @throws Throwable
*/
@Override
public void before(Method method, Object[] objects, Object o) throws Throwable {
System.out.println("日志输出" + method.getName());
System.out.println(objects);
System.out.println(o);
}
}
@Test
public void test1(){
ApplicationContext ac = new ClassPathXmlApplicationContext(“applicationContext.xml”);
Service bookServiceAgent = (Service) ac.getBean("bookServiceAgent");
bookServiceAgent.buy();
}
AOP相关术语
1)切面:泛指交叉业务逻辑,或是公共的,通用的业务。上例中的事务处理、日志处理以及权限验证就可以理解为切面。
2)连接点:就是目标方法,因为在目标方法中要实现目标方法的功能和切面功能。需要将切面功能和目标方法进行连接共同完成功能。连接点指可以被切面织入的具体方法。通常业务接口中的方法均为连接点。
3)切入点(Pointcut):一个连接点或多个连接点的集合。指定切入的位置,多个连接点构成切入点。切入点可以是一个目标方法,也可以是一个类中的所有方法,可以是某个包下的所有类中的目标方法。
4)目标对象:操作谁,谁就是目标对象。(比如BookServiceImpl对象)
5)通知(Advice):表示切面功能的执行时间,来指定切入的时机。是在目标方法执行前(前置通知)还是执行后(后置通知)还是出错时,还是环绕目标方法(环绕通知)切入切面功能。
面向切面编程的AspectJ框架
AspectJ 是一个优秀面向切面的框架,它扩展了 Java 语言,提供了强大的切面实现,优化了面向切面编程的步骤。它因为是基于java语言开发的,所以无缝扩展.easy to learn and use(易学易用).
Spring看它好用就把它整合进来了。
AspectJ常见通知类型
(1)前置通知@Before:在目标方法执行之前执行切面功能。
(2)后置通知@AfterReturning:在目标方法正常执行之后返回值后执行切面功能。
(3)环绕通知@Around:方法执行前后分别执行切面功能。
(4)最终通知@After:不管目标方法有没有执行完成都会切入切面功能。
(5)定义切入点@Pointcut(了解)
AspectJ 的切入点表达式(掌握)
切入点表达式要匹配的对象就是目标方法的方法名。
表达式:public void main(参数)
execution(访问权限 方法返回值 方法声明(参数) 异常类型)
简化版:
execution(方法返回值 方法声明(参数) )
举例说明:
execution(public * *(…))
公共的,任意返回值类型且方法名任意,方法参数任意的方法。就是任意的公共方法。
execution(* set*(…))
任意返回值类型,方法名以set开头且任意参数的方法。切入点就是任意setXXX方法。
execution(* com.xyz.service.impl..(…))
任意返回值类型,com.xyz.service.impl包下的任意类中的任意方法且参数也是任意的方法。切入点是:com.xyz.service.impl包下的任意类中的任意方法。
1.AspectJ的前置通知@Before
在目标方法执行前切入切面功能。在切面方法中不可以获得目标方法的返回值,因为是在目标方法执行前就执行了,只能得到目标方法的签名。
方法的签名?方法的访问权限、返回值、方法名、方法的参数
1)先引入依赖:
<dependencies>
<!--引入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>
</dependencies>
2)创建业务接口和业务实现类
/**
-
定义各种业务功能
-
@author shkstart
-
@create 2022-06-11 22:31
*/
public interface SomeService {public String showName(String name,int age);
}
/** -
具体业务实现类
-
@author shkstart
-
@create 2022-06-11 22:32
*/
public class UserServiceImpl implements SomeService{
@Override
public String showName(String name, int age) {
System.out.println(“showName方法执行”);
return name;
}
}
3)创建切面实现类
@Aspect // 指明这是一个切面,交给AspectJ框架管理,扫描到这个注解就会知道这个是一个切面类然后进行切面的操作
@Component // 创建切面对象
public class LogAspect {
// 前置通知,在目标方法执行之前执行。
/*
* 切面方法,前置通知
* 1)访问权限是public
* 2)方法的返回值是void
* 3)方法名称自定义
* 4)方法没有参数,如果有也只能是JoinPoint类型
* 5)必须使用@Before注解来声明切入的时机是前置通知和切入点
* 参数:value 指定切入点表达式,
* 这里是切入点就是一个连接点com.lin.aspectj1.UserServiceImpl.showName(方法名)
* 在此showName方法执行前执行这个前置通知。
* */
@Before(value = "execution(public String com.lin.aspectj1.UserServiceImpl.showName(String,int ))")
public void printLog(JoinPoint jp){
System.out.println("打印日志信息");
System.out.println("目标方法的签名:" + jp.getSignature());
System.out.println("目标方法的参数:" + Arrays.toString(jp.getArgs()));
}
}
4)在applicationContext.xml文件中绑定业务和切面
<!--创建对象改为注解方式
<!–创建业务对象–>
<bean id="UserService" class="com.lin.aspectj.UserServiceImpl"></bean>
<!–创建切面对象–>
<bean id="LogAspect" class="com.lin.aspectj.LogAspect"></bean>-->
<!--添加包扫描-->
<context:component-scan base-package="com.lin.aspectj"></context:component-scan>
<!--
当业务实现类没有父接口时,可以使用CGLib子类代理,然后用实现类接代理对象。
SomeServiceImpl service = (SomeServiceImpl)ac.getBean("someServiceImpl");
当业务实现类有父接口时,就直接用jdk动态代理,用接口接收代理对象。
SomeService service = (SomeService)ac.getBean("someServiceImpl");
-->
<!--绑定业务对象 和 切面对象-->
<!--底层自动通过jdk动态代理创建业务对象 + 切面对象 的代理对象(只能用接口接收)-->
<aop:aspectj-autoproxy></aop:aspectj-autoproxy>
<!--设置为CGLib子类代理,可以使用接口和实现类接代理对象-->
测试:
@Test
public void test(){
ApplicationContext ac = new ClassPathXmlApplicationContext("aspectj1/applicationContext.xml");
SomeService userService = (SomeService) ac.getBean("UserService");
// 底层aspectj通过动态代理将业务对象的代理对象创建了
System.out.println(userService.getClass());// class com.sun.proxy.$Proxy12
// 这个业务方法绑定了一个前置通知
userService.showName("dfd",34);
}
2.AspectJ的前置通知@AfterReturning
1、在目标方法正常执行之后执行,目标方法没有发生异常。由于是目标方法之后执行,所以可以获取到目标方法的返回值。被注解为后置通知的方法,除了可以包含 JoinPoint 参数外,还可以包含用于接收返回值的变量。该变量最好为 Object 类型,因为目标方法的返回值可能是任何类型。
2、
/**
- 后置通知的方法的规范
- 1)访问权限是public
- 2)方法没有返回值void
- 3)方法名称自定义
- 4)方法有参数(可以是JoinPint获取目标方法签名 和 Object获取目标方法的返回值)
- (也可以没有参数,如果目标方法没有返回值,则可以写无参的方法,但一般会写有参,这样可以处理无参可以处理有参),
- 这个切面方法的参数就是目标方法的返回值,目标方法执行完成后将返回值赋给Object goalMethodReturn
- 5)使用@AfterReturning注解表明是后置通知
- 参数:
-
value:指定切入点表达式
-
returning:指定目标方法的返回值的名称,则名称必须与切面方法的参数名称一致.
*/
后置方法获取到目标方法的返回值后可以根据情况修改:
如果如果目标方法的返回值类型是8中基本类型+String,则不能修改(传递的只是数据值)
如果如果目标方法的返回值类型是引用数据类型,则可以修改(传递的是地址值)
切面:
@Aspect
@Component
public class MyAspect {
@Before(value = "execution(* com.lin.aspectj.UserServiceImpl.*(..))")
public void MyBefore(){}
// public String doSome(String name, int age) 目标方法
@AfterReturning(returning = "goalMethodReturn",value = "execution(String com.lin.aspectj2.UserService.doSome(String,int)),")
public void MyAfterReturning(Object goalMethodReturn){
System.out.println("目标方法执行完毕,接入后置通知方法....");
// 试图修改目标方法的返回值,此时返回值类型是String,修改失败(数据值传递)
goalMethodReturn = "修改返回值";
System.out.println("后置通知方法接收到目标方法的返回值:" + goalMethodReturn);
}
/**
* 后置通知的方法的规范
* 1)访问权限是public
*
* 2)方法没有返回值void
*
* 3)方法名称自定义
*
* 4)方法有参数(可以是JoinPint获取目标方法签名 和 Object获取目标方法的返回值)
* (也可以没有参数,如果目标方法没有返回值,则可以写无参的方法,但一般会写有参,这样可以处理无参可以处理有参),
* 这个切面方法的参数就是目标方法的返回值,目标方法执行完成后将返回值赋给Object goalMethodReturn
*
* 5)使用@AfterReturning注解表明是后置通知
* 参数:
* value:指定切入点表达式
* returning:指定目标方法的返回值的名称,则名称必须与切面方法的参数名称一致.
*/
@AfterReturning(value = "execution(* com.lin.aspectj2.*.get*(..))",returning = "goalMethodReturn")
public void myAfterReturning2(Object goalMethodReturn){
System.out.println("目标方法执行完毕,接入后置通知方法........");
// 试图修改目标方法的返回值,此时返回值类型是User引用数据类型,修改成功(地址值传递)
if (goalMethodReturn instanceof User){
((User) goalMethodReturn).setName("修改过的");
}
System.out.println("在后置通知方法中将目标方法的返回值改为:" + goalMethodReturn);
}
}
业务方法:
@Service
public class UserService implements SomeService{
@Override
public String doSome(String name, int age) {
System.out.println("用户" + name + "今年" + age + "了.");
return name;
}
@Override
public User getUser(String name, int age) {
System.out.println("目标方法getUser()执行...");
return new User(name,age);
}
}
xml:
<!--添加包扫描-->
<context:component-scan base-package="com.lin.aspectj2"></context:component-scan>
<!--声明自动代理,底层动态代理生成代理对象-->
<aop:aspectj-autoproxy></aop:aspectj-autoproxy>
测试:
@Test
public void test(){
ApplicationContext ac = new ClassPathXmlApplicationContext("aspectj2/applicationContext.xml");
SomeService userService = (SomeService) ac.getBean("userService");
String name = userService.doSome("lin", 21);
System.out.println("在测试中,目标方法的返回值:" + name);
}
@Test
public void test2(){
ApplicationContext ac = new ClassPathXmlApplicationContext("aspectj2/applicationContext.xml");
SomeService userService = (SomeService) ac.getBean("userService");
User user = userService.getUser("lin", 21);
System.out.println("在测试中,目标方法的返回值:" + user);
}
3.AspectJ的环绕通知@Around
在目标方法执行之前之后执行。
它是通过拦截目标方法的方式 ,在目标方法前后增强功能的通知。它是功能最强大的通知,一般事务使用此通知。它可以轻易的改变目标方法的返回值。
@Aspect// 交给AspectJ框架管理切面
@Component // 交给Spring创建对象
public class MyAspect {
/**
* 环绕通知方法的规范
* 1)访问权限是public
* 2)切面方法有返回值,此返回值就是目标方法的返回值
* 3)方法名称自定义
* 4)方法有参数,此参数就是目标方法
* 5)回避异常Throwable
* 6)使用@Around注解声明是环绕通知
* 参数:
* value:指定切入点表达式
*/
/**
*
* @param pjp 目标方法,可以理解为目标方法被拦截下来了。
* @return 目标方法的返回值
*/
@Around(value = "execution(String com.lin.aspectj3.UserService.doSome(String))")
public Object myAround(ProceedingJoinPoint pjp) throws Throwable {
// 切面功能1
System.out.println("事务开启" + Arrays.toString(pjp.getArgs()));
// 业务功能执行(反射),pjp.getArgs()目标方法的参数
Object goalMethodReturn = pjp.proceed(pjp.getArgs());//method.invoke();
// 切面功能2
System.out.println("事务提交");
return goalMethodReturn;
}
}
@Test
public void test(){
ApplicationContext ac = new ClassPathXmlApplicationContext(“aspectj3/applicationContext.xml”);
// 获取代理类对象
SomeService userService = (SomeService) ac.getBean("userService");
System.out.println(userService.getClass());// class com.sun.proxy.$Proxy18
String s = userService.doSome("lin");
System.out.println("测试方法中,目标方法的返回值:" + s);
}
4.AspectJ的最终通知@After
无论目标方法是否可以正常执行完毕,最终通知的方法都会在目标方法后执行。
可以理解为try catch finally中的finally。
@Aspect
@Component
public class MyAspect {
/**
* 最终通知方法的规范
* 1)访问权限是public
* 2)方法没有返回值
* 3)方法名称自定义
* 4)方法没有参数,如果有也只能是JoinPoint
* 5)使用@After注解表明是最终通知
* 参数:
* value:指定切入点表达式
/
@After("execution( com.lin.aspectj4..(…))")
public void myAfter(){
System.out.println(“最终通知:释放资源…”);
}
5、@PointCut给切入点表达式起别名
可以给切入点表达式起别名,相当于起一个变量。
起别名前:
@Aspect
@Component
public class MyAspect {
/**
* 最终通知方法的规范
* 1)访问权限是public
* 2)方法没有返回值
* 3)方法名称自定义
* 4)方法没有参数,如果有也只能是JoinPoint
* 5)使用@After注解表明是最终通知
* 参数:
* value:指定切入点表达式
/
@After("execution( com.lin.aspectj4..(…))")
public void myAfter(){
System.out.println(“最终通知功能:释放资源…”);
}
@Before("execution(* com.lin.aspectj4.*.*(..))")
public void myBefore(){
System.out.println("前置通知功能:日志输出........");
}
@AfterReturning(value = "execution(* com.lin.aspectj4.*.*(..))",returning = "goalReturn")
public void myAfterReturning(Object goalReturn){
System.out.println("后置通知功能:日志输出........");
}
@Around(value = "execution(* com.lin.aspectj4.*.*(..))")
public Object myAround(ProceedingJoinPoint pjp) throws Throwable {
System.out.println("环绕通知中的前置通知功能:事务开启...........");
Object re = pjp.proceed(pjp.getArgs());
System.out.println("环绕通知中的后置通知功能:事务提交...........");
return re;
}
// 给这个切入点execution(* com.lin.aspectj4.*.*(..))起别名mycut
@Pointcut("execution(* com.lin.aspectj4.*.*(..))")
public void mycut(){}
}
起别名后:
@Aspect
@Component
public class MyAspect {
/**
* 最终通知方法的规范
* 1)访问权限是public
* 2)方法没有返回值
* 3)方法名称自定义
* 4)方法没有参数,如果有也只能是JoinPoint
* 5)使用@After注解表明是最终通知
* 参数:
* value:指定切入点表达式
*/
@After(“mycut()”)
public void myAfter(){
System.out.println(“最终通知功能:释放资源…”);
}
@Before("mycut()")
public void myBefore(){
System.out.println("前置通知功能:日志输出........");
}
@AfterReturning(value = "mycut()",returning = "goalReturn")
public void myAfterReturning(Object goalReturn){
System.out.println("后置通知功能:日志输出........");
}
@Around(value = "mycut()")
public Object myAround(ProceedingJoinPoint pjp) throws Throwable {
System.out.println("环绕通知中的前置通知功能:事务开启...........");
Object re = pjp.proceed(pjp.getArgs());
System.out.println("环绕通知中的后置通知功能:事务提交...........");
return re;
}
// 给这个切入点execution(* com.lin.aspectj4.*.*(..))起别名mycut
@Pointcut("execution(* com.lin.aspectj4.*.*(..))")
public void mycut(){}
}
四、Spring整合MyBatis
Spring看mybatis好用,就把Mybatis整合进来了,而且优化了很多操作。
比如自己创建Mapper接口对应的实现类对象。
/*
- 1、之前MyBatis获取UserMapper实现类的方式:
- InputStream is = Resources.getResourceAsStream(“mybatis-config.xml”);
- SqlSessionFactory ssf = new SqlSessionFactoryBuilder().build(is);
- SqlSession sqlSession = ssf.openSession(true);
- // 底层通过动态代理的方式获取UserMapper接口的实现类
- UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
- 2、现在只需要使用@Autowired注解,交给Spring的IOC思想,创建Mapper接口的实现类对象就交给Spring。
- UserMapper接口对应的动态代理的实现类对象,spring底层已经帮我们创建完了
- */
D:\workspaces\maven-workspace\ideaSpace\Spring\Spring_009_sm
整合步骤:
建表
新建maven项目Spring_009_sm
修改pom.xml文件,添加相关的依赖(使用老师提供)
添加MyBatis相应的模板(mybatis-config.xml和XXXMapper.xml文件)
添加SqlMapConfig.xml文件(MyBatis核心配置文件),并拷贝jdbc.propertiest属性文件(连接数据库的四个基本元素)到resources目录下
添加applicationContext_mapper.xml(数据访问层配置文件)
添加applicationContext_service.xml(业务逻辑层控制文件)
添加Users实体类,Accounts实体类
添加mapper包,添加UsersMapper接口和UsersMapper.xml文件并开发
添加service包,添加UsersService接口和UsersServiceImpl实现类
添加测试类进行功能测试
建表sql:
CREATE DATABASE ssm;
use ssm;
create table users(
userid int primary key,
uname varchar(20),
upass varchar(20)
);
create table accounts(
aid int primary key,
aname varchar(20),
acontent varchar(50)
);
select userid,uname,upass from users;
select aid,aname,acontent from accounts;
pom.xml文件:
<dependencies>
<!--单元测试-->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>test</scope>
</dependency>
<!--aspectj依赖-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aspects</artifactId>
<version>5.2.5.RELEASE</version>
</dependency>
<!--spring核心ioc-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.2.5.RELEASE</version>
</dependency>
<!--做spring事务用到的-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-tx</artifactId>
<version>5.2.5.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>5.2.5.RELEASE</version>
</dependency>
<!--mybatis依赖-->
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.5.6</version>
</dependency>
<!--mybatis和spring集成的依赖-->
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis-spring</artifactId>
<version>1.3.1</version>
</dependency>
<!--mysql驱动-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.32</version>
</dependency>
<!--阿里公司的数据库连接池-->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.1.12</version>
</dependency>
</dependencies>
<build>
<!--目的是把src/main/java目录中的xml文件包含到输出结果中。输出到classes目录中-->
<resources>
<resource>
<directory>src/main/java</directory><!--所在的目录-->
<includes><!--包括目录下的.properties,.xml 文件都会扫描到-->
<include>**/*.properties</include>
<include>**/*.xml</include>
</includes>
<filtering>false</filtering>
</resource>
<resource>
<directory>src/main/resources</directory><!--所在的目录-->
<includes><!--包括目录下的.properties,.xml 文件都会扫描到-->
<include>**/*.properties</include>
<include>**/*.xml</include>
</includes>
<filtering>false</filtering>
</resource>
</resources>
</build>
jdbc.propertiest属性文件:
jdbc.driverClassName=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/ssm?useUnicode=true&characterEncoding=utf8
jdbc.username=root
jdbc.password=123
Spring接管MyBatis框架的配置文件:applicationContext_mapper.xml(数据访问层配置文件)
<!--引入properties属性配置文件,用于连接数据库的四个基本元素-->
<context:property-placeholder location="jdbc.properties"></context:property-placeholder>
<!--创建数据源对象-->
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
<!--设置数据库连接的四要素-->
<property name="driverClassName" value="${jdbc.driver}"></property>
<property name="url" value="${jdbc.url}"></property>
<property name="username" value="${jdbc.username}"></property>
<property name="password" value="${jdbc.password}"></property>
</bean>
<!--配置SqlSessionFactoryBean类,用于创建SqlSession对象
这里不设置id,是因为下面没有要引用到的地方-->
<bean class="org.mybatis.spring.SqlSessionFactoryBean">
<!--配置数据源-->
<property name="dataSource" ref="dataSource"></property>
<!--配置mybatis的核心配置文件-->
<property name="configLocation" value="mybatis-config.xml"></property>
<!--给实体类起别名-->
<property name="typeAliasesPackage" value="com.lin.sm.bean"></property>
</bean>
<!--引入映射文件,注册mapper.xml文件-->
<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
<!--指明mapper.xml所在的包-->
<property name="basePackage" value="com.lin.sm.mapper"></property>
</bean>
由于Spring接管了MyBatis框架,所以在MyBatis的核心配置文件中,就可以删除一些配置,因为Spring替MyBatis干了,但是有些活还是得由MyBatis自己干,所以要保留MyBatis的核心配置文件写一些配置由MyBatis自己做的活。
经Spring接管后的MyBatis核心配置文件(同一个配置不能有两套):
<?xml version="1.0" encoding="UTF-8" ?><!--Spring替MyBatis做了-->
<!--Spring替MyBatis做了-->
<!--Spring替MyBatis做了-->
<!--Spring替MyBatis做了-->
applicationContext_service.xml: <?xml version="1.0" encoding="UTF-8"?>
<!--添加Service的包扫描-->
<context:component-scan base-package="com.lin.sm.service.impl"></context:component-scan>
UserServiceImpl:
@Service// 交给Spring创建对象
public class UserServiceImpl implements UserService {
// Service层需要有一个数据持久层对象的依赖,这个依赖交给Spring来注入
/*
* 1、之前MyBatis获取UserMapper实现类的方式:
* InputStream is = Resources.getResourceAsStream(“mybatis-config.xml”);
* SqlSessionFactory ssf = new SqlSessionFactoryBuilder().build(is);
* SqlSession sqlSession = ssf.openSession(true);
* // 底层通过动态代理的方式获取UserMapper接口的实现类
* UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
*
* 2、现在只需要使用@Autowired注解,交给Spring的IOC思想,创建Mapper接口的实现类对象就交给Spring。
* UserMapper接口对应的动态代理的实现类对象,spring底层已经帮我们创建完了
* */
@Autowired
private UserMapper userMapper;
@Override
public int userRegister(User user) {
// 调用数据持久层的方法将用户的信息保存到底层数据库
int count = userMapper.insertUser(user);
return count;
}
}
sm整合测试:
@Test
public void testUser(){
// 创建并启动Spring容器
ApplicationContext ac = new ClassPathXmlApplicationContext("total.xml");
// 取出userServiceImpl对象
UserService userServiceImpl = (UserService) ac.getBean("userServiceImpl");
// 要注册的用户
User bat = new User(4, "bat", "234");
// 注册业务实现
int count = userServiceImpl.userRegister(bat);
System.out.println(count);
}
五、事务
由问题引出事务的重要性
@Service
public class AccountServiceImpl implements AccountService {
@Autowired
private AccountMapper accountMapper;
@Override
public int addAccount(Account account) {
int influencedRows = 0;
influencedRows = accountMapper.insertAccount(account);
System.out.println("增加账户成功,influencedRows="+influencedRows);
// 手动抛出一个异常,模拟网络出错,出错的话应该不要让数据插入到底层数据库
System.out.println(1/0);
return influencedRows;
}
}
@Test
public void testAccount(){
ApplicationContext ac = new ClassPathXmlApplicationContext(“applicationContext_service.xml”);
AccountService accountService = (AccountService) ac.getBean("accountServiceImpl");
// 要增加的账户
Account account = new Account(3, "user3", "账户正常3");
// 没有添加事务切面之前,业务对象
System.out.println(accountService.getClass());// class com.lin.sm.service.impl.AccountServiceImpl
int count = accountService.addAccount(account);
System.out.println(count);
}
从以上的运行截图,可以看出即使出错了,数据还是插入到了底层数据库,这样是不安全的。
正常是使用事务,一旦出错就回滚,撤销出错前的代码。
Spring能够自动提交事务,但是不能自动回滚,所以需要手动添加。
Spring中添加事务的两种方式
1)注解式的事务
使用@Transactional注解完成事务控制,此注解可添加到类上,则对类中所有的方法执行事务的设定。此注解也可添加到方法上,只是对此方法执行事务的处理。
此方式有缺点,事务一般用于增删改操作,有出错则回滚增删改操作。倘若一个类中有查询方法的话,@Transactional注解声明在类上的话,就不合适了,因为查询方法不需要事务,这样就得在每个增删改方法上面声明@Transactional注解,代码量太大,如果只有增删改方法,则可以用用。
2)声明式事务(必须掌握),在配置文件中添加一次,整个项目遵循事务的设定.
在Spring中通过注解添加事务
@Transactional注解参数详解
@Transactional(propagation = Propagation.REQUIRED,//事务的传播特性
noRollbackForClassName = “ArithmeticException”, //指定发生什么异常不回滚,使用的是异常的名称
noRollbackFor = ArithmeticException.class,//指定发生什么异常不回滚,使用的是异常的类型
rollbackForClassName = “”,//指定发生什么异常必须回滚
rollbackFor = ArithmeticException.class,//指定发生什么异常必须回滚
timeout = -1, //连接超时设置,默认值是-1,表示永不超时
readOnly = false, //默认是false,如果是查询操作,必须设置为true.
isolation = Isolation.DEFAULT//使用数据库默认的隔离级别
)
为什么要添加事务管理器?
JDBC的事务处理: Connection con.commit(); con.rollback();
MyBatis的事务处理: SqlSession sqlSession.commit(); sqlSession.rollback();
Hibernate的事务处理: Session session.commit(); session.rollback();
事务管理器用来生成相应技术的连接+执行语句的数据库操作对象.
1)如果使用MyBatis框架,必须使用DataSourceTransactionManager类完成处理,用于生成SqlSession对象。
2)如果使用Hibernate框架,事务管理器对象 <property name="sessionFactory" ref="sessionFactory" />
项目中的所有事务,必须添加到业务逻辑层上.
1.在Spring注解文件中添加事务处理
<!--事务处理-->
<!--1.添加事务管理器对象-->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<!--因为事务要关联数据库处理,所以得配置数据源-->
<property name="dataSource" ref="dataSource"></property>
</bean>
<!--2.添加事务的注解驱动-->
<tx:annotation-driven transaction-manager="transactionManager"></tx:annotation-driven>
2.在需要事务切面的业务逻辑的实现类上添加注解@Transactional(propagation = Propagation.REQUIRED)
@Test
public void testAccount2(){
ApplicationContext ac = new ClassPathXmlApplicationContext("applicationContext_service.xml");
AccountService accountService = (AccountService) ac.getBean("accountServiceImpl");
// 要增加的账户
Account account = new Account(4, "user4", "账户正常4");
// 添加事务切面之后,业务对象是动态代理对象
System.out.println(accountService.getClass());// com.sun.proxy.$Proxy22
int count = accountService.addAccount(account);
System.out.println(count);
}
可以看到,底层数据没有插入。添加完事务切面后,即使在执行完插入数据库的代码后,在出错后,也能回滚事务。
声明式事务的实现
Spring配置文件:
<!--添加包扫描,告知Spring哪些包中包含要交给Spring创建对象的类-->
<context:component-scan base-package="com.lin.sm.service.impl"></context:component-scan>
<!--导入Spring整合MyBatis的配置文件-->
<import resource="applicationContext_mapper.xml"></import>
<!--Spring的声明式事务处理-->
<!--1.创建事务管理器-->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<!--事务管理器需要配合数据库操作,所以需要添加数据源-->
<property name="dataSource" ref="dataSource"></property>
</bean>
<!--2.配置事务切面功能-->
<tx:advice transaction-manager="transactionManager" id="myCut">
<!--指定哪些业务方法拥有此事务切面功能-->
<tx:attributes>
<tx:method name="*insert*" propagation="REQUIRED"/><!--只要方法名包含insert的方法,都拥有传播特性属性为REQUIRED的事务-->
<tx:method name="*add*" propagation="REQUIRED"/>
<tx:method name="*Register*" propagation="REQUIRED"/>
<tx:method name="*delete*" propagation="REQUIRED"/>
<tx:method name="*remove*" propagation="REQUIRED"/>
<tx:method name="*update*" propagation="REQUIRED"/>
<tx:method name="*modify*" propagation="REQUIRED"/>
<tx:method name="*select*" read-only="true"/>
<tx:method name="*get*" read-only="true"/><!--只要方法名包含get的方法,都拥有只读属性为true的事务-->
</tx:attributes>
</tx:advice>
<!--3.将事务切面 和 切入点 绑定,指定哪些接入点拥有上面的事务切面-->
<aop:config>
<!--给切入点起个id标识-->
<aop:pointcut id="myServiceCut" expression="execution(* com.lin.sm.service.impl.*.*(..))"/>
<!--绑定-->
<aop:advisor advice-ref="myCut" pointcut-ref="myServiceCut"></aop:advisor>
</aop:config>
@Service// 交给Spring创建对象
public class UserServiceImpl implements UserService {
@Autowired
private UserMapper userMapper;
@Autowired
private AccountService accountService;
/*
* userRegister这个方法可以看成事务A,在事务A中包含事务B
* */
@Override
public int userRegister(User user) {
// 调用数据持久层的方法将用户的信息保存到底层数据库
int count = userMapper.insertUser(user);
System.out.println("用户注册成功");
// 增加账户,包含事务B addAccount
accountService.addAccount(new Account(5,"user5","用户5"));
return count;
}
}
@Service
public class AccountServiceImpl implements AccountService {
@Autowired
private AccountMapper accountMapper;
/*
* addAccount可以看成事务B
* */
@Override
public int addAccount(Account account) {
int influencedRows = 0;
influencedRows = accountMapper.insertAccount(account);
System.out.println("增加账户成功,influencedRows="+influencedRows);
// 手动抛出一个异常,模拟网络出错,出错的话应该不要让数据插入到底层数据库
System.out.println(1/0);
return influencedRows;
}
}
@Test
public void testUserByTrans(){
// 创建并启动Spring容器
ApplicationContext ac = new ClassPathXmlApplicationContext(“applicationContext_serviceTrans.xml”);
// 取出userServiceImpl对象
UserService userServiceImpl = (UserService) ac.getBean("userServiceImpl");
System.out.println(userServiceImpl.getClass());
// 要注册的用户
User bat = new User(4, "bat", "234");
// 注册业务实现
int count = userServiceImpl.userRegister(bat);
System.out.println(count);
}
可以看到,声明式事务起作用了,底层数据没有插入。即使在执行完插入数据库的代码后,在出错后,也能回滚事务。
设置事务处理的优先级
当我们使用了声明式注解,又想使用注解式注解,就是某些不想受声明式事务管理。
就可以开启注解式事务,然后将注解式事务的优先级设置的比声明式事务的高,就会优先使用注解式事务。
<tx:annotation-driven transaction-manager=“transactionManager” order=“100”></tx:annotation-driven>
aop:config
<aop:pointcut id=“myServiceCut” expression=“execution(* com.lin.sm.service.impl..(…))”/>
<aop:advisor order=“1” advice-ref=“myCut” pointcut-ref=“myServiceCut”></aop:advisor>
</aop:config>
@Service
// 不使用声明式事务,使用自己的注解式事务
@Transactional(propagation = Propagation.NOT_SUPPORTED)
public class AccountServiceImpl implements AccountService {
@Autowired
private AccountMapper accountMapper;
/*
* addAccount可以看成事务B
* */
@Override
public int addAccount(Account account) {
int influencedRows = 0;
influencedRows = accountMapper.insertAccount(account);
System.out.println("增加账户成功,influencedRows="+influencedRows);
// 手动抛出一个异常,模拟网络出错,出错的话应该不要让数据插入到底层数据库
System.out.println(1/0);
return influencedRows;
}
}
事务中指定某些错误类型不回滚
// 指定算术异常不回滚
@Transactional(propagation = Propagation.REQUIRED,noRollbackForClassName = “ArithmeticException”)
noRollbackForClassName属性:指定不回滚的异常类型名称。
@Service
// 指定算术异常不回滚
@Transactional(propagation = Propagation.REQUIRED,noRollbackForClassName = “ArithmeticException”)
public class AccountServiceImpl implements AccountService {
@Autowired
private AccountMapper accountMapper;
@Override
public int addAccount(Account account) {
int influencedRows = 0;
influencedRows = accountMapper.insertAccount(account);
System.out.println("增加账户成功,influencedRows="+influencedRows);
// 手动抛出一个异常,模拟网络出错,出错的话应该不要让数据插入到底层数据库
System.out.println(1/0);
return influencedRows;
}
@Test
public void testAccount2(){
ApplicationContext ac = new ClassPathXmlApplicationContext("applicationContext_service.xml");
AccountService accountService = (AccountService) ac.getBean("accountServiceImpl");
// 要增加的账户
Account account = new Account(4, "user4", "账户正常4");
// 添加事务切面之后,业务对象是动态代理对象
System.out.println(accountService.getClass());// com.sun.proxy.$Proxy22
int count = accountService.addAccount(account);
System.out.println(count);
}
可以看到就算出现了异常也可以规定哪个错误类型不回滚,更加灵活。
事务的隔离级别
未提交读(Read Uncommitted):允许脏读,也就是可能读取到其他会话中未提交事务修改的数据
提交读(Read Committed):只能读取到已经提交的数据。Oracle等多数数据库默认都是该级别 (不重复读)
可重复读(Repeated Read):可重复读。在同一个事务内的查询都是事务开始时刻一致的,InnoDB默认级别。在SQL标准中,该隔离级别消除了不可重复读,但是还存在幻象读,但是innoDB解决了幻读
串行读(Serializable):完全串行化的读,每次读都需要获得表级共享锁,读写相互都会阻塞
isolation = Isolation.DEFAULT//使用数据库默认的隔离级别
对于同时运行的多个事务,当这些事务同时操作同一个数据库中相同的数据时,
如果没有采取必要的隔离机制,就会导致各种并发问题,事务就相当于一个线程。(类似于Java中的线程安全问题,数据库就是共享数据):
–脏读: 对于两个事务T1, T2;T1读取了T2 还没提交的数据,读到的是T2更新的数据.
之后, 若T2 回滚, T1读取的内容就是临时且无效的。
–不可重复读: 对于两个事务T1, T2;T1 读取了一个字段, 然后T2 更新了该字段.
之后, T1再次读取同一个字段, 值就不同了。
–幻读: 对于两个事务T1, T2; T1在T2提交插入语句之前对表进行查询, 然后T2 在该表中插入了一些新的行。
之后, 如果T1 再次查询这个表, 就会多出几行,读到插入的数据,简单说就是在同一个事务T1中,两次查询的结果不一样.
6.2数据库事务的隔离性: 数据库系统必须具有隔离并发运行各个事务的能力, 使它们不会相互影响, 避免各种并发问题.
一个事务与其他事务隔离的程度称为隔离级别. 数据库规定了多种事务隔离级别,
不同隔离级别对应不同的干扰程度, 隔离级别越高, 数据一致性就越好, 但并发性越弱,效率越低
通过设置隔离级别来解决并发问题
事务的隔离级别:
脏读 不可重复读 幻读
read uncommitted: √ √ √
读取未提交的
read committed: × √ √
读取已提交的
repeatable read : × × √
可重复读
serializable: × × ×
串行化
说明:
1.打对勾√的表示不可以避免,打叉的说明此问题可以避免
2.MySQL中默认的隔离级别:repeatable read可重复读
3.Oracle中默认的隔离级别:read committed读取已提交的
Spring事务的传播特性
多个事务之间的合并,互斥等都可以通过设置事务的传播特性来解决。
常用
PROPAGATION_REQUIRED:必被包含事务(增删改必用)
PROPAGATION_REQUIRES_NEW:自己新开事务,不管之前是否有事务
PROPAGATION_SUPPORTS:支持事务,如果加入的方法有事务,则支持事务,如果没有,不单开事务
PROPAGATION_NEVER:不能运行中事务中,如果包在事务中,抛异常
PROPAGATION_NOT_SUPPORTED:不支持事务,运行在非事务的环境
不常用
PROPAGATION_MANDATORY:必须包在事务中,没有事务则抛异常
PROPAGATION_NESTED:嵌套事务
事务传播特性解析
场景三:
@Service// 交给Spring创建对象
@Transactional(propagation = Propagation.REQUIRED)// 增加事务切面
public class UserServiceImpl implements UserService {
// Service层需要有一个数据持久层对象的依赖,这个依赖交给Spring来注入
@Autowired
private UserMapper userMapper;
@Autowired
private AccountService accountService;
/*
* userRegister这个方法可以看成事务A,在事务A中包含事务B
* */
@Override
public int userRegister(User user) {
// 调用数据持久层的方法将用户的信息保存到底层数据库
int count = userMapper.insertUser(user);
// 增加账户,包含事务B addAccount
accountService.addAccount(new Account(5,"user5","用户5"));
return count;
}
}
@Service
public class AccountServiceImpl implements AccountService {
@Autowired
private AccountMapper accountMapper;
/*
* addAccount可以看成事务B
* */
@Override
public int addAccount(Account account) {
int influencedRows = 0;
influencedRows = accountMapper.insertAccount(account);
System.out.println("增加账户成功,influencedRows="+influencedRows);
// 手动抛出一个异常,模拟网络出错,出错的话应该不要让数据插入到底层数据库
System.out.println(1/0);
return influencedRows;
}
}
可以看到两个事务都没有成功插入数据,都回滚了。
事务A有事务的控制, 在accountService.addAccount(new Account(5,“user5”,“用户5”));代码处出现异常了,所以回滚了。
事务B没有事务的功能,但是还是回滚了,是因为它包含在事务A中,所以也具有了事务A的事务功能。
面试:
如何实现一个ioc容器
1、配置文件配置包扫描路径
2、递归包扫描获取class文件
3、反射,确定需要交给ioc管理的类
4、对需要注入的类进行依赖注入。
配置文件中指定需要扫描的包路径
定义一些注解,分别表示访问控制层,业务服务层,数据持久层,依赖注入注解,获取配置文件注解
从配置文件获取需要扫描的包路径,获取到当前路径下的文件信息及文件夹信息,我们将当前路径下所有以.class结尾的文件添加到一个set集合中进行存储。
遍历这个set集合,获取在雷伤有指定注解的类,并将其交给IOC容器,定义一个安全的map来存储这些对象。
遍历这个ioc容器,获取到每一个类的实例,判断里面是有依赖其他类的实例,然后递归注入。
spring是什么?
轻量级的开源框架j2ee框架,它是一个容器框架,用来装javabean(java对象),中间框架(万能胶)可以起一个连接作用,
比如说吧Strut和hibernate粘合在一起,可以让我们的企业开发更快,更简洁。
从大小与开销两方面而言spring都是轻量级的。
通过控制反转ioc的技术达到松耦合的目的
提供了面向切面编程的丰富支持,运行通过分离应用的业务逻辑与系统级服务进行内聚性的开发
包含并管理应用对象(Bean)的配置和生命周期,这个意义上是一个容器
将简单的组件配置,组合成为复杂的应用,这个意义上是一个框架。
谈谈你对ioc的理解:
可以通过三部分来讲:
容器的概念、控制反转、依赖注入
ioc容器:实际上就是一个map,里面存放的是各种对象(在xml里配置的bean节点,@repository、@Service、@Controller、@Component),在项目启动的时候会读取配置文件
的bean节点,根据全限定类名,使用反射创建对象放到map里面,扫描到打上上述注解的类,还是通过反射创建对象放到map里面。
这个时候map里面就有各种对象了,接下来我们在代码里需要用到里面的对象时,再通过di注入(autowired、resource等注解,xml里bean节点内的ref属性,项目启动的
时候会读取xml节点ref属性根据id注入,也会扫描这些注解,根据类型或id注入:id就是对象名)
控制反转:自己创建对象,交给spring对象去创建管理。
关于在spring 容器初始化 bean 和销毁前所做的操作定义方式有三种:
第一种:通过@PostConstruct 和 @PreDestroy 方法 实现初始化和销毁bean之前进行的操作
第二种是:通过 在xml中定义init-method 和 destory-method方法
第三种是: 通过bean实现InitializingBean和 DisposableBean接口
谈谈你对AOP的理解
系统是由许多不同的组件所组成的,每一个关系组件各负责一块特定功能,除了实现自身核心的功能之外,这些组件还经常承担着额外的职责,比如日志,事务管理,
和安全这样的核心服务经常融入到自身具有核心业务逻辑的组件中去。
这些系统服务经常被称为横切关注点,因为它们会跨越系统的多个组件。
当我们需要为分散的组件引入公共的行为,OOP则显得无能为力。也就是说,OOP允许你定义从上到下的关系,但并不适合定义从左到右的关系。例如日志功能。
在OOP设计中,它导致了大量代码的重复,而不利于各个模块的重用。
Aop:将程序中的交叉业务逻辑(比如日志,安全,事务等),封装成一个切面,然后注入到目标对象(具体业务逻辑)中去。
aop可以对某个对象,或者某些对象的功能进行增强,比如对象中的方法进行增强,可以执行某个方法之前额外的做一些事情,在某个方法执行之后额外的做一些事情。
BeanFactory和Application Context有什么区别?
ApplicationContext是BeanFactory的子接口
ApplicationContext提供了更完整的功能。
1、集成MessageSoure,因此支持国际化。
2、统一的资源文件访问方式。
3、提供在监听器中注册bean的事件。
4、同时加载多个配置文件
5、载入多个(有继承关系)上下文,使得每个一个上下文都专注于一个特定的层次,比如应用的web层。
BeanFactory采用的是延迟加载形式来注入Bean的,即只有在使用到某个Bean时,调用getBean,才对该Bean进行加载实例化。
这样,我们就不能发行一些存在的spring配置问题。如果bean的某衣蛾属性没有注入,BeanFactory加载后,直至第一次使用调用getBean方法才会抛出异常,
ApplicationContext,它是容器启动时,一次性创建了索引的Bean。这样,在容器启动时,我们就可以发现SpringContext中存在的配置错误,这样有利于检查
依赖属性是否注入。ApplicationContext启动预加载了所有的单例Bean,通过预加载单例bean,确保当你需要的时候,你就不用等待,因为它们已经创建好了。
相对于基本的Beanfactory,ApplicationContext唯一的不足就是占有内存空间。当应用程序配置Bean较多的时候,启动比较慢。
BeanFactory通常以编程的方式被创建,ApplicationContext还能以声明的方式创建,如使用ContextLoader
BeanFactory和ApplicationContext都支持BeanPostProcessor、BeanFactoryProcessor的使用,但两者的区别是:BeanFactory需要手动注册,而ApplicationContext则是自动注册。
描述一下spring bean的生命周期?
1、解析类得到BeanDefinition
2、如果有多个构造方法,则要推断构造方法
3、确定好构造方法后,进行实例化得到一个对象
4、对对象中的加了@Autowired注解的属性进行属性填充
5、回调Aware方法,比如BeanNameAware,BeanFactoryAware
6、调用BeanPostProcessor的初始化前的方法
7、调用初始化方法
8、调用BeanPostProcessor的初始化的方法,在这里会进行AOp
9、如果当前创建的bean是单例则会把bean、放入单例池
10、使用bean
11、spring容器关闭的调用DispacherBean中的destory()方法
Spring 框架支持以下五种 bean 的作用域:
singleton : bean 在每个 Spring ioc 容器中只有一个实例。
prototype:一个 bean 的定义可以有多个实例。
request:每次 http 请求都会创建一个 bean,该作用域仅在基于 web 的 Spring ApplicationContext 情形下有效。
session:在一个 HTTP Session 中,一个 bean 定义对应一个实例。该作用域 仅在基于 web 的 Spring ApplicationContext 情形下有效。
global-session:在一个全局的 HTTP Session 中,一个 bean 定义对应一个实 例。该作用域仅在基于 web 的 Spring ApplicationContext 情形下有效。
缺省的 Spring bean 的作用域是 Singleton。
如果你使组件扫描来发现和声明 Bean,那么你可以在 Bean 的类上使用 @Scope 注解,将其声明为原型 Bean :
Spring框架中都用到了哪些设计模式?(必会)
- 工厂模式:BeanFactory就是简单工厂模式的体现,用来创建对象的实例
- 单例模式:Bean默认为单例模式
- 代理模式:Spring的AOP功能用到了JDK的动态代理和CGLIB字节码生成技术
- 模板方法:用来解决代码重复的问题。比如. RestTemplate, JmsTemplate, JpaTemplate
- 观察者模式:定义对象键一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都会得到通知被制动更新,如Spring中listener的实现–ApplicationListener
Spring事务的实现方式和实现原理(必会)
Spring支持两种类型的事务管理:
编程式事务管理:这意味你通过编程的方式管理事务,给你带来极大的灵活性,但是难维
护。
声明式事务管理:这意味着你可以将业务代码和事务管理分离,你只需用注解和XML配置来
管理事务。在一个方法上加上Transaction
Spring事务的本质其实就是数据库对事务的支持,没有数据库的事务支持,spring是无法提供事务功能的。
真正的数据库层的事务提交和回滚是通过binlog或者redo log实现的。
spring事务实现主要有两种方法
1、编程式,beginTransaction()、commit()、rollback()等事务管理相关的方法
2、声明式,利用注解Transactional 或者aop配置
spring事务的传播行为说的是,当多个事务同时存在的时候,spring如何处理这些事务的行为。
① PROPAGATION_REQUIRED:如果当前没有事务,就创建一个新事务,如果当前存在事务,就
加入该事务,该设置是最常用的设置。
② PROPAGATION_SUPPORTS:支持当前事务,如果当前存在事务,就加入该事务,如果当前不
存在事务,就以非事务执行。
③ PROPAGATION_MANDATORY:支持当前事务,如果当前存在事务,就加入该事务,如果当前
不存在事务,就抛出异常。
④ PROPAGATION_REQUIRES_NEW:创建新事务,无论当前存不存在事务,都创建新事务。
⑤ PROPAGATION_NOT_SUPPORTED:以非事务方式执行操作,如果当前存在事务,就把当前
事务挂起。
⑥ PROPAGATION_NEVER:以非事务方式执行,如果当前存在事务,则抛出异常。
⑦ PROPAGATION_NESTED:如果当前存在事务,则在嵌套事务内执行。如果当前没有事务,则
按REQUIRED属性执行。
spring事务什么时候会失效?
spring事务的原理是aop,进行了切面增强,那么失效的根本原因就是这个aop不起作用了!
常见情况有如下几种
1、发生自调用,类里面使用this调用本类的方法(this通常省略),此时这个this对象不是代理类,而是userservice本身!
解决的方法很简单。让那个this变成userservice的代理类即可!
2、方法不是public的
@Transaction 只能用于public的方法上,否则事务不会失效,如果用在非public方法上
开启aspectj代理模式。
3、数据库不支持事务
4、没有被spring管理
5、异常被吃掉,事务不会回滚(或者抛出的异常没有被定义,默认为runtimeexception)
解释不同方式的自动装配,spring 自动装配 bean 有哪些方
式?
在spring中,对象无需自己查找或创建与其关联的其他对象,由容器负责把需要相互协作的对象引
用赋予各个对象,使用autowire来配置自动装载模式。
在Spring框架xml配置中共有5种自动装配:
no:默认的方式是不进行自动装配的,通过手工设置ref属性来进行装配bean。 byName:通过bean的名称进行自动装配,
如果一个bean的 property 与另一bean 的name 相
同,就进行自动装配。
byType:通过参数的数据类型进行自动装配。
constructor:利用构造函数进行装配,并且构造函数的参数通过byType进行装配。
autodetect:自动探测,如果有构造方法,通过 construct的方式自动装配,否则使用
byType的方式自动装配。
3.16 使用@Autowired注解自动装配的过程是怎样的?
使用@Autowired注解来自动装配指定的bean。在使用@Autowired注解之前需要在Spring配置文
件进行配置,<context:annotation-config />。
在启动spring IOC时,容器自动装载了一个AutowiredAnnotationBeanPostProcessor后置处理
器,当容器扫描到@Autowied、@Resource或@Inject时,就会在IOC容器自动查找需要的bean,
并装配给该对象的属性。在使用@Autowired时,首先在容器中查询对应类型的bean:
如果查询结果刚好为一个,就将该bean装配给@Autowired指定的数据;
如果查询的结果不止一个,那么@Autowired会根据名称来查找;
如果上述查找的结果为空,那么会抛出异常。解决方法时,使用required=false。
3.17 自动装配有哪些局限性?
自动装配的局限性是:
重写:你仍需用 和 配置来定义依赖,意味着总要重写自动装配。
基本数据类型:你不能自动装配简单的属性,如基本数据类型,String字符串,和类。
模糊特性:自动装配不如显式装配精确,如果有可能,建议使用显式装配。