Spring笔记

Spring

1、Spring 概述

1.1、什么是Spring

spring 就是一个java框架,使用java语言开发的、轻量级的、开源的框架,可以在 j2se、j2ee项目中都可以使用

Spring核心技术:IOC、AOP

Spring又叫做:容器。spring作为容器,装载的是java对象,可以让spring创建java对象,给属性赋值

Spring作用:实现解耦合,解决java对象之间的耦合,解决模块之间的耦合

Spring文档地址:https://spring.io

Tomcat 也是容器,管理的是 servlet、listener、filter 等对象(创建HelloServlet类,写web.xml)

Spring管理的是上面以外的对象,写配置文件

Spring 的工作方式:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-50LUBK3j-1644638852227)(C:\Users\ASUS\AppData\Roaming\Typora\typora-user-images\image-20220126150957564.png)]

1.2、Spring优点

Spring 是一个框架、半成品软件,是一个容器管理对象,容器装的是对象,Spring 是存储对象的容器

优点:

  1. 轻量
  2. 针对接口编程,解耦合
  3. AOP 编程的支持
  4. 方便集成各种优秀框架

2、 IOC 控制反转

2.1、概念

IOC ,Inversion Of Control:控制反转,是一个理论,一个指导思想,指导开发人员如何使用对象、管理对象的,把对象的创建、属性赋值、对象的生命周期都交给代码之外的容器管理

2.1.1、IOC 分为控制和反转

IOC 分为控制和反转

​ 控制:对象创建、属性赋值、对象生命周期管理

​ 反转:把开发人员管理对象的权限转移给代码之外的容器实现,由容器完成对象的管理

​ 正转:开发人员在代码中,使用 new 构造方法创建对象。开发人员掌握对象的创建、属性赋值、对象从开始到销毁的全部过程,开发人员对对象全部控制

通过容器,可以使用容器中的对象(容器已经创建对象、对象属性已赋值、对象也组装好了)

Spring 就是一个日期,可以管理对象、创建对象、给属性赋值

2.1.2、IOC 的技术实现

IOC 的技术实现

DI (依赖注入):Dependency Injection,是 IOC 的一种技术实现。程序只需要提供要使用的对象的名称就可以了,对象如何创建、如何从容器中查找与获取 都由容器内部自己实现

依赖: 比如 Class A 使用了 Class B 的属性或方法,叫做 Class A 依赖 Class B

public class B{
	public void createOrder(){}
}
public class A{
    private ClassB b = new ClassB();
    public void buy(){
        b.createOrder();
    }
}
执行Class Abuy()
    ClassA a = new ClassA();
	a.buy();

2.1.3、Spring 框架是使用 DI 实现 IOC

通过 Spring 框架,只需要提供要使用的对象名称就可以了,从容器中获取名称对应的对象

Spring 底层使用的是反射机制,通过反射创建对象,并属性赋值

2.2、创建容器对象

设置字符集 UTF-8

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-YhmyEb8Q-1644638852244)(C:\Users\ASUS\AppData\Roaming\Typora\typora-user-images\image-20220125153542688.png)]

使用 Spring:Spring 作为容器管理对象,开发人员从 Spring 中获取要使用的对象

实现步骤:

  1. 新建 maven 项目

  2. 加入依赖,修改 pom.xml

    spring-context :Spring依赖

    junit :单元测试

  3. 定义类:接口、实现类

    类也可以没有接口

    接口、实现类:和没有Spring一样定义

  4. 创建 Spring 的配置文件,用于声明对象

    把对象交给Spring创建和管理

    使用<bean>标签表示对象声明,一个 bean 表示一个 java 对象

  5. 使用容器中的对象

    创建一个表示 Spring 容器的对象 ApplicationContext

    从容器中,根据名称获取对象,使用 getBean("对象名称")

2.2.1、pom.xml

<properties>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    <maven.compiler.source>1.8</maven.compiler.source>
    <maven.compiler.target>1.8</maven.compiler.target>
</properties>

<dependencies>
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-context</artifactId>
        <version>5.2.5.RELEASE</version>
    </dependency>
    <dependency>
        <groupId>junit</groupId>
        <artifactId>junit</artifactId>
        <version>4.11</version>
    </dependency>
</dependencies>

2.2.2、Spring 配置文件

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-CUkizKmP-1644638852245)(C:\Users\ASUS\AppData\Roaming\Typora\typora-user-images\image-20220125155937299.png)]

<?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: 自定义对象名称,唯一值(可以没有,spring可以提供默认的名称)
        class: 类的全限定名称,spring 通过反射机制创建对象,不能是接口
        
    spring根据 id、class 创建对象,把对象放入spring的一个map对象中
        map.put(id,对象)
    -->
    <bean id="someService" class="com.service.impl.SomeServiceImpl"/>
</beans>

Spring标准的配置文件:

  1. 根标签是 beans
  2. beans 后面是约束文件说明
  3. beans 里面是 bean 声明
  4. 什么是bean: bean就是java对象,Spring容器管理的java对象,叫做bean

2.2.3、创建spring容器对象

容器对象特点:

  1. 容器对象 ApplicationContext :接口

    通过 ApplicationContext 对象,获取要使用的 java 对象,执行 getBean(“id”)

  2. Spring 默认是调用类的无参构造器创建对象

  3. Spring 读取配置文件,一次创建好所有的 java 对象,都放到 map 中

    Object object = this.factoryBeanObjectCache.get(beanName);

// 1、指定spring配置文件:从类路径(classpath)之下开始的路径
String config="beans.xml";
// 2、创建容器对象,ApplicationContext 表示spring容器对象,通过applicationContext获取某个java对象
ApplicationContext applicationContext = new ClassPathXmlApplicationContext(config);
// 3、从容器中获取指定名称的悐,使用 getBean("id")
SomeServiceImpl service = (SomeServiceImpl) applicationContext.getBean("someService");
// 4、调用实现类对象的方法,接口中的方法
service.doSome();
  1. spring创建对象,调用类的哪个方法?

    Spring默认使用无参构造方法创建对象

  2. spring在什么时候创建的对象?

    创建 Spring 容器对象的时候,会读取配置文件,创建文件中声明的java对象

    所以在new ClassPathXmlApplicationContext(config)的时候调用无参构造器

    速度快,但是耗费占用内存

  3. spring一次创建几个对象?

    全部创建。

    在创建容器对象(ApplicationContext)时,会把配置文件中所有对象都创建处理(Spring默认规则)

获取容器中对象信息:

获取容器中定义对象的数量: getBeanDefinitionCount()

获取容器中定义对象的名称(bean标签的id属性值):getBeanDefinitionNames()

配置文件:
<bean id="someService" class="com.service.impl.SomeServiceImpl"/>
<bean id="someService1" class="com.service.impl.SomeServiceImpl"/>
@Test
public void test1(){
    ApplicationContext appc = new ClassPathXmlApplicationContext("beans.xml");

    //获取容器中定义对象的数量
    int nums = appc.getBeanDefinitionCount();
    System.out.println("容器中定义对象的数量==" + nums);

    //获取容器中定义对象的名称(bean标签的id属性值)
    String[] names = appc.getBeanDefinitionNames();
    for (String name : names) {
        System.out.println("容器中定义对象的名称==" + name);
    }
}
spring创建非自定义类的对象

配置文件:

<!-- 创建非自定义类的对象 -->
<bean id="mydate" class="java.util.Date"/>

创建对象:

@Test
public void test2(){
    ApplicationContext appc = new ClassPathXmlApplicationContext("beans.xml");
    // 让Spring创建非自定义类的对象,就是有Class就能让Spring创建对象
    Date date = (Date) appc.getBean("mydate");
    System.out.println(date);
}

2.3、DI:给属性赋值

Spring 调用类的无参构造器创建对象,对象创建后给属性赋值

属性的赋值有两个方式:1、xml 配置文件中的标签和属性;2、使用注解

DI 分类:1、set 注入,也叫做设值注入;2、构造注入

2.3.1、基于 xml 的DI

在 xml 配置文件中使用标签和属性,完成对象创建和属性赋值

2.3.1.1、set 注入

set 注入,也叫做设值注入

概念 :Spring调用类中的set方法,在set方法中可以完成属性赋值。(推荐使用)

使用<property name="构造器形参名" value="简单类型形参的值" ref="引用类型形参的值"/> 标签

  1. 简单类型:java中的基本数据类型和String

    简单类型的设值注入,使用value
    <bean id="mystudent" class="com.domain01.Student">
        <property name="name" value="小草莓"/> <!--调用setName()方法-->
        <property name="age" value="18"/> <!--调用setAge()方法-->
    </bean>
    
  2. 引用类型:自定义类对象作为属性

    引用类型的set注入,使用ref
    <bean id="mystudent" class="com.domain01.Student">
        <property name="name" value="小草莓"/> <!--调用setName()方法-->
        <property name="age" value="18"/> <!--调用setAge()方法-->
        <property name="school" ref="myschool"/> <!--调用setSchool()方法-->
    </bean>
    <bean id="myschool" class="com.domain01.School">
        <property name="schoolname" value="家里蹲"/> <!--调用setSchoolname()方法-->
    </bean>
    
2.3.1.2、构造注入

概念:Spring调用类中的有参构造器,在创建对象的同时,给属性赋值

使用<constructor-arg name="构造器形参名" index="构造器参数位置" value="简单类型形参的值" ref="引用类型形参的值"/>标签

  1. 使用 name 属性

    <!--构造注入:使用 name 属性-->
    <bean id="mystudent" class="com.domain01.Student">
        <constructor-arg name="name" value="小草莓"/> 
        <constructor-arg name="age" value="18"/> 
        <constructor-arg name="school" ref="myschool"/>
    </bean>
    
  2. 使用 index 属性

    <!--构造注入:使用 index,参数位置从左往右是0,1,2-->
    <bean id="mystudent" class="com.domain01.Student">
        <constructor-arg index="1" value="18"/>
        <constructor-arg index="0" value="小草莓"/>
        <constructor-arg index="2" ref="myschool"/>
    </bean>
    

    也可以省略 index

    <!-- 省略 index -->
    <bean id="mystudent" class="com.domain01.Student">
        <constructor-arg value="小草莓"/>
        <constructor-arg value="18"/>
        <constructor-arg ref="myschool"/>
    </bean>
    
2.3.1.3、引用类型属性自动注入

概念:Spring可以根据某些规则给引用类型完成赋值,只对引用类型有效

byName(按名称注入)

byName(按名称注入):java类中引用类型属性名称和Spring容器中bean 的 id 属性值一样,且数据类型也是一样的,这样bean能够赋值给引用类型

示例:

java对象:

@Data
public class Student {
    private String name;
    private Integer age;
    private School myschool;
}

配置文件:

<!--
    引用类型自动注入:byName
        java类中引用类型的属性名称和 bean 标签的 id 属性值一样,且数据类型一样
-->
<bean id="mystudent" class="com.domain01.Student" autowire="byName">
    <property name="name" value="小草莓"/>
    <property name="age" value="18"/> 
</bean>

<!-- 下面bean标签的id值需要和Student类中的private School myschool;属性名一致 -->
<bean id="myschool" class="com.domain01.School">
    <property name="schoolname" value="家里蹲"/>
</bean>
byType(按类型注入)

byType(按类型注入):java类中引用类型属性的数据类型和 Spring容器中bean 的 class 属性值是同源关系,这样bean能够赋值给引用类型

注意: 在配置文件中,符合条件的对象只能有一个,多余一个都会报错

同源关系:

  1. 引用类型属性和bean的class值 数据类型是一样的
  2. 引用类型属性和bean的class值 数据类型是父子类关系
  3. 引用类型属性和bean的class值 数据类型是借口和实现类关系

示例:

java对象:

@Data
public class Student {
    private String name;
    private Integer age;
    private School myschool;
}

配置文件:

<!--
    引用类型自动注入:byType
        java类中引用类型属性和 bean 标签的 class 属性值的数据类型是同源关系
-->
<bean id="mystudent" class="com.domain01.Student" autowire="byType">
    <property name="name" value="小草莓"/>
    <property name="age" value="18"/> 
</bean>

<!-- 声明School的子类 -->
<bean id="myprimaryschool" class="com.domain01.PrimarySchool">
    <property name="primarySchoolname" value="家里蹲"/>
</bean>
2.3.1.4、使用多个Spring配置文件

分多个配置文件方式:

  1. 按功能模块分,一个模块一个配置文件
  2. 按类的功能分,数据库操作相关的类在一个文件,service类在一个配置文件,redis、事务等一个配置文件

Spring管理多个配置文件:

​ 常用的是包含关系的配置文件。项目中有一个总的问文件,里面用 import 标签指向包含的其他多个配置文件

总的文件
<import resource="classpath:其他文件路径1"/>
<import resource="classpath:其他文件路径2"/>

关键字"claspath":
	表示类路径,也就是类文件(class文件)所在的目录。Spring到类路径中加载文件
什么时候使用classpath:
	在一个文件中要使用其他文件,需要使用classpath

示例:

总的文件beans.xml,包含其他多个配置文件,一般不声明bean

<!--
    当前是总的文件,用于指向其他多个配置文件,一般不声明 bean
    语法:
        <import resource="classpath:其他文件路径"/>
    classpath:表示类路径,类文件所在目录,target/classes目录
-->
<import resource="classpath:spring-school.xml"/>
<import resource="classpath:spring-student.xml"/>

通配符

包含关系的配置文件,可使用通配符 * ,表示任意字符

注意:总的文件名称,不能包含在通配符范围内(beans.xml不能是spring-beans.xml)

示例: 总的文件beans.xml

可使用以下一行代码指代上面例子中的两行代码,使用通配符 * 替代了后面所有字符

<import resource="classpath:spring-*.xml"/>

2.3.2、基于注解的DI

基于注解的DI:使用 Spring 提供的注解,完成 java 对象创建、属性赋值

2.3.2.1、定义bean的 @Component

@Component:表示创建对象,对象放到容器中,作用是<bean/>

使用步骤:

  1. 在源代码加入注解 @Component

    import lombok.Data;
    import org.springframework.stereotype.Component;
    /**
     *  @Component:表示创建对象,对象放到容器中,作用是<bean/>
     *      属性: value ,表示对象名称,也就是bean的 id 属性值
     *      位置: 在类的上面,表示创建此类的对象
     *  @Component(value = "mystudent") 等同于
     *      <bean id="mystudent" class="com.domain01.Student"/>
     *      
     *  和 @Component 功能相同的创建对象的注解:
     *      1) @Repository: 放在dao接口的实现类上面,表示创建dao对象、持久层对象,能访问数据库
     *      2) @Service: 放在业务层接口的实现类上面,表示创建业务层对象,业务层对象有事务的功能
     *      3) @Controller: 放在控制器类的上面,表示创建控制器对象,属于表层对象,
     * 					   			  				控制器对象接收请求、将处理结果显示给用户
     *      
     *  以上四个注解都能创建对象,但是 @Repository、@Service、@Controller 有角色说明,
     *  							能够表示对象是分层的,表示对象是属于不同层的,具有额外的功能
     */
    //使用value指定对象的名称
    //@Component(value = "mystudent")
    //可以省略value,默认就是首字母小写的类名
    @Component
    @Data
    public class Student {
        private String name;
        private Integer age;
    }
    
  2. 在 Spring 的配置文件,加入注解扫描器标签

    <!--
        声明组件扫描器:使用注解必须加入这个语句
        component-scan: 组件扫描器,组件是java对象
            属性: base-package ,注解在项目中的包名
                    框架会扫描此包及子包的所有类,找类中的所有注解
                    找到注解后,按照注解表示的功能,去创建对象,给属性赋值
    -->
    <context:component-scan base-package="com"/>
    
  3. context:exclude-filter 标签用于设置哪些内容不进行扫描

    <context:component-scan base-package="com">
        <context:exclude-filter type="annotation" 
                               expression="org.springframework.stereotype.Controller"/>
    </context:component-scan>
    

扫描多个包的三种方式:

<!-- 扫描多个包的三种方式 -->
<!-- 第一种:使用多次组件扫描区 -->
<context:component-scan base-package="com.domain01"/>
<context:component-scan base-package="com.domain02"/>

<!-- 第二种:使用分隔符(, ;),指定多个包 -->
<context:component-scan base-package="com.domain01,com.domain02;com.domain03"/>

<!-- 第三种:指定父包 -->
<context:component-scan base-package="com"/>
2.3.2.2、简单类型注入的 @Value

简单类型属性赋值: @Value

示例:

import lombok.Data;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

@Component
@Data
public class Student {
    /**
     *  @Value:简单类型属性赋值
     *      属性: value 简单类型属性值
     *      位置:   1) 在属性定义的上面,无需 set 方法,推荐
     *              2) 在set方法的上面
     */
    @Value(value = "小草莓")
    private String name;
    private Integer age;
}

@Value 使用外部属性配置文件

  1. 创建外部属性配置文件 resources/myconf.properties

    myname=小草莓
    myage=18
    
  2. 在 Spring 的配置文件,加入外部属性配置文件指定标签

    <!--
        读取外部的属性配置文件
        property-placeholder:读取properties之类的文件
    -->
    <context:property-placeholder location="classpath:myconf.properties"/>
    
  3. 在源代码加入注解

    @Component
    @Data
    public class Student {
    
        //使用外部属性文件中的数据,@Value("${key名}")
        @Value("${myname}")
        private String name;
        private Integer age;
    }
    
2.3.2.3、自动注入的 @Autowired
@Autowired 属性 required

boolean类型的属性,默认为true

true 表示若引用类型属性赋值失败,终止程序执行,并报错

false 表示若引用类型属性赋值失败,程序正常执行,引用类型属性值为 null

@Component
@Data
public class Student {
    //使用外部属性文件中的数据,@Value("${key名}")
    @Value("${myname}")
    private String name;
    private Integer age;
    /**
     *  引用类型自动注入
     *  @Autowired:Spring框架提供,给引用类型属性赋值的,支持 byName、byType。默认是byType
     *      属性: required ,boolean类型的属性,默认为true
     *                  true:spring在启动时,创建容器对象,会检查引用类型属性是否赋值成功
     *                          若赋值失败,终止程序执行,并报错
     *                  false:若引用类型属性赋值失败,程序正常执行,引用类型属性值为 null
     *      位置:   1) 在属性定义的上面,无需 set 方法,推荐
     *              2) 在set方法的上面
     */
    @Autowired(required = false)
    @Qualifier("aaaa")
    private School school;
}

测试:

@Test
public void test1(){
    ApplicationContext appc = new ClassPathXmlApplicationContext("beans.xml");
    Student student = (Student) appc.getBean("student");
    System.out.println(student);
}

输出结果:

Student(name=小草莓, age=null, school=null)
使用 byType 自动注入

注意:

当一个接口有多个实现类的时候,使用 @Autowired 进行类型自动注入就会报错

自定义类:

import lombok.Data;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

@Data
@Component(value = "myschool")
public class School {
    @Value(value = "家里蹲")
    private String schoolName;
}

使用自动注入,引用类型属性

import com.domain02.School;
import lombok.Data;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

@Component
@Data
public class Student {
    @Value("${myname}")
    private String name;
    private Integer age;
    /**
     *  引用类型自动注入
     *  @Autowired:Spring框架提供,给引用类型属性赋值的,自动注入
     *      支持 byName、byType。默认是byType
     *      位置:   1) 在属性定义的上面,无需 set 方法,推荐
     *              2) 在set方法的上面
     */
    //默认使用byType
    @Autowired
    private School school;
}

测试:

@Test
public void test1(){
    ApplicationContext appc = new ClassPathXmlApplicationContext("beans.xml");
    Student student = (Student) appc.getBean("student");
    System.out.println(student);
}

结果:

Student(name=小草莓, age=null, school=School(schoolName=家里蹲))
使用 byName 自动注入 @Qualifier

自定义类:

import lombok.Data;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

@Data
@Component(value = "myschool")
public class School {
    @Value(value = "家里蹲")
    private String schoolName;
}

使用自动注入,引用类型属性

import com.domain02.School;
import lombok.Data;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

@Component
@Data
public class Student {
    @Value("${myname}")
    private String name;
    private Integer age;
    /**
     *  byName自动注入
     *  	1) @Autowired:给引用类型属性赋值
     *  	2) @Qualifier(value = "bean标签的id"):从容器中找到指定名称的对象,
     * 															把对象赋值给引用类型属性 
     */
    //使用byName
    @Autowired
    @Qualifier("myschool")
    private School school;
}
2.3.2.4、JDK注解 @Resource 自动注入

注意: JDK高于1.8的就没有这个注解,需要加入依赖

<dependency>
    <groupId>javax.annotation</groupId>
    <artifactId>javax.annotation-api</artifactId>
    <version>1.3.2</version>
</dependency>

默认先使用 byName 赋值,如果赋值失败,再使用 byType

@Component
@Data
public class Student {
    @Value(value = "小草莓")
    private String name;
    private Integer age;
    /**
     *  @Resource:来自JDK,给引用类型属性赋值的,自动注入,支持 byName、byType。默认是 byName
     *      位置:   1)在属性定义的上面,无需 set 方法,推荐
     *             2) 在set方法的上面
     *  
     *  注意:JDK高于1.8的就没有这个注解,需要加入依赖 javax.annotation-api 1.3.2
     *  
     */
    //默认使用 byName 自动注入
    //先使用 byName 赋值,如果赋值失败,再使用 byType
    @Resource
    private School school;
}

如果只想使用 byName 赋值,使用注解属性 name="bean的id"

    @Resource(name="myschool")
    private School school;
2.3.2.5、IOC 总结

IOC :管理对象的,把对象放到容器中,创建、赋值、管理依赖关系

通过管理对象,实现解耦合,IOC 解决处理业务逻辑对象之间的耦合关系,也就是 servlet 与 dao 之间的解耦合

Spring 作为容器适合管理什么对象?

  1. service 对象、dao 对象
  2. 工具类对象

不适合交给 Spring 的对象?

  1. 实体类
  2. servlet、listener、filter 等web中的对象,这些是Tomcat创建和管理的

3、AOP 面向切面编程

在编写好程序之后,又有需求对编写好的代码上进行功能的增加

在源代码中、业务方法中增加功能,可能导致的问题:

  1. 源代码可能改动的比较多
  2. 重复代码多
  3. 代码难于维护

现在想要实现在不更改原来编写的源代码的情况下,增加新的功能

AOP 能完成代理的功能,在保证核心的源代码不动的情况下,增加其他功能,并调用核心的源代码

public class ServiceProxy implements UserService {
    //真正的目标
    UserService userService = new UserServiceImpl();
    @Override
    public void addUser() {
        System.out.println("日志功能:记录方法执行时间" + new Date());
        userService.addUser();
        System.out.println("事务功能:业务方法之后,提交事务");
    }
}

3.1、AOP概念

3.1.1、什么是 AOP ?

  1. AOP(Aspect Orient Programming):面向切面编
  2. Aspect:表示切面,给业务方法增加的功能,叫做切面。切面一般都是非业务功能,而且切面功能一般都是可以复用的。例如,日志功能、事务功能、权限检查、参数检查、统计信息等
  3. Orient:面向
  4. Programming:编程

怎么理解面向切面编程?

  1. 就是以切面为核心设计开发你的应用
  2. 设计项目时,找出切面的功能
  3. 安排切面的执行时间,执行的位置

AOP 的作用:

  1. 让切面功能复用
  2. 让开发人员专注业务逻辑,提高开发效率
  3. 实现业务功能和其他非业务功能解耦合
  4. 给存在的业务方法增加功能,不用修改原来的代码

3.1.2、AOP 中的术语:

  1. Aspect:切面。给业务方法增加的功能
  2. JoinPoint:连接点。连接切面的业务方法,在这嘎业务方法执行时,会同时执行切面的功能
  3. PointCut:切入点。是一个或多个连接点集合,表示这些方法执行时,都能增加切面的功能,表示切面执行的位置
  4. target:目标对象。给哪个对象增加切面的功能,这个对象就是目标对象
  5. Advice:通知(增强)。表示切面的执行时间,在目标方法之前执行切面,还是目标方法之后执行切面

AOP 中重要三个要素:Aspect、PointCut、Advice。

理解:在 Advice 的时间,在 PointCut 的位置,执行 Aspect

AOP 是一个动态的思想,在程序运行期间,创建代理(ServiceProxy),使用代理执行法时,增加切面的功能,这个代理对象是存在内存中的

什么时候用 AOP ?

  1. 当你要给某些非法增加相同的一些功能时,源代码不能改
  2. 给业务方法增加非业务功能,也可用 AOP

AOP 技术思想的实现

使用框架实现 AOP,实现 AOP 的框架有很多,有名的是以下两个:

  1. Spring:spring框架实现 AOP 思想中的部分功能,比较繁琐
  2. Aspectj:独立的框架,专门作用于 AOP,属于 Eclipse

3.2、使用AspectJ框架实现 AOP

AspectJ 框架可以使用注解和xml配置文件两种方式实现AOP

3.2.1、通知分类

AspectJ 表示切面执行时间,用的通知(Advice),这个通知可以使用注解表示
下面是表示切面的 5 个执行时间,这些注解叫做通知注解

1. @Before :前置通知
2. @AfterReturning :后置通知
3. @Around : 环绕通知
4. @AfterThrowing :异常通知
5. @After :最终通知

3.2.2、PointCut 位置(切入点表达式)

PointCut 用来表示切面执行的位置,使用 AspectJ 中切入点表达式来表示
切入点表达式: execution(权限类型 返回值类型 包名类名方法名(参数类型和参数个数) 抛出异常类型)
举例:execution(public void com.proxy.MyProxy.doSome(String,Integer))

  1. 权限类型、包名类名、抛出异常类型 :三个是可选部分,可有可无
  2. 返回值类型、方法名(参数类型和参数个数) :必须存在

通配符:

  1. * : 0至多个任意字符
  2. .. : 用在方法参数中,表示任意多个参数;用在包名后,表示当前包及其子包路径,此时后须跟*
  3. + : 用在类名后,表示当前类及其子类;用在接口后,表示当前接口及其实现类

切入点表达式例子:

  1. execution(puiblic * *(..))
    指定切入点为:任意公共方法
  2. execution( * set*(..))
    指定切入点为:任意以set开头方法名的方法
  3. execution( * com.service.*.*(..))
    指定切入点为:在com.service包内的任意类的任意方法
  4. execution( * com.service..*.*(..))
    指定切入点为:在com.service包及其子包的任意类的任意方法
  5. execution( * *..service.*.*(..))
    指定所有包下的 service 子包的任意类的任意方法

3.2.3、前置通知@Before

实现步骤:

1. 新建maven项目,加入依赖
   spring-context、spring-aspects、junit
2. 创建业务接口和实现类
3. 创建一个普通类,作为切面类
   1. 在类的上面加入@Aspect
   2. 在类中定义方法,方法表示切面的功能
      在方法的上面加入 Aspect 框架中的通知注解,如@Before(value="切入点表达式")
4. 创建Spring配置文件
   1. 声明目标对象
    2. 声明切面类对象
    3. 声明自动代理生成器
5. 创建测试类,测试目标方法执行时,增加切面的功能

前置通知方法的定义:

  1. 方法是 public
  2. 方法是没有返回值的,void
  3. 方法名称自定义
  4. 方法可以有参数,如果有是JoinPoint;也可以没有参数

示例:

com.service.impl.SomeServiceImpl 实现类:

public class SomeServiceImpl implements SomeService {
    @Override
    public void doSome(String name, Integer age) {
        System.out.println("业务方法doSome()");
    }
}

com.handle.MyAspect 切面类:

/**
 *  @Aspect:切面类的注解
 *      位置:在普通类的上面
 *      作用:表示当前类是切面类(表示切面功能的类)
 */
@Aspect
public class MyAspect {
    //定义方法
    /*
        前置通知方法的定义:
            1)方法是 public
            2)返回值为 void
            3)方法名称自定义
            4)方法可以有参数,如果有是JoinPoint;也可以没有参数
     */

    /**
     *  @Before:前置通知
     *      属性:value ,值为 切入点表达式,表示切面的执行位置,在执行此方法时,会同时执行切面的功能
     *      位置:在方法上面
     *      特点:
     *          1)执行时间:在目标方法之前执行
     *          2)不会影响目标方法的执行
     *          3)不会修改目标方法的执行结果
     */
    @Before(value = "execution(public void com.service.impl.SomeServiceImpl.doSome(String,Integer))")
    public void myBefore(){
        System.out.println("前置通知,切面的功能。在目标方法之前先执行");
    }
}

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: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/aop https://www.springframework.org/schema/aop/spring-aop.xsd">

    <!--声明目标对象-->
    <bean id="someServiceImpl" class="com.service.impl.SomeServiceImpl"/>
    <!--声明切面类对象-->
    <bean id="myAspect" class="com.handle.MyAspect"/>
    <!--声明自动代理生成器:目的是创建目标对象的代理(ServiceProxy)
            调用AspectJ框架中的功能,寻找Spring容器中所有目标对象,将每个目标对象加入切面类中的功能,生成代理
            这个代理对象是修改的内存中的目标对象,这个目标对象就是代理对象(ServiceProxy)
    -->
    <aop:aspectj-autoproxy/>
</beans>

测试:

@Test
public void test02(){
    ApplicationContext appc = new ClassPathXmlApplicationContext("applicationContext.xml");
    //注意,对于使用AOP这里一定是接口的类型,而不是实现类的类型
    SomeService service = (SomeService) appc.getBean("someServiceImpl");
    System.out.println(service.getClass().getName()); //com.sun.proxy.$Proxy8
    service.doSome("",null); 
}

使用参数 JoinPoint :
当想要在通知方法里面使用到目标类的信息时,可以使用参数 JoinPoint

使用参数的前置通知方法:

/**
 *  @Aspect:切面类的注解
 *      位置:在普通类的上面
 *      作用:表示当前类是切面类(表示切面功能的类)
 */
@Aspect
public class MyAspect {
    //定义方法
    /*
        前置通知方法的定义:
            1)方法是 public
            2)返回值为 void
            3)方法名称自定义
            4)方法可以有参数,如果有是JoinPoint;也可以没有参数
     */
    /**
     *  切面类中的通知方法可以有参数 JoinPoint
     *  JoinPoint :表示正在执行的业务方法,相当于反射中 Method
     *      使用要求:必须是参数列表的第一个
     *      作用:获取方法执行时的信息,例如方法名称、方法的参数集合
     */
    @Before(value = "execution(* *..SomeServiceImpl.do*(..))")
    public void myBefore(JoinPoint jp){
        //获取方法的定义
        System.out.println("前置通知==获取方法声明" + jp.getSignature());
        System.out.println("前置通知==获取方法名" + jp.getSignature().getName());
        //获取方法执行时参数
        Object[] args = jp.getArgs();  //数组中存放的是方法的所有参数
        for (Object o : args) {
            System.out.println("前置通知==获取方法的参数" + o);
        }

        /*
            获取到方法执行的信息有什么用呢?
                下面举例:
         */
        String methodName = jp.getSignature().getName();
        if("doSome".equals(methodName)){
            //切面的代码
            System.out.println("前置通知==输出日志功能");
        }else if ("doOther".equals(methodName)){
            System.out.println("前置通知==记录时间功能");
        }
    }
}

测试结果:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-UWcdzO26-1644638852246)(C:\Users\ASUS\AppData\Roaming\Typora\typora-user-images\image-20220206172925083.png)]

3.2.4、后置通知@AfterReturning

后置通知方法的执行是在目标方法之后执行

语法:@AfterReturning(value=“切入点表达式” , returning=“通知方法的形参名”)

后置通知方法的定义:

  1. 方法是 public
  2. 方法是没有返回值的,void
  3. 方法名称自定义
  4. 方法有参数,推荐使用 Object

Spring配置文件未做改变

SomeServiceImpl 实现类:

public class SomeServiceImpl implements SomeService {
    @Override
    public String doSome(String name, Integer age) {
        System.out.println("业务方法doSome()");
        return name+age;
    }
}

MyAspect 切面类:

@Aspect
public class MyAspect {
    /**
     *  @AfterReturning:后置通知,属性间用逗号隔开
     *      属性: value 切入点表达式
     *            returning  自定义的变量,表示目标方法的返回值
     *                       自定义变量名必须和通知方法的形参名一致
     *      位置:在方法的上面
     *  特点:
     *      1)在目标方法之后执行
     *      2)能获取到目标方法的执行结果
     *      3)不会影响目标方法的执行
     *  方法参数:
     *      Object obj :表示目标方法的返回值,使用obj接受doSome方法的调用结果
     *      Object obj = doSome();
     */
    @AfterReturning(value = "execution(* *..SomeServiceImpl.doSome(..))",returning = "obj")
    public void myAfterReturning(Object obj){
        System.out.println("后置通知==在目标方法之后执行,能拿到执行结果:" + obj);
    }
}

测试:

@Test
public void test03(){
    ApplicationContext appc = new ClassPathXmlApplicationContext("applicationContext.xml");
    //注意,对于使用AOP这里一定是接口的类型,而不是实现类的类型
    SomeService service = (SomeService) appc.getBean("someServiceImpl");
    System.out.println(service.getClass().getName()); //com.sun.proxy.$Proxy8
    String result = service.doSome("xcm",18);
    System.out.println(result);    //xcm18
}

测试结果:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-TC71YohP-1644638852247)(C:\Users\ASUS\AppData\Roaming\Typora\typora-user-images\image-20220206172952073.png)]

后置通知的方法参数可以拿到目标方法的返回值结果,那么拿到结果有什么用呢?

    @AfterReturning(value = "execution(* *..SomeServiceImpl.doSome(..))",returning = "obj")
    public void myAfterReturning(Object obj){
        System.out.println("后置通知==在目标方法之后执行,能拿到执行结果:" + obj);
        /*
            参数 Object obj 有什么用?
                1)根据返回值的不同执行不同的增强功能
                2)更改方法返回值结果
         */
        if ("xcm".equals(obj)){
            System.out.println("根据返回值的不同执行不同的增强功能");
        }
        if (obj != null){
            obj = "更改后的方法返回值结果";
        }
    }

思考:

  1. doSome 方法返回值是String、Integer、Long等基本类型时
    在后置通知中,修改返回值,是不会影响目标方法的最后调用结果的
  2. doSome 返回的结果是对象类型,例如 Student
    在后置通知中,修改这个Student对象的属性值,会不会影响最后调用结果?

3.2.5、环绕通知@Around

环绕通知方法的定义:

  1. 方法是 public

  2. 方法是必须是有返回值,推荐使用 Object 类型

  3. 方法名称自定义

  4. 方法必须有 ProceedingJoinPoint 参数

  5. 不使用环绕通知的 ProceedingJoinPoint 参数时:使用环绕通知,执行目标方法的时候实际上调用的是切面类中的通知方法

    示例:

    1. 切面类中的环绕通知:

      /**
       *  @Aspect:切面类的注解
       *      位置:在普通类的上面
       *      作用:表示当前类是切面类(表示切面功能的类)
       */
      @Aspect
      public class MyAspect {
          /**
           *  环绕通知方法的定义:
           *      1)方法是public,方法名称自定义
           *      2)方法是必须是有返回值,推荐使用 Object 类型
           *      3)方法必须有 ProceedingJoinPoint 参数
           *  @Around:环绕通知
           *      属性: value  切入点表达式
           *      位置: 在方法定义的上面
           *
           *  返回值: Object ,表示调用目标方法希望得到执行结果(不一定是目标方法自己的返回值)
           *  参数:  ProceedingJoinPoint ,相当于反射中 Method
           *              作用:执行目标方法,等于 Method.invoke()
           *  特点:
           *      1)在目标方法执行的前、后都能有增强功能
           *      2)控制目标方法是否执行
           *      3)修改目标方法的执行结果
           */
          @Around(value = "execution(* *..SomeServiceImpl.doSome(..))")
          public Object myAround(ProceedingJoinPoint pjp){
              System.out.println("环绕通知==执行环绕通知方法");
              return "HelloAround";
          }
      }
      
    2. 测试:

      @Test
      public void test04(){
          ApplicationContext appc = new ClassPathXmlApplicationContext("applicationContext.xml");
          //注意,对于使用AOP这里一定是接口的类型,而不是实现类的类型
          SomeService service = (SomeService) appc.getBean("someServiceImpl");
          System.out.println(service.getClass().getName());   //com.sun.proxy.$Proxy8
          String result = service.doSome("xcm",18);    //环绕通知==执行环绕通知方法
          System.out.println(result);    //HelloAround
      }
      
    3. 测试结果:输出的返回值也是通知方法的返回值

      [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-cU1ekRQZ-1644638852247)(C:\Users\ASUS\AppData\Roaming\Typora\typora-user-images\image-20220206173027556.png)]

  6. 使用 ProceedingJoinPoint 参数

    1. 在目标方法执行的前、后都能有增强功能;控制目标方法是否执行

      @Aspect
      public class MyAspect {
      	@Around(value = "execution(* *..SomeServiceImpl.doSome(..))")
          public Object myAround(ProceedingJoinPoint pjp) throws Throwable {
              System.out.println("环绕通知==在目标方法执行之前");
              
              Object methodResult = null;
              //执行目标方法,相当于反射的method.invoke()
              methodResult = pjp.proceed();  //返回的结果methodResult就是目标方法的返回值
      
              System.out.println("环绕通知==在目标方法执行之后");
              return "HelloAround";
              //可以直接返回目标方法的返回结果,不修改
              //return methodResult;
          }
      }
      
    2. 修改目标方法的执行结果

      @Aspect
      public class MyAspect {
          @Around(value = "execution(* *..SomeServiceImpl.doSome(..))")
          public Object myAround(ProceedingJoinPoint pjp) throws Throwable {
              Object methodResult = null;
              methodResult = pjp.proceed();  //返回的结果就是目标方法的返回值
              //修改目标方法的返回值
              if (methodResult != null){
                  methodResult = "在环绕通知中更改方法返回值";
              }
              return methodResult;
          }
      }
      
    3. 可以获取目标执行方法的参数、方法名

      @Aspect
      public class MyAspect {
      	@Around(value = "execution(* *..SomeServiceImpl.doSome(..))")
          public Object myAround(ProceedingJoinPoint pjp) throws Throwable {
              Object methodResult = null;
      
              //ProceedingJoinPoint 类继承了 JoinPoint 类,所以也可以得到目标方法的参数、方法名
              String objName = "";
              Object[] args = pjp.getArgs();
              for (Object o : args) {
                  if (o != null){
                      //此时前提是doSome方法的参数仅有一个 String name
                      objName = (String) o;
                  }
              }
              //判断参数内容
              if ("xcm".equals(objName)){
                  //执行目标方法,相当于反射的method.invoke()
                  methodResult = pjp.proceed();  //返回的结果就是目标方法的返回值
              }
      
              //可以直接返回目标方法的返回结果
              return methodResult;
          }
      }
      

3.2.6、异常通知@AfterThrowing

语法:@AfterThrowing (value=“切入点表达式” , throwing=“通知方法的形参名”)

异常通知方法的定义:

  1. 方法是 public
  2. 方法是没有返回值的,void
  3. 方法名称自定义
  4. 方法有参数,参数是 Exception

Spring配置文件未做改变

@Aspect
public class MyAspect {
	/**
     *  @AfterThrowing:异常通知
     *      属性:value 切入点表达式
     *           throwing 自定义变量,表示目标方法抛出的异常
     *                    变量名必须和通知方法的形参名一致
     *      位置:在方法的上面
     *  特点:
     *      1)在目标方法抛出异常后执行,没有异常不执行
     *      2)能获取到目标方法的异常信息
     *      3)不是异常处理程序,可以得到发生异常的通知,可以发送邮件、短信通知开发人员
     *              看做是目标方法的监控程序
     */
    @AfterThrowing(value = "execution(* *..SomeServiceImpl.doSome(..))", throwing = "ex")
    public void myAfterThrowing(Exception ex){
        System.out.println("异常通知,在目标方法抛出异常的时候执行。异常原因:" + ex.getMessage());
        /*
            异常发生可以做:
                1、记录异常的时间、位置等信息
                2、发送邮件、短信,通知开发人员
         */
    }
}

3.2.7、最终通知@After

最终通知方法的定义:

  1. 方法是 public
  2. 方法是没有返回值的,void
  3. 方法名称自定义
  4. 方法没有参数

Spring配置文件未做改变

@Aspect
public class MyAspect {
	/**
     *  @After:最终通知
     *      属性: value ,切入点表达式
     *      位置:在方法的上面
     *  特点:
     *      1)在目标方法之后执行
     *      2)总是会被执行
     *      3)可以用来作程序的收尾工作,例如清除临时数据、变量,清理内存
     */
    @After(value = "execution(* *..SomeServiceImpl.doSome(..))")
    public void myAfter(){
        System.out.println("最终通知,总是会被执行的");
    }
}

3.2.8、@Pointcut 定义切入点

当使用较多相同的 execution 切入点表达式时,可使用 @Pointcut 注解,用于定义 execution 切入点表达式

注解 @Pointcut 放在一个方法上,以后所有的 execution 的 value 属性值均可使用该方法名作为切入点

@Aspect
public class MyAspect {
	/**
     *  @Pointcut:定义和管理切入点,不是通知注解
     *      属性:value 切入点表达式
     *      位置:在一个自定义方法上面,mpt()方法名看做是切入点表达式execution(* *..SomeServiceImpl.doSome(..))的别名
     *             其他的通知注解中,可以使用方法名,就表示使用这个切入点表达式
     *  自定义方法一般定义为 private ,防止其他地方调用
     */
    @Pointcut(value = "execution(* *..SomeServiceImpl.doSome(..))")
    private void mpt(){
        //方法里面无需代码
    }


    //这里 mpt() 就相当于 execution(* *..SomeServiceImpl.doSome(..))
    @After(value = "mpt()")
    public void myAfter(){
        System.out.println("最终通知,总是会被执行的");
    }
}

3.3、总结

AOP 是一种动态的技术思想,目的是实现业务功能和非业务功能的耦合。业务功能是独立的模块,其他功能也是独立的模块

例如 事务功能、日志功能等,让这些事务、日志功能是可以被复用的

当目标方法需要一些功能时,可以在不修改、不能修改源代码的情况下,使用 AOP 技术在程序执行期间,生产代理对象,通过代理执行业务方法,同时增加功能

3.4、练习

使用 AOP 做方法的参数检查

要求:

  1. 当 addNumber 方法的参数不为null、参数大于0 的时候,才可以执行 addNumber() 方法计算
  2. 如果任意一个参数是 null 、或者小于 0,则调用 addNumber 方法返回结果是 -1

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: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/aop https://www.springframework.org/schema/aop/spring-aop.xsd">
    <bean id="numberServiceImpl" class="com.service.impl.NumberServiceImpl"/>
    <bean id="myAspect" class="com.aspect.MyAspect"/>
    <aop:aspectj-autoproxy/>
</beans>

切面类

package com.aspect;

import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;

@Aspect
public class MyAspect {
    @Around(value = "execution(* *..NumberServiceImpl.addNumber(..))")
    public Object myAround(ProceedingJoinPoint pjp) throws Throwable {
        System.out.println("环绕通知");
        Object methodResult = null;
        Object[] args = pjp.getArgs();
        for (Object arg : args) {
            if (arg == null || (Integer)arg <= 0){
                methodResult = -1;
                break;
            }else {
                methodResult = pjp.proceed();
            }
        }
        return methodResult;
    }
}

实现类

public class NumberServiceImpl implements NumberService {
    @Override
    public Integer addNumber(Integer n1, Integer n2, Integer n3) {
        return n1+n2+n3;
    }
}

测试类

import com.service.NumberService;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class MyTest {
    @Test
    public void test1(){
        ApplicationContext appc = new ClassPathXmlApplicationContext("applicationcontext.xml");
        NumberService numberService = (NumberService) appc.getBean("numberServiceImpl");
        Integer integer = numberService.addNumber(23, 23, 23);
        System.out.println(integer);
    }
}

4、Spring 集成 MyBatis

4.1、集成思路

Spring 能集成很多的框架,是Spring的一个有事功能,通过集成功能,让开发人员使用其他框架更方便,集成使用的是 Spring IOC 核心技术

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-IMzcvmHM-1644638852248)(C:\Users\ASUS\AppData\Roaming\Typora\typora-user-images\image-20220209101720395.png)]

使用 MyBatis,需要创建 MyBatis 框架中的某些对象,使用这些对象,就能使用 MyBatis 提供的功能了

分析:MyBatis 执行 SQL 语句,要使用哪些对象?

  1. 需要有 dao 接口的代理对象,例如 StudentDao 接口,需要它的代理对象

    使用 SqlSession.getMapper(StudenDao.class),得到 dao 代理对象

  2. 需要有 SqlSessionFactory ,创建 SqlSessionFactory 对象,才能使用 openSession() 得到 SqlSession 对象

  3. 数据源 DataSource 对象,使用一个更强大、功能更多的连接池对象代替 MyBatis 自己的 PooledDataSource

4.2、集成步骤

实现步骤:

  1. 使用 MySQL 库,使用学生表

  2. 创建 maven 项目

  3. 加入依赖

    spring依赖、mybatis依赖、mysql驱动、Junit依赖、mybatis-spring依赖(用来在spring中创建mybatis对象)、sprig有关事务的依赖

  4. 创建实体类 Student

  5. 创建 Dao 接口和 mapper 文件写 sql 语句

  6. MyBatis 主配置文件

  7. 新建 service 接口和其实现类

  8. 创建spring的配置文件

    1. 声明数据源DataSource,使用功能阿里的Druid连接池
    2. 声明 SQLSessionFactoryBean类,在这个类内部创建SQLSessionFactory对象
    3. 声明MapperScannerConfiguration类,在内部创建dao代理对象,创建的对象都放在spring容器中
    4. 声明service对象,将上面 3 中的 dao代理对象 赋值给service属性
  9. 测试dao访问数据库

4.2.1、pom.xm文件

<properties>
  <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
  <maven.compiler.source>1.8</maven.compiler.source>
  <maven.compiler.target>1.8</maven.compiler.target>
</properties>

<dependencies>
  <dependency>
    <groupId>junit</groupId>
    <artifactId>junit</artifactId>
    <version>4.11</version>
    <scope>test</scope>
  </dependency>
  <!--spring依赖-->
  <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.1</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.9</version>
  </dependency>
  <!--阿里连接池-->
  <dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>druid</artifactId>
    <version>1.1.12</version>
  </dependency>
  <!-- lombok -->
  <dependency>
    <groupId>org.projectlombok</groupId>
    <artifactId>lombok</artifactId>
    <version>1.18.12</version>
    <scope>provided</scope>
  </dependency>
</dependencies>

<build>
  <resources>
    <resource>
      <directory>src/main/java</directory>
      <includes>
        <include>**/*.properties</include>
        <include>**/*.xml</include>
      </includes>
      <filtering>false</filtering>
    </resource>
  </resources>
</build>

4.2.2、Dao 接口和 mapper 文件

dao接口:com.afei.dao.StudentDao

public interface StudentDao {
    int insertStudent(Student student);
    List<Student> selectStudents();
}

Mapper文件:com.afei.dao.StudentDao.xml

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">

<mapper namespace="com.afei.dao.StudentDao">
    <select id="selectStudents" resultType="com.afei.domain.Student">
        SELECT * FROM `student`
    </select>
    <insert id="insertStudent" >
        insert into `student`(name,age) values(#{name},#{age})
    </insert>
</mapper>

4.2.3、MyBatis 主配置文件

主配置文件:resources/mybatis.xml

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
        PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
    <!--设置日志-->
    <settings>
        <setting name="logImpl" value="STDOUT_LOGGING"/>
    </settings>
    <!--别名-->
    <typeAliases>
        <package name="com.afei.domain"/>
    </typeAliases>
    <!--指定其他 mapper 文件的位置-->
    <mappers>
        <mapper resource="com/afei/dao/StudentDao.xml"/>
        <!--使用要求:
                1)Mapper文件和dao接口在同一目录
                2)Mapper文件和dao接口名称一致
        -->
        <!--  <package name="com.afei.dao"/>  -->
    </mappers>
</configuration>

4.2.4、service实现类

注意要有 set 方法

@Setter
public class StudentServiceImpl implements StudentService {
    private StudentDao studentDao;

    @Override
    public int addStudent(Student student) {
        return studentDao.insertStudent(student);
    }

    @Override
    public List<Student> queryStudents() {
        return studentDao.selectStudents();
    }
}

4.2.5、Spring 配置文件★★★★★★

  1. 声明数据源DataSource,使用功能阿里的Druid连接池
  2. 声明 SQLSessionFactoryBean类,在这个类内部创建SQLSessionFactory对象
  3. 声明MapperScannerConfiguration类,在内部创建dao代理对象,创建的对象都放在spring容器中
  4. 声明service对象,将上面 3 中的 dao代理对象 赋值给service属性
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd">

    <!--加载外部的属性配置文件-->
    <context:property-placeholder location="classpath:jdbc.properties"/>
    
    <!-- 声明数据源 DataSource -->
    <bean id="myDataSources" class="com.alibaba.druid.pool.DruidDataSource">
        <property name="url" value="${jdbc.url}"/>
        <property name="username" value="${jdbc.username}"/>
        <property name="password" value="${jdbc.password}"/>
        <!--Druid会根据URL自动识别驱动类名,就不需要配置属性driverClassName-->
    </bean>

    <!--声明SqlSessionFactoryBean
            在该类内部创建 SqlSessionFactory,这个bean创建的对象的数据类型是 SqlSessionFactory 类型
            底层SqlSessionFactoryBean还是使用SqlSessionFactoryBuilder创建SqlSessionFactory对象
    -->
    <bean id="factory" class="org.mybatis.spring.SqlSessionFactoryBean">
        <!--指定数据源-->
        <property name="dataSource" ref="myDataSources"/>
        <!--指定mybatis主配置文件
                Resource可以直接使用 value属性赋值,value指定主配置文件的路径
        -->
        <property name="configLocation" value="classpath:mybatis.xml"/>
    </bean>

    <!--声明 MapperScannerConfigurer
            作用:循环basePackage所表示的包,将包中的每个接口都找到,然后调用 sqlSession.getMapper(StudentDao.class)
                   对找到的每个接口都创建出 dao 代理对象,dao 代理对象放在容器中,对象名即为首字母小写的接口类名

            SqlSession sqlSession = 容器对象.getBean("factory").openSession();
            遍历basePackage所表示的包 : 接口 对象 = sqlSession.getMapper(接口);
                                        SpringMap.put(首字母小写的接口类名, 对象);
    -->
    <bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
        <!--指定 SqlSessionFactory对象的名称-->
        <property name="sqlSessionFactoryBeanName" value="factory"/>
        <!--指定基本包:dao接口所在的包名-->
        <property name="basePackage" value="com.afei.dao"/>
    </bean>

    <!--声明service-->
    <bean id="studentService" class="com.afei.service.impl.StudentServiceImpl">
        <!--这里能直接使用ref="studentDao",也是因为在上面MapperScannerConfigurer中,
                    生成了dao代理对象,对象名即为首字母小写的接口类名
        -->
        <property name="studentDao" ref="studentDao"/>
    </bean>
</beans>

5、Spring事务

5.1、事务概念

什么事事务?事务是一些 SQL 序列的集合,是多条SQL,作为一个整体执行

mysql执行
beginTransaction 开启事务
insert into student() values...
select * from student where id = 1005
update school set name=xxx where id = 1005
endTransaction 结束事务

什么情况下需要使用事务?

一个操作需要多条(2条或以上的SQL)SQL语句一起完成,操作才能成功

事务:加在业务类的方法上面(public方法上面),表示业务方法执行时,需要事务的支持

public class StudentServiceImpl implements StudentService {
    private StudentDao studentDao;
    private SchoolDao schoolDao;

    @Override
    public int addStudent() {
        studentDao.insertStudent();
        schoolDao.insertSchool();
    }
}

Spring统一管理事务概念

不同的数据库访问技术,处理事务的代码是不同的,Spring统一管理事务,把不同的数据库访问技术的事务处理统一起来

使用 Spring 的事务管理器,管理不同数据库访问技术的事务处理,开发人员只需要掌握 Spring 的事务处理一个方案,就可以实现使用不同数据库访问技术的事务管理

管理事务面向的是 Spring,有 Spring管理事务,做事务提交,事务回滚

例如:使用 jdbc 访问数据库,事务处理如下

public void updateAccount(){
    Connection conn = ...;
    conn.setAutoCommit(false);
    stat.insert();
    stat.update();
    stat.commit();
    conn.setAutoCommit(true);
}

例如:mybatis 执行数据库,事务处理如下

public void updateAccount(){
    SqlSession sqlSession = SqlSession.openSession(false);
    try{
        sqlSession.insert("insert into student...");
        sqlSession.update("update school...");
        sqlSession.commit();
    }catch(Exception e){
        sqlSession.rollback();
    }
}

5.2、事务管理器接口

Spring 框架使用事务管理器对象,管理所有的事务

事务管理器接口:PlatformTransactionManager

​ 作用:定义了事务的操作,主要是 commit(),rollback()

事务管理器接口有很多实现类:一种数据库的访问技术对应着有一个实现类,由实现类具体完成事务的提交,回滚

  1. jdbc或者mybatis访问数据库有自己的事务管理器实现类:DataSourceTransactionManager
  2. hibernate框架:HibernateTransactionManager
事务管理器工作方式:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-fep5WTjT-1644638852248)(C:\Users\ASUS\AppData\Roaming\Typora\typora-user-images\image-20220209101533958.png)]

事务提交和事务回滚的时机:

什么时候提交、回滚?

方法中抛出了运行时异常【事务回滚】
方法正常执行、抛出受查异常【事务提交】

异常分类:

  1. Error:严重错误。【回滚事务】
  2. Exception:异常类
    1. 运行时异常:RuntimeException 及其子类都是运行时异常【回滚事务】,例如 NullPointException、NumberFormatException、ArithmeticException、IndexOutOfBoundException
    2. 受查异常(编译时异常):必须处理的异常,否则编译不通过【提交事务】,例如 IOException、SQLException、FileNotFoundException
事务底层实现:

事务底层使用的是 AOP 的环绕通知,可以在目标方法的前后都增强功能,不需要修改目标原代码

spring给业务方法执行时,增加上事务的切面功能
@Around(value = "execution(* 所有业务类中的方法")
public Object myAround(ProceedingJoinPoint pjp){
    try{
        PlatformTransactionManager.begeinTransaction();   //使用功能Spring事务管理器,开启事务
        pjd.proceed();  //执行目标方法 doSome()
        PlatformTransactionManager.commit();   //业务方法正常执行,提交事务
    }catch(Exception e){
        PlatformTransactionManager.rollback();   //回滚事务
    }
}

5.3、事务定义接口

事务定义接口 TransactionDefinition ,其中定义了事务描述相关的三类常量:

1)事务隔离级别、2)事务传播行为、3)事务默认超时时限

给业务方法说明事务属性,和 ACID 不一样

5.3.1、事务隔离级别

隔离级别:控制事务之间影响的程度

5 个常量,定义 4 个隔离级别

  1. DEFAULT :采用 DB 默认的事务隔离级别,如mysql默认REPEATABLE_READ 、oracle默认READ_COMMITTED
  2. READ_UNCOMMITTED:读未提交,未解决任何并发问题
  3. READ_COMMITTED:读已提交,解决脏读问题,存在不可重复读、幻读问题
  4. REPEATABLE_READ:可重复读,解决脏读、不可重复读问题,存在幻读问题
  5. SERIALIZABLE:串行化,解决所有并发问题

5.3.2、超时时限

超时时间,以秒为单位,是增数值,默认为 -1

表示一个业务方法最长的执行时间,到达时间没有执行完毕,便回滚事务

5.3.3、传播行为

有 7 个值,表示业务方法在调用时,事务在方法之间的,传递和使用。使用传播行为,标识方法有无事务

  1. PROPAGATION_REQUIRED

    spring默认传播行为,方法在调用的时候,如果存在事务就使用当前事务;如果没有事务,就新建事务,方法在新事务中执行

  2. PROPAGATION_REQUIRES_NEW

    方法有事务可以正常执行,没有事务也可以正常执行 (业务查询操作)

  3. PROPAGATION_SUPPORTS

    方法需要一个新事务。如果调用方法时,存在事务,则原来的事务暂停,直到新事务执行完毕;

    如果方法调用时没有事务,则新建事务,在新事务中执行代码

以上三个需要掌握

PROPAGATION_MANDATORY

PROPAGATION_NESTED

PROPAGATION_NEVER

PROPAGATION_NOT_SUPPORTED

5.4、程序举例(环境搭建)

实现步骤:

  1. 使用 MySQL 库,使用sale表、goods表

  2. 创建 maven 项目

  3. 加入依赖

    spring依赖、mybatis依赖、mysql驱动、Junit依赖、mybatis-spring依赖(用来在spring中创建mybatis对象)、sprig有关事务的依赖

  4. 创建实体类 Sale、Goods

  5. 创建 Dao 接口和 mapper 文件

    SaleDao、GoodSale,两个 mapper 文件

  6. MyBatis 主配置文件

  7. 新建 service 接口及其实现类,实现 buy 的方法

  8. 创建spring的配置文件

    1. 声明数据源DataSource,使用功能阿里的Druid连接池
    2. 声明 SQLSessionFactoryBean类,在这个类内部创建SQLSessionFactory对象
    3. 声明MapperScannerConfiguration类,在内部创建dao代理对象,创建的对象都放在spring容器中
    4. 声明service对象,将上面 3 中的 dao代理对象 赋值给service属性
  9. 测试

5.4.1、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.afei</groupId>
    <artifactId>springjdbc</artifactId>
    <version>1.0-SNAPSHOT</version>

    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <maven.compiler.source>1.8</maven.compiler.source>
        <maven.compiler.target>1.8</maven.compiler.target>
    </properties>

    <dependencies>
        <!-- lombok -->
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <version>1.18.12</version>
            <scope>provided</scope>
        </dependency>
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.11</version>
            <scope>test</scope>
        </dependency>
        <!--spring依赖-->
        <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.1</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.9</version>
        </dependency>
        <!--阿里连接池-->
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid</artifactId>
            <version>1.1.12</version>
        </dependency>
    </dependencies>

    <build>
        <resources>
            <resource>
                <directory>src/main/java</directory>
                <includes>
                    <include>**/*.properties</include>
                    <include>**/*.xml</include>
                </includes>
                <filtering>false</filtering>
            </resource>
        </resources>
    </build>

</project>

5.4.2、实体类

@Data
public class Goods {
    private Integer id;
    private String name;
    private Integer amount;
    private Float price;
}
@Data
public class Sale {
    private Integer id;
    private Integer gid;
    private Integer num;
}

5.4.3、dao接口与mapper

GoodsDao

public interface GoodsDao {
    Goods selectById(Integer id);

    //goods表示本次购买的商品id和购买数量amount
    //更新商品库存
    int updateGoods(Goods goods);
}
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">

<mapper namespace="com.dao.GoodsDao">
    <select id="selectById" resultType="com.domain.Goods">
        select * from goods where id = #{id}
    </select>
    <update id="updateGoods" >
        update goods set amount = amount-#{amount} where id = #{id}
    </update>
</mapper>

SaleDao

public interface SaleDao {
    int insertSale(Sale sale);
}
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">

<mapper namespace="com.dao.SaleDao">
    <insert id="insertSale" >
        insert into sale(gid,num) values(#{gid},#{num})
    </insert>
</mapper>

5.4.4、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>
    <!--设置日志-->
    <settings>
        <setting name="logImpl" value="STDOUT_LOGGING"/>
    </settings>
    <!--别名-->
    <typeAliases>
        <package name="com.domain"/>
    </typeAliases>
    <!--指定其他 mapper 文件的位置-->
    <mappers>
        <mapper resource="com/dao/GoodsDao.xml"/>
        <mapper resource="com/dao/SaleDao.xml"/>
        <!--使用要求:
                1)Mapper文件和dao接口在同一目录
                2)Mapper文件和dao接口名称一致
        -->
        <!--  <package name="com.dao"/>  -->
    </mappers>
</configuration>

5.4.5、service接口的实现类

@Setter
public class BuyGoodsServiceImpl implements BuyGoodsService {
    private GoodsDao goodsDao;
    private SaleDao saleDao;

    @Override
    public void buy(Integer goodsId, Integer num) {
        System.out.println("===buy方法的开始===");
        //生成销售记录
        Sale sale = new Sale();
        sale.setGid(goodsId);
        sale.setNum(num);
        int rows = saleDao.insertSale(sale);
        //查询商品
        Goods goods = goodsDao.selectById(goodsId);
        if (goods == null){
            throw new NullPointerException(goodsId + "商品不存在");
        }else if (goods.getAmount() < num){
            throw new NotEnoughException(goodsId +"库存不足");
        }
        //更新库存
        Goods buyGoods = new Goods();
        buyGoods.setId(goodsId);
        buyGoods.setAmount(num);
        goodsDao.updateGoods(buyGoods);
        System.out.println("===buy方法的完成===");
    }
}

5.4.6、spring配置文件

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">

    <!--声明数据源-->
    <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
        <property name="url" value="jdbc:mysql://localhost:3306/atguigu"/>
        <property name="username" value="root"/>
        <property name="password" value="admin"/>
    </bean>
    <!--声明SqlSessionFactoryBean-->
    <bean id="factory" class="org.mybatis.spring.SqlSessionFactoryBean">
        <property name="dataSource" ref="dataSource"/>
        <property name="configLocation" value="classpath:mybatis.xml"/>
    </bean>
    <!--声明MapperScannerConfiguration,内部dao代理类-->
    <bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
        <property name="basePackage" value="com.dao"/>
        <property name="sqlSessionFactoryBeanName" value="factory"/>
    </bean>
    <!--创建service对象-->
    <bean id="buyGoodsServiceImpl" class="com.service.impl.BuyGoodsServiceImpl">
        <property name="goodsDao" ref="goodsDao"/>
        <property name="saleDao" ref="saleDao"/>
    </bean>
</beans>

5.5、注解方式@Transactional

@Transactional 注解,使用注解的属性控制事务(隔离级别,传播行为,超时)

注解的属性:

  1. propagation:设置事务传播属性,使用 Propagation 类的枚举值,默认为 Propagation.REQUIRED
  2. isolation:设置事务隔离级别,使用 Isolation 类的枚举值,默认为 Isolation.DEFAULT
  3. readOnly:是boolean类型,表示数据库操作是不是只读,默认是 false
  4. timeout:设置本操作与数据库连接的超时时间,单位为秒,int 类型,默认 -1
  5. rollbackFor:表示回滚的异常类列表,值是一个数组,每个值是异常类型的 class
  6. rollbackForClassName:表示回滚的异常类列表,值是异常类名称,是 String 类型的值
  7. noRollbackFor:表示不需要回滚的异常类列表,是 class 类型
  8. noRollbackForClassName:表示不需要回滚的异常类列表的名称,是 String 类型

注解的位置:

  1. 在业务方法上面,public 类型
  2. 在类的上面

注解的使用步骤:

  1. 在 spring 的配置文件,声明事务的内容

    声明事务管理器,说明使用哪个事务管理器对象

    声明使用注解管理事务,开启注解驱动

  2. 在类的源代码中,加入 @Transactional

修改 spring 配置文件:

注意事务驱动使用哪一个

<!--声明事务管理器-->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
    <!--指定数据源-->
    <property name="dataSource" ref="dataSource"/>
</bean>
<!--开启事务驱动:
    告诉框架使用注解管理事务
    transaction-manager:指定事务管理器的 id 
        数据源标签bean可以有多个,事务管理器标签bean可以有多个,在此指定一个
-->
<tx:annotation-driven transaction-manager="transactionManager"/>
添加 @TRansactional 注解:

注解使用的特点:

  1. 一定在 public 方法上面
  2. 适用于中小型项目,使用方便
package com.service.impl;

import com.dao.GoodsDao;
import com.dao.SaleDao;
import com.domain.Goods;
import com.domain.Sale;
import com.exception.NotEnoughException;
import com.service.BuyGoodsService;
import lombok.Setter;
import org.springframework.transaction.annotation.Transactional;

@Setter
public class BuyGoodsServiceImpl implements BuyGoodsService {
    private GoodsDao goodsDao;
    private SaleDao saleDao;

    /**
     *  @Transactional 注解加在 public 方法的上面,表示方法有事务功能
     *  1、第一种方式:
     *      @Transactional(
     *                 propagation = Propagation.REQUIRED,
     *                 isolation = Isolation.DEFAULT,
     *                 readOnly = false,
     *                 timeout = 20,
     *                 rollbackFor = {NullPointerException.class , NotEnoughException.class}
     *      )
     *  2、第二种方式:
     *      @Transactional(
     *                 propagation = Propagation.REQUIRED,
     *                 isolation = Isolation.DEFAULT,
     *                 readOnly = false,
     *                 timeout = 20
     *      )
     *      rollbackFor的使用:
     *          1)框架首先检查方法抛出的异常是不是rollbackFor的数组中,若是,一定会滚
     *          2)如果方法抛出的异常不在rollbackFor数组中,框架会继续检查是不是 RuntimeException
     *                      如果第 RuntimeException 异常,继续回滚
     *  3、第三种方式:
     *      @Transactional
     *      此时注解里面的属性都是使用默认值,默认 Propagation.REQUIRED,默认运行时异常回滚
     */
    @Transactional
    @Override
    public void buy(Integer goodsId, Integer num) {
        System.out.println("===buy方法的开始===");
        //生成销售记录
        Sale sale = new Sale();
        sale.setGid(goodsId);
        sale.setNum(num);
        int rows = saleDao.insertSale(sale);
        //查询商品
        Goods goods = goodsDao.selectById(goodsId);
        if (goods == null){
            throw new NullPointerException(goodsId + "商品不存在");
        }else if (goods.getAmount() < num){
            throw new NotEnoughException(goodsId +"库存不足");
        }
        //更新库存
        Goods buyGoods = new Goods();
        buyGoods.setId(goodsId);
        buyGoods.setAmount(num);
        goodsDao.updateGoods(buyGoods);
        System.out.println("===buy方法的完成===");
    }
}

5.6、xml配置文件方式

使用 AspectJ 的 AOP ,在spring配置文件中声明事务控制

使用步骤:

  1. pom.xml 文件中加入 spring-aspects 依赖

    <!-- spring-aspects -->
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-aspects</artifactId>
      <version>5.2.5.RELEASE</version>
    </dependency>
    
  2. 在 spring 配置文件中声明事务内容

    1. 声明事务管理器对象

    2. 声明业务方法需要的事务属性

      [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ZRZ80EQp-1644638852249)(C:\Users\ASUS\AppData\Roaming\Typora\typora-user-images\image-20220206173142013.png)]

    3. 声明切入点表达式

<?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: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/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">
    
    <!--声明数据源-->
    <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
        <property name="url" value="jdbc:mysql://localhost:3306/book"/>
        <property name="username" value="root"/>
        <property name="password" value="admin"/>
    </bean>
    <!--声明SqlSessionFactoryBean-->
    <bean id="factory" class="org.mybatis.spring.SqlSessionFactoryBean">
        <property name="dataSource" ref="dataSource"/>
        <property name="configLocation" value="classpath:mybatis.xml"/>
    </bean>
    <!--声明MapperScannerConfiguration,内部dao代理类-->
    <bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
        <property name="basePackage" value="com.dao"/>
        <property name="sqlSessionFactoryBeanName" value="factory"/>
    </bean>
    <!--创建service对象-->
    <bean id="buyGoodsServiceImpl" class="com.service.impl.AddStudentServiceImpl">
        <property name="dao" ref="studentDao"/>
    </bean>

    
    <!--1、声明事务管理器-->
    <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <!--指定数据源-->
        <property name="dataSource" ref="dataSource"/>
    </bean>
    <!--2、声明业务方法的事务属性(隔离级别、传播行为、超时)
            id:给业务方法配置事务段代码名称,唯一值
            transaction-manager:事务管理器的id
    -->
    <tx:advice id="serviceAdvice" transaction-manager="transactionManager">
        <!--给具体的业务方法增加事务的说明-->
        <tx:attributes>
            <!--
                给具体的业务方法,说明它需要的事务属性
                name:业务方法名称,配置 name 的值:
                    1)业务方法名称
                    2)带有部分通配符*的方法名称,如 addStudent、addSchool、addTeacher方法,使用add*
                    3)使用 *
                propagation:指定传播行为
                isolation:隔离级别
                read-only:是否只读,默认是false
                timeout:超时时间
                rollback-for:指定回滚的异常类型,使用异常类的全限定名称
            -->
            <tx:method name="buy" rollback-for="java.lang.NullPointerException , com.exception.NotEnoughException"/>
            
            <!-- 在业务方法有命名规则后,可以对一些方法批量使用事务 -->
            <tx:method name="add*" propagation="REQUIRES_NEW"/>
            <tx:method name="remove*" propagation="REQUIRED"/>
            <tx:method name="modify*" propagation="REQUIRED"/>
            
            <!-- 以上方法以外的 -->
            <tx:method name="*" propagation="SUPPORTS"/>
        </tx:attributes>
    </tx:advice>
    <!-- 3、声明切入点表达式:
        上面一步是给具体的业务方法,这一步就是表示哪些包中的类、类中的方法参与事务
    -->
    <aop:config>
        <!-- 声明切入点表达式
            expression:切入点表达式,表示哪些类和类中的方法要参与事务
                下面的式子表示所有包内有 service 包的所有子包内的所有类的所有方法
            id:切入点表达式的名称,唯一性
         -->
        <aop:pointcut id="servicepointcut" expression="execution(* *..service..*.*(..))"/>
        <!-- 关联切入点表达式和事务通知 -->
        <aop:advisor advice-ref="serviceAdvice" pointcut-ref="servicepointcut"/>
    </aop:config>
</beans>

声明式事务优缺点:

  1. 理解难、配置复杂
  2. 代码和事务配置分开,控制事务源代码不用修改
  3. 能快速了解项目全部事务,适合大型项目

6、Spring 与 Web

6.1、环境配置

完成学生注册功能,步骤:

  1. 新建maven,修改 pom.xml
  2. 创建实体类 Student
  3. dao接口和mapper文件
  4. 创建 mybatis 主配置文件
  5. service接口和实现类
  6. servlet,接收请求参数,调用service对象
  7. 创建 jsp ,提交请求参数
  8. 创建jsp,作为视图,显示请求的处理结果
  9. 创建 spring 配置文件

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.afei</groupId>
  <artifactId>demo02</artifactId>
  <version>1.0-SNAPSHOT</version>
  <packaging>war</packaging>

  <properties>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    <maven.compiler.source>1.8</maven.compiler.source>
    <maven.compiler.target>1.8</maven.compiler.target>
  </properties>

  <dependencies>
    <dependency>
      <groupId>junit</groupId>
      <artifactId>junit</artifactId>
      <version>4.11</version>
      <scope>test</scope>
    </dependency>
    <!-- servlet -->
    <dependency>
      <groupId>javax.servlet</groupId>
      <artifactId>javax.servlet-api</artifactId>
      <version>3.1.0</version>
    </dependency>
    <!-- jsp -->
    <dependency>
      <groupId>javax.servlet.jsp</groupId>
      <artifactId>jsp-api</artifactId>
      <version>2.2.1-b03</version>
    </dependency>
    <!-- 监听器依赖 -->
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-web</artifactId>
      <version>5.2.5.RELEASE</version>
    </dependency>
    <!-- xml配置文件方式配置事务的依赖 -->
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-aspects</artifactId>
      <version>5.2.5.RELEASE</version>
    </dependency>
    <!-- spring依赖 -->
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-context</artifactId>
      <version>5.3.1</version>
      <scope>compile</scope>
    </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.1</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.9</version>
    </dependency>
    <!--阿里连接池-->
    <dependency>
      <groupId>com.alibaba</groupId>
      <artifactId>druid</artifactId>
      <version>1.1.12</version>
    </dependency>
    <!-- lombok -->
    <dependency>
      <groupId>org.projectlombok</groupId>
      <artifactId>lombok</artifactId>
      <version>1.18.12</version>
      <scope>provided</scope>
    </dependency>
  </dependencies>

  <build>
    <resources>
      <resource>
        <directory>src/main/java</directory>
        <includes>
          <include>**/*.properties</include>
          <include>**/*.xml</include>
        </includes>
        <filtering>false</filtering>
      </resource>
    </resources>
    <plugins>
      <!-- 插件 Tomcat7 -->
      <plugin>
        <groupId>org.apache.tomcat.maven</groupId>
        <artifactId>tomcat7-maven-plugin</artifactId>
        <version>2.1</version>
        <configuration>
          <port>8080</port> <!-- 端口 -->
          <path>/</path> <!-- 上下路径 -->
          <uriEncoding>UTF-8</uriEncoding> <!-- 针对 GET 方式乱码处理 -->
        </configuration>
      </plugin>
    </plugins>
  </build>
</project>

实体类 Student

@Data
public class Student {
    private Integer stuId;
    private String stuName;
    private Integer stuAge;
}

dao接口和mapper文件

package com.dao;

import com.domain.Student;
import org.apache.ibatis.annotations.Param;

public interface StudentDao {
    int insertStudent(Student student);
    Student selectById(@Param("studentid")Integer id);
}
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="dao接口的全限定名称com.dao.StudentDao">
    <!-- 参数是自定义对象时,#{属性名称} -->
    <insert id="insertStudent">
        insert into student(name,age) values(#{stuName},#{stuAge})
    </insert>

    <!-- 定义列和属性的对应关系 -->
    <resultMap id="studentMap" type="com.domain.Student">
        <id column="id" property="stuId"/>
        <result column="name" property="stuName"/>
        <result column="age" property="stuAge"/>
    </resultMap>
    <!-- 参数是基本类型时,#{@Param注解的值} -->
    <select id="selectById" resultMap="studentMap">
        select * from studnet where id = #{studentid}
    </select>
</mapper>

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>
    <!--设置sql日志 打印在控制台-->
    <settings>
        <setting name="logImpl" value="STDOUT_LOGGING"/>
    </settings>
    <!--配置别名-->
    <typeAliases>
        <!--此包下的实体类名===别名-->
        <package name="com.domain.Student"/>
    </typeAliases>

    <!--管理映射源文件-->
    <mappers>
        <!--加载 dao 包中所有 mapper 文件-->
        <package name="com.dao"/>
    </mappers>

</configuration>

service接口和实现类

package com.service.impl;

import com.dao.StudentDao;
import com.domain.Student;
import com.service.StudentService;
import lombok.Setter;

@Setter
public class StudentServiceImpl implements StudentService {
    private StudentDao dao;

    @Override
    public int addStudent(Student student) {
        return dao.insertStudent(student);
    }

    @Override
    public Student queryById(Integer id) {
        return dao.selectById(id);
    }
}

servlet 接收请求参数

package com.controller;

import com.domain.Student;
import com.service.StudentService;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

public class AddStudentServlet extends HttpServlet {
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        String name = request.getParameter("name");
        String age = request.getParameter("age");

        //调用service
        ApplicationContext appc = new ClassPathXmlApplicationContext("applicationContext.xml");
        StudentService service = (StudentService) appc.getBean("studentServiceImpl");

        Student student = new Student();
        student.setStuName(name);
        student.setStuAge(Integer.valueOf(age));
        service.addStudent(student);
        //给用户显示处理结果
        request.getRequestDispatcher("/show.jsp").forward(request,response);
    }
}

6.2、现在容器对象的问题

问题:

  1. 创建容器对象次数多,没调用一次 service ,就要创建一次容器对象
  2. 在多个 servlet 中,分别创建容器对象

当创建容器对象的时候,spring 配置文件中的对象都会一同被创建

现在需要一个什么样的容器对象?

  1. 容器对象只有一个,创建一次即可
  2. 容器对象应该在整个项目中共有,多个 servlet 都能使用同一个容器对象

解决问题:

  1. 使用监听器 ServletContextListener (内含两个方法:初始时执行的,销毁时执行的)
  2. 在监听器中创建好的容器对象,应该放在 web 应用中的 ServletContext 作用域中

6.3、ContextLoaderListener 监听器

ContextLoaderListener 是一个监听器对象,是 ServletContextListener 的实现类,是spring框架提供的

使用这个监听器作用:

  1. 创建容器对象,一次
  2. 把容器对象放到 ServletContext 作用域

步骤:

  1. 加入 spring-web 依赖

    <!-- spring-web -->
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-web</artifactId>
      <version>5.2.5.RELEASE</version>
    </dependency>
    
  2. web.xml 声明监听器

    <!DOCTYPE web-app PUBLIC
     "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
     "http://java.sun.com/dtd/web-app_2_3.dtd" >
    
    <web-app>
        <!--声明监听器
                1、默认监听器:创建容器对象是,读取配置文件:/WEB_INF/applicationContext.xml
                2、自定义日期使用配置文件的路径:使用 context-param 标签
        -->
        <!--自定义spring容器使用的配置文件路径
            context-param 叫做上下文路径,给监听器提供参数的
        -->
        <context-param>
            <!-- contextConfigLocation:固定名称,表示自定义spring配置文件路径 -->
            <param-name>contextConfigLocation</param-name>
            <!-- 自定义配置文件路径 -->
            <param-value>classpath:applicationContext.xml ,classpath:myspring.xml</param-value>
        </context-param>
        <listener>
            <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
        </listener>
    
        <servlet>
            <servlet-name>AddStudentServlet</servlet-name>
            <servlet-class>com.controller.AddStudentServlet</servlet-class>
        </servlet>
        <servlet-mapping>
            <servlet-name>AddStudentServlet</servlet-name>
            <url-pattern>/add</url-pattern>
        </servlet-mapping>
    </web-app>
    

6.4、监听器源代码

public class ContextLoaderListener extends ContextLoader implements ServletContextListener {

    //监听器的初始方法
    public void contextInitialized(ServletContextEvent event) {
        this.initWebApplicationContext(event.getServletContext());
    }

}
private WebApplicationContext context;

public WebApplicationContext initWebApplicationContext(ServletContext servletContext){
	try {
        if (this.context == null) {
            //创建spring的容器对象
            this.context = this.createWebApplicationContext(servletContext);
        }
        //把容器对象放入 ServletContext 作用域
        /*
        	key = WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE
        	value = 容器对象
        */
        servletContext.setAttribute(
            WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE,
            this.context
        );
    } catch (Error | RuntimeException var8) {}
}

//WebApplicationContext是web项目中使用的容器对象
public interface WebApplicationContext extends ApplicationContext {
    String ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE = WebApplicationContext.class.getName() + ".ROOT";
}

6.5、使用ContextLoaderListener监听器

自己手动编写来获取spring容器对象

//使用监听器已经创建好了的容器对象,从 ServletContext 作用域获取容器

WebApplicationContext wappc = null;
String key = WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE;
//获取 ServletContext 对象
ServletContext sc = getServletContext();   // servlet中的方法获取 ServletContext 对象
//ServletContext sc = request.getServletContext();  请求对象来获取 ServletContext 对象
Object attribute = sc.getAttribute(key);
if(attribute != null){
    wappc = (WebApplicationContext) attribute;
}

StudentService service = (StudentService) wappc.getBean("studentServiceImpl");

使用 spring 提供的工具方法,获取容器对象

//使用 spring 提供的工具方法,获取容器对象
WebApplicationContext wappc = WebApplicationContextUtils
    						.getRequiredWebApplicationContext(getServletContext());
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值