目录
2、 IOC理解,创建一个模块由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容器进行控制,称为反转。 容器说了算.
<bean id="stu" class="com.bjpowernode.pojo.Student"> ===>Spring容器负责对象的创建
<property name="name" value="张三"> ===>Spring容器依赖注入值
<property name="age" value="22">
</bean>
2、 IOC理解,创建一个模块由Spring管理创建对象,另一个由程序员自己创建对象,两则进行对比。
创建一个空项目,然后在其中创建一个maven子模块:
其pom.xml文件:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<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>
</project>
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"?>
<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">
<!--
基于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>
</beans>
测试代码:
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 + '\'' +
'}';
}
}
<!--创建部门对象-->
<!--
使用有参构造器创建对象,并根据有参构造器的参数名注入值。
相当于:new Dept(1,"测试部");
-->
<bean id="deptBySpring" class="com.lin.spring.bean.Dept">
<constructor-arg name="did" value="1"></constructor-arg>
<constructor-arg name="dName" value="测试部"></constructor-arg>
</bean>
@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"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<!--创建学校的对象,使用有参构造方法参数名称注入值-->
<bean id="school" class="com.bjpowernode.pojo3.School">
<constructor-arg name="address1" value="海淀区"></constructor-arg>
<constructor-arg name="name1" value="清华大学"></constructor-arg>
</bean>
<!--创建学生对象,使用有参构造方法的参数的下标注入值-->
<bean id="stu" class="com.bjpowernode.pojo3.Student">
<constructor-arg index="0" value="钱七"></constructor-arg>
<constructor-arg index="2" ref="school"></constructor-arg>
<constructor-arg index="1" value="22"></constructor-arg>
</bean>
</beans>
第三种:使用构造方法参数默认顺序
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 +
'}';
}
}
<!--创建学生对象,使用有参构造方法的参数的默认顺序-->
<!--按照有参构造方法定义的时候的参数书写顺序赋值-->
<bean id="stuSequence" class="com.bjpowernode.pojo3.Student">
<constructor-arg value="陈十"></constructor-arg>
<constructor-arg value="22"></constructor-arg>
<constructor-arg ref="school"></constructor-arg>
</bean>
基于注解的IOC
- 之前是在xml文件中,指明要创建的对象,现在是使用注解指明要创建对象的类。
- 基于注解的IOC也称为DI(Dependency Injection),它是IOC(思想)的具体实现的技术.
- 基于注解的IOC,必须要在Spring的核心配置文件中添加包扫描,这样Spring才会去这个包下去扫描,一旦扫描到某个类有用创建对象的注解,就创建对象。
- <context:component-scan base-package="com.lin.s01"></context:component-scan>
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"?>
<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核心配置文件中添加包扫描
届时Spring就会扫描com.bjpowernode.s01这个包下有没有创建对象的注解,有则创建对象。
-->
<context:component-scan base-package="com.bjpowernode.s01"></context:component-scan>
</beans>
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工厂中搜索指定名称的对象进行注入。
注意:如果有父子类的情况下,建议直接按名称进行注入值.
C.@Resource 即可根据类型注入,也可以根据名称注入
@Resource
private School school;
@Resource(name = "school")
private School school;
这个注解包含以上AB两种方式的作用,但是这个注解不是spring的,是Java的扩展包javax包中的,所以spring不推荐使用。
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 +
'}';
}
}
<?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核心配置文件中添加包扫描
届时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>
</beans>
@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文件中配置。
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<!--创建项目中需要的各种对象-->
<!--创建数据访问层的对象-->
<bean id="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>
</beans>
/**
* 三层架构之数据访问层
*
* @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的无参构造方法。。。。");
}
}
<?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扫描的包-->
<context:component-scan base-package="com.lin.sanceng"></context:component-scan>
</beans>
测试:
@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
<bean id="uController" class="com.bjpowernode.controller.UsersController">
<bean id="bController" class="com.bjpowernode.controller.BookController">
三层之业务逻辑层:applicationContext_service.xml
<bean id="uService" class="com.bjpowernode.controller.UsersService">
<bean id="bService" class="com.bjpowernode.controller.BookService">
三层之数据访问层:applicationContext_mapper.xml
<bean id="uMapper" class="com.bjpowernode.controller.UsersMapper">
<bean id="bMapper" class="com.bjpowernode.controller.BookMapper">
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"?>
<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扫描的包-->
<!--数据访问层对象所在的包-->
<context:component-scan base-package="com.lin.sanceng.mapper"></context:component-scan>
</beans>
<?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扫描的包-->
<!--业务逻辑层对象所在的包-->
<context:component-scan base-package="com.lin.sanceng.service"></context:component-scan>
</beans>
<?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扫描的包-->
<!--界面层之控制层中的对象所在的包-->
<context:component-scan base-package="com.lin.sanceng.controller"></context:component-scan>
</beans>
<?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核心配置文件-->
<!--一个一个导入整合-->
<!-- <import resource="applicationContext_mapper.xml"></import>-->
<!-- <import resource="applicationContext_service.xml"></import>-->
<!-- <import resource="applicationContext_controller.xml"></import>-->
<!--批量导入-->
<import resource="applicationContext_*.xml"></import>
</beans>
// 创建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。
<?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 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>
</beans>
/**
* 日志切面
* 做一些和日志相关的重复操作
*
* @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看它好用就把它整合进来了。
<!-- 当业务实现类没有父接口时,可以使用CGLib子类代理,然后用实现类接代理对象。 SomeServiceImpl service = (SomeServiceImpl)ac.getBean("someServiceImpl"); 当业务实现类有父接口时,就直接用jdk动态代理,用接口接收代理对象。 SomeService service = (SomeService)ac.getBean("someServiceImpl"); -->
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文件中绑定业务和切面
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:aop="http://www.springframework.org/schema/aop"
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/aop https://www.springframework.org/schema/aop/spring-aop.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd">
<!--创建对象改为注解方式
<!–创建业务对象–>
<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子类代理,可以使用接口和实现类接代理对象-->
<!-- <aop:aspectj-autoproxy proxy-target-class="true"></aop:aspectj-autoproxy>-->
</beans>
测试:
@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:
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:aop="http://www.springframework.org/schema/aop"
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/aop https://www.springframework.org/schema/aop/spring-aop.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd">
<!--添加包扫描-->
<context:component-scan base-package="com.lin.aspectj2"></context:component-scan>
<!--声明自动代理,底层动态代理生成代理对象-->
<aop:aspectj-autoproxy></aop:aspectj-autoproxy>
</beans>
测试:
@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底层已经帮我们创建完了 <bean class="org.mybatis.spring.mapper.MapperScannerConfigurer"> <!--指明mapper.xml所在的包--> <property name="basePackage" value="com.lin.sm.mapper"></property> </bean> * */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(数据访问层配置文件)
<?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接管MyBatis框架配置-->
<!--引入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>
</beans>
由于Spring接管了MyBatis框架,所以在MyBatis的核心配置文件中,就可以删除一些配置,因为Spring替MyBatis干了,但是有些活还是得由MyBatis自己干,所以要保留MyBatis的核心配置文件写一些配置由MyBatis自己做的活。
经Spring接管后的MyBatis核心配置文件(同一个配置不能有两套):
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
<!--Spring替MyBatis做了-->
<!-- <properties resource="jdbc.properties"/>-->
<!--Spring替MyBatis做了-->
<!-- <typeAliases>-->
<!-- <!–给包中的全类名起别名–>-->
<!-- <package name="com.lin.sm.bean"/>-->
<!-- </typeAliases>-->
<!--Spring替MyBatis做了-->
<!-- <environments default="development">-->
<!-- <environment id="development">-->
<!-- <transactionManager type="JDBC"/>-->
<!-- <dataSource type="POOLED">-->
<!-- <property name="driver" value="${jdbc.driver}"/>-->
<!-- <property name="url" value="${jdbc.url}"/>-->
<!-- <property name="username" value="${jdbc.username}"/>-->
<!-- <property name="password" value="${jdbc.password}"/>-->
<!-- </dataSource>-->
<!-- </environment>-->
<!-- </environments>-->
<!--Spring替MyBatis做了-->
<!-- <mappers>-->
<!-- <!–以包的形式引入映射文件–>-->
<!-- <package name="com.lin.sm.mapper"/>-->
<!-- </mappers>-->
</configuration>
applicationContext_service.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">
<!--添加Service的包扫描-->
<context:component-scan base-package="com.lin.sm.service.impl"></context:component-scan>
</beans>
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底层已经帮我们创建完了
<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
<!--指明mapper.xml所在的包-->
<property name="basePackage" value="com.lin.sm.mapper"></property>
</bean>
* */
@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对象。<!--添加事务管理器对象,用于生成数据库操作对象。--> <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> <!--因为事务必须关联数据库处理,所以要配置数据源--> <property name="dataSource" ref="dataSource"></property> </bean>2)如果使用Hibernate框架,事务管理器对象
<bean id="transactionManager" class="org.springframework.orm.hibernate3.HibernateTransactionManager">
<property name="sessionFactory" ref="sessionFactory" />
</bean>
项目中的所有事务,必须添加到业务逻辑层上.
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配置文件:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context" xmlns:tx="http://www.springframework.org/schema/tx"
xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd http://www.springframework.org/schema/aop https://www.springframework.org/schema/aop/spring-aop.xsd">
<!--添加包扫描,告知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>
</beans>
@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);
}
可以看到,声明式事务起作用了,底层数据没有插入。即使在执行完插入数据库的代码后,在出错后,也能回滚事务。
设置事务处理的优先级
当我们使用了声明式注解,又想使用注解式注解,就是某些不想受声明式事务管理。
就可以开启注解式事务,然后将注解式事务的优先级设置的比声明式事务的高,就会优先使用注解式事务。
<!--注解式事务的驱动,开启注解式事务。优先级order="100"-->
<tx:annotation-driven transaction-manager="transactionManager" order="100"></tx:annotation-driven>
<!--3.将事务切面 和 切入点 绑定,指定哪些接入点拥有上面的事务切面-->
<aop:config>
<!--给切入点起个id标识-->
<aop:pointcut id="myServiceCut" expression="execution(* com.lin.sm.service.impl.*.*(..))"/>
<!--绑定 advice-ref切面 pointcut-ref切入点-->
<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:嵌套事务
事务传播特性解析
ok表示数据成功插入了,出现异常没有事务处理如回滚等。no表示数据没有插入成功,有事务处理。
场景三:
@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的事务功能。