spring

1、Spring简介

1.1、Spring概述

官网地址:https://spring.io/

Spring 是最受欢迎的企业级 Java 应用程序开发框架,数以百万的来自世界各地的开发人员使用

Spring 框架来创建性能好、易于测试、可重用的代码。

Spring 框架是一个开源的 Java 平台,它最初是由 Rod Johnson 编写的,并且于 2003 年 6 月首

次在 Apache 2.0 许可下发布。

Spring 是轻量级的框架,其基础版本只有 2 MB 左右的大小。

Spring 框架的核心特性是可以用于开发任何 Java 应用程序,但是在 Java EE 平台上构建 web 应

用程序是需要扩展的。 Spring 框架的目标是使 J2EE 开发变得更容易使用,通过启用基于 POJO

编程模型来促进良好的编程实践。

[!note]
还不怎么理解

视频

1.2、Spring家族

项目列表:https://spring.io/projects

1.3、Spring Framework

Spring 基础框架,可以视为 Spring 基础设施,其他spring项目都是以spring Framework为基础的

1.3.1、Spring Framework特性
  • 非侵入式:使用 Spring Framework 开发应用程序时,Spring 对应用程序本身的结构影响非常小。对领域模型可以做到零污染;对功能性组件也只需要使用几个简单的注解进行标记,完全不会破坏原有结构,反而能将组件结构进一步简化。这就使得基于 Spring Framework 开发应用程序时结构清晰、简洁优雅。
  • 控制反转:IOC——Inversion of Control,翻转资源获取方向。把自己创建资源、向环境索取资源
    变成环境将资源准备好,我们享受资源注入。

[!note]
把对象的创建权和控制权交给spring

  • 面向切面编程:AOP——Aspect Oriented Programming,在不修改源代码的基础上增强代码功能。
  • 容器:Spring IOC 是一个容器,因为它包含并且管理组件对象的生命周期。组件享受到了容器化的管理,替程序员屏蔽了组件创建过程中的大量细节,极大的降低了使用门槛,大幅度提高了开发效率。
  • 组件化:Spring 实现了使用简单的组件配置组合成一个复杂的应用。在 Spring 中可以使用 XML和 Java 注解组合这些对象。这使得我们可以基于一个个功能明确、边界清晰的组件有条不紊的搭建超大型复杂应用系统。
  • 声明式:很多以前需要编写代码才能实现的功能,现在只需要声明需求即可由框架代为实现。
  • 一站式:在 IOC 和 AOP 的基础上可以整合各种企业应用的开源框架和优秀的第三方类库。而且Spring 旗下的项目已经覆盖了广泛领域,很多方面的功能性需求可以在 Spring Framework 的基础上全部使用 Spring 来实现。
1.3.2、Spring Framework五大功能模块
功能模块功能介绍
Core Container核心容器,在 Spring 环境下使用任何功能都必须基于 IOC 容器。
AOP&Aspects面向切面编程
Testing提供了对 junit 或 TestNG 测试框架的整合。
Data Access/Integration提供了对数据访问/集成的功能。
Spring MVC提供了面向Web应用程序的集成功能。

2、IOC

2.1、IOC容器

2.1.1、IOC思想

IOC:Inversion of Control,翻译过来是反转控制

①获取资源的传统方式

自己做饭:买菜、洗菜、择菜、改刀、炒菜,必须熟悉资源创建的所有细节而且掌控他们
在应用程序中的组件需要获取资源时,传统的方式是组件主动的从容器中获取所需要的资源,在这样的
模式下开发人员往往需要知道在具体容器中特定资源的获取方式,增加了学习成本,同时降低了开发效率。

②反转控制方式获取资源

点外卖:下单、等、吃,省时省力,不必关心资源创建过程的所有细节。把细节给spring做,自己获得最后的结果

③DI

DI:Dependency Injection,翻译过来是依赖注入
DI 是 IOC 的另一种表述方式:即组件以一些预先定义好的方式(例如:setter 方法)接受来自于容器
的资源注入。相对于IOC而言,这种表述更直接。
所以结论是:IOC 就是一种反转控制的思想, 而 DI 是对 IOC 的一种具体实现方式。

2.1.2、IOC容器在Spring中的实现

Spring 的 IOC 容器就是 IOC 思想的一个落地的产品实现。IOC 容器中管理的组件也叫做 bean。在创建bean 之前,首先需要创建 IOC 容器。Spring 提供了 IOC 容器的两种实现方式:

①BeanFactory

这是 IOC 容器的基本实现,是 Spring 内部使用的接口。面向 Spring 本身,不提供给开发人员使用。

②ApplicationContext

BeanFactory 的子接口,提供了更多高级特性。面向 Spring 的使用者,几乎所有场合都使用

ApplicationContext 而不是底层的 BeanFactory。

③ApplicationContext的主要实现类

5s

类型名简介
ClassPathXmlApplicationContext通过读取类路径下的 XML 格式的配置文件创建 IOC 容器对象
FileSystemXmlApplicationContext通过文件系统路径读取 XML 格式的配置文件创建 IOC 容器对象
ConfigurableApplicationContextApplicationContext 的子接口,包含一些扩展方法refresh() 和 close() ,让 ApplicationContext 具有启动、关闭和刷新上下文的能力。
WebApplicationContext专门为 Web 应用准备,基于 Web 环境创建 IOC 容器对象,并将对象引入存入 ServletContext 域中。

2.2、基于XML管理bean

2.2.1、实验一:入门案例

①创建Maven Module
②引入依赖
<dependencies>  
    <!-- 基于Maven依赖传递性,导入spring-context依赖即可导入当前所需所有jar包 -->  
    <dependency>  
        <groupId>org.springframework</groupId>  
        <artifactId>spring-context</artifactId>  
        <version>5.3.1</version>  
    </dependency>  
    <!-- junit测试 -->  
    <dependency>  
        <groupId>junit</groupId>  
        <artifactId>junit</artifactId>  
        <version>4.12</version>  
        <scope>test</scope>  
    </dependency>  
</dependencies>

6a

③创建类HelloWorld
public class HelloWorld {  
    public void sayHello(){  
        System.out.println("helloworld");  
    }  
}

image.png
image.png

现在可以指定xml获得ioc容器

⑤在Spring的配置文件中配置bean

bean是spring的一个组件换句话来说是spring管理的一个对象

<!--
	配置HelloWorld所对应的bean,即将HelloWorld的对象交给Spring的IOC容器管理
	通过bean标签配置IOC容器所管理的bean
	属性:
		id:设置bean的唯一标识
		class:设置bean所对应类型的全类名
-->
<bean id="helloworld" class="com.atguigu.spring.bean.HelloWorld"></bean>
⑥创建测试类测试

resources和java最终加载到同一个路径下,即类路径下面所以可以直接写文件名

@Test
public void testHelloWorld(){
    ApplicationContext ac = newClassPathXmlApplicationContext("applicationContext.xml");
	    HelloWorld helloworld = (HelloWorld) ac.getBean("helloworld");
	    helloworld.sayHello();
}
⑦思路

9s

image.png
image.png

[!note]
知道了bean的id就知道了我们要创建对象的类型,在类型不确定的情况下我们要创建和管理对象要用反射和工厂模式

⑧注意

Spring 底层默认通过反射技术调用组件类的无参构造器来创建组件对象,这一点需要注意。如果在需要无参构造器时,没有无参构造器,则会抛出下面的异常:

org.springframework.beans.factory.BeanCreationException: Error creating bean with name

‘helloworld’ defined in class path resource [applicationContext.xml]: Instantiation of bean

failed; nested exception is org.springframework.beans.BeanInstantiationException: Failed

to instantiate [com.atguigu.spring.bean.HelloWorld]: No default constructor found; nested

exception is java.lang.NoSuchMethodException: com.atguigu.spring.bean.HelloWorld.

<init>()

原因是有参构造可以是多种样子的,并不好自动构造出来对象

2.2.2、实验二:获取bean

①方式一:根据id获取

由于 id 属性指定了 bean 的唯一标识,所以根据 bean 标签的 id 属性可以精确获取到一个组件对象。

上个实验中我们使用的就是这种方式。

②方式二:根据类型获取

[!danger]
有多个类型匹配的bean会出错

@Test  
 @Test  
    public void testIOC(){  
        ClassPathXmlApplicationContext ioc = new ClassPathXmlApplicationContext("spring-ioc.xml");  
        Student studentOne = (Student) ioc.getBean("studentOne");  
//        通过类来获取  
        Student bean = ioc.getBean(Student.class);  
        System.out.println(studentOne);  
  
    }

所以有了以下方式获取

③方式三:根据id和类型
@Test  
public void testHelloWorld(){  
    ApplicationContext ac = new ClassPathXmlApplicationContext("applicationContext.xml");  
    HelloWorld bean = ac.getBean("helloworld", HelloWorld.class);  
    bean.sayHello();  
}
④注意

当根据类型获取bean时,要求IOC容器中指定类型的bean有且只能有一个

当IOC容器中一共配置了两个:


<bean id="helloworldOne" class="com.atguigu.spring.bean.HelloWorld"></bean>  
<bean id="helloworldTwo" class="com.atguigu.spring.bean.HelloWorld"></bean>

根据类型获取时会抛出异常:

> org.springframework.beans.factory.NoUniqueBeanDefinitionException: No qualifying bean
> 
> of type 'com.atguigu.spring.bean.HelloWorld' available: expected single matching bean but
> 
> found 2: helloworldOne,helloworldTwo

如果bean没有也会报错
image.png

⑤扩展

如果组件类实现了接口,根据接口类型可以获取 bean 吗?

可以,前提是bean唯一

public class Student implements Person

@Test  
    public void testIOC(){  
        ClassPathXmlApplicationContext ioc = new ClassPathXmlApplicationContext("spring-ioc.xml");  
        Student studentOne = (Student) ioc.getBean("studentOne");  
//        通过类来获取  
//        Student bean = ioc.getBean(Student.class);  
//        System.out.println(studentOne);  
        Person bean = ioc.getBean(Person.class);  
        System.out.println(bean);  
  
    }

image.png

可以根据接口类型获得对象,因为是兼容的

如果一个接口有多个实现类,这些实现类都配置了 bean,根据接口类型可以获取 bean 吗?

不行,因为bean不唯一

⑥结论

根据类型来获取bean时,必须要满足bean唯一的情况下,其实只是看:『对象 instanceof 指定的类

型』的返回结果,只要返回的是true就可以认定为和类型匹配,能够获取到

[!note]
就是当前bean本身的类型所继承的类型所实现的接口类型都可以

2.2.3、实验三:依赖注入之setter注入

依赖注入是ioc的一种具体的实现方式,就是设置好方法然后来接收对象

创建学生类Student
package com.atguigu.spring.pojo;  
  
public class Student implements Person{  
    private Integer sid;  
    private String sname;  
    private Integer age;  
    private String gender;  
  

    public Integer getSid() {  
        return sid;  
    }  
  
    public void setSid(Integer sid) {  
        this.sid = sid;  
    }  
  
    public String getSname() {  
        return sname;  
    }  
  
    public void setSname(String sname) {  
        this.sname = sname;  
    }  
  
    public Integer getAge() {  
        return age;  
    }  
  
    public void setAge(Integer age) {  
        this.age = age;  
    }  
  
    public String getGender() {  
        return gender;  
    }  
  
    public void setGender(String gender) {  
        this.gender = gender;  
    }  
  
    @Override  
    public String toString() {  
        return "Student{" +  
                "sid=" + sid +  
                ", sname='" + sname + '\'' +  
                ", age=" + age +  
                ", gender='" + gender + '\'' +  
                '}';  
    }  
}
配置bean时属性赋值
<bean id="studentOne" class="com.atguigu.spring.pojo.Student">  
    <!-- property标签:通过组件类的setXxx()方法给组件对象设置属性 -->  
    <!-- name属性:指定属性名(这个属性名是getXxx()、setXxx()方法定义的,和成员变量无关)-->  
    <!-- value属性:指定属性值 -->  
    <property name="sid" value="1001"></property>  
    <property name="sname" value="张三"></property>  
    <property name="age" value="23"></property>  
    <property name="gender" value=""></property>  
</bean>
测试
@Test  
public void testDI(){  
    ClassPathXmlApplicationContext ioc = new ClassPathXmlApplicationContext("spring-ioc.xml");  
    Student studentTwo = ioc.getBean("studentTwo", Student.class);  
    System.out.println(studentTwo);  
  
  
}

image.png

2.2.4、实验四:依赖注入之构造器注入

①在Student类中添加有参构造
public Student(Integer sid, String sname, Integer age, String gender) {  
    this.sid = sid;  
    this.sname = sname;  
    this.age = age;  
    this.gender = gender;  
}```
#### ②配置bean
```xml
<bean id="studentThree" class="com.atguigu.spring.pojo.Student">  
    <constructor-arg value="1002"></constructor-arg>  
    <constructor-arg value="李四"></constructor-arg>  
    <constructor-arg value="33"></constructor-arg>  
   <constructor-arg value="女"></constructor-arg>  
</bean>

如果遇到有参构造不好识别是那种的情况需要指定参数名
image.png

注意:

constructor-arg标签还有两个属性可以进一步描述构造器参数:

  • index属性:指定参数所在位置的索引(从0开始)

  • name属性:指定参数名

③测试

public void testDI(){  
    ClassPathXmlApplicationContext ioc = new ClassPathXmlApplicationContext("spring-ioc.xml");  
    Student studentTwo = ioc.getBean("studentThree", Student.class);  
    System.out.println(studentTwo);  
  
  
}

2.2.5、实验五:特殊值处理

①字面量赋值

什么是字面量?

int a = 10;
这里的就是字面量
声明一个变量a,初始化为10,此时a就不代表字母a了,而是作为一个变量的名字。当我们引用a

的时候,我们实际上拿到的值是10。

而如果a是带引号的:‘a’,那么它现在不是一个变量,它就是代表a这个字母本身,这就是字面

量。所以字面量没有引申含义,就是我们看到的这个数据本身。

<!-- 使用value属性给bean的属性赋值时,Spring会把value属性的值看做字面量 -->
<property name="name" value="张三"/>
②null值
<property name="name">
	<null />
</property>

注意:

`<property name="name" value="null"></property>`

以上写法,为name所赋的值是字符串null

③xml实体
<!-- 小于号在XML文档中用来定义标签的开始,不能随便使用 -->
<!-- 解决方案一:使用XML实体来代替 -->
<property name="expression" value="a &lt; b"/>
④CDATA节

要写在value的子标签里面,CDATA节是xml中一个特殊的标签,因此不能写在一个属性中

<property name="expression">
    <!-- 解决方案二:使用CDATA节 -->
    <!-- CDATA中的C代表Character,是文本、字符的含义,CDATA就表示纯文本数据 -->
    <!-- XML解析器看到CDATA节就知道这里是纯文本,就不会当作XML标签或属性来解析 -->
    <!-- 所以CDATA节中写什么符号都随意 -->
    <value><![CDATA[a < b]]></value>
</property>

2.2.6、实验六:为类类型属性赋值

①创建班级类Clazz
public class Clazz {
    private Integer clazzId;
    private String clazzName;
    public Integer getClazzId() {
        return clazzId;
    }
    public void setClazzId(Integer clazzId) {
        this.clazzId = clazzId;
    }
    public String getClazzName() {
        return clazzName;
    }
    public void setClazzName(String clazzName) {
        this.clazzName = clazzName;
    }
    @Override
    public String toString() {
        return "Clazz{" +
            "clazzId=" + clazzId +
            ", clazzName='" + clazzName + '\'' +
            '}';
    }
    public Clazz() {
    }
    public Clazz(Integer clazzId, String clazzName) {
        this.clazzId = clazzId;
        this.clazzName = clazzName;
    }
}
②修改Student类

在Student类中添加以下代码:

private Clazz clazz;
public Clazz getClazz() {
    return clazz;
}
public void setClazz(Clazz clazz) {
    this.clazz = clazz;
}
③方式一:引用外部已声明的bean

就是用bean给类属性的变量赋值

配置Clazz类型的bean:

<bean id="clazzOne" class="com.atguigu.spring.pojo.Clazz">  
    <property name="clazzId" value="1111"></property>  
    <property name="clazzName" value="一班"></property>  
</bean>

为Student中的clazz属性赋值:

[!note]
ref属性:引用IOC容器中某个bean的id,将所对应的bean为属性赋值

<bean id="studentFour" class="com.atguigu.spring.bean.Student">
    <property name="id" value="1004"></property>
    <property name="name" value="赵六"></property>
    <property name="age" value="26"></property>
    <property name="sex" value=""></property>
    <!-- ref属性:引用IOC容器中某个bean的id,将所对应的bean为属性赋值 -->
    <property name="clazz" ref="clazzOne"></property>
</bean>

image.png

④方式二:内部bean
<bean id="studentFive" class="com.atguigu.spring.pojo.Student">
    <property name="sid" value="1004"></property>
    <property name="sname" value="赵六"></property>
    <property name="age" value="26"></property>
    <property name="gender" value=""></property>
    <property name="clazz">
        <!-- 在一个bean中再声明一个bean就是内部bean -->
        <!-- 内部bean只能用于给属性赋值,不能在外部通过IOC容器获取,因此可以省略id属性 -->
        <bean id="clazzInner" class="com.atguigu.spring.bean.Clazz">
            <property name="clazzId" value="2222"></property>
            <property name="clazzName" value="远大前程班"></property>
        </bean>
    </property>
</bean>

image.png

内部bean不能直接通过ioc容器获取
image.png

③方式三:级联属性赋值
<bean id="studentFour" class="com.atguigu.spring.bean.Student">
    <property name="id" value="1004"></property>
    <property name="name" value="赵六"></property>
    <property name="age" value="26"></property>
    <property name="sex" value=""></property>
    <!-- 一定先引用某个bean为属性赋值,才可以使用级联方式更新属性 -->
    <property name="clazz" ref="clazzOne"></property>
    <property name="clazz.clazzId" value="3333"></property>
    <property name="clazz.clazzName" value="最强王者班"></property>
</bean>

2.2.7、实验七:为数组类型属性赋值

①修改Student类

在Student类中添加以下代码:

private String[] hobby;
public String[] getHobby() {  
    return hobby;  
}  
  
public void setHobby(String[] hobby) {  
    this.hobby = hobby;  
}
②配置bean
<bean id="studentFive" class="com.atguigu.spring.pojo.Student">  
    <property name="sid" value="1004"></property>  
    <property name="sname" value="赵六"></property>  
    <property name="age" value="26"></property>  
    <property name="gender" value=""></property>  
    <property name="clazz">  
        <!-- 在一个bean中再声明一个bean就是内部bean -->  
        <!-- 内部bean只能用于给属性赋值,不能在外部通过IOC容器获取,因此可以省略id属性 -->  
        <bean id="clazzInner" class="com.atguigu.spring.pojo.Clazz">  
            <property name="clazzId" value="2222"></property>  
            <property name="clazzName" value="远大前程班"></property>  
        </bean>  
    </property>  
    <property name="hobby">  
        <array>  
            <value>抽样</value>  
            <value>喝酒</value>  
            <value>烫头</value>  
        </array>  
    </property>  
</bean>

若是类类型
image.png

③测试

image.png

2.2.8、实验八:为集合类型属性赋值

①为List集合类型属性赋值

在Clazz类中添加以下代码:

private List<Student> students;
public List<Student> getStudents() {
    return students;
}
public void setStudents(List<Student> students) {
    this.students = students;
}

配置bean:

<bean id="clazzOne" class="com.atguigu.spring.pojo.Clazz">  
    <property name="clazzId" value="1111"></property>  
    <property name="clazzName" value="一班"></property>  
    <property name="students">  
        <list>  
            <ref bean="studentTwo"></ref>  
            <ref bean="studentThree"></ref>  
        </list>  
    </property>  
</bean>

测试
image.png

使用list类型的bean

[!danger]
原本bean只能给属性赋值并不能填充数据,所以要引入新的依赖

<bean id="clazzOne" class="com.atguigu.spring.pojo.Clazz">  
    <property name="clazzId" value="1111"></property>  
    <property name="clazzName" value="一班"></property>  
    <property name="students" ref="studentList">  
  
    </property>  
</bean>  
<util:list id="studentList">  
    <ref bean="studentThree"></ref>  
    <ref bean="studentTwo"></ref>  
</util:list>

若为Set集合类型属性赋值,只需要将其中的list标签改为set标签即可

②为Map集合类型属性赋值

创建教师类Teacher:

public class Teacher {
    private Integer teacherId;
    private String teacherName;
    public Integer getTeacherId() {
        return teacherId;
    }
    public void setTeacherId(Integer teacherId) {
        this.teacherId = teacherId;
    }
    public String getTeacherName() {
        return teacherName;
    }
    public void setTeacherName(String teacherName) {
        this.teacherName = teacherName;
    }
    public Teacher(Integer teacherId, String teacherName) {
        this.teacherId = teacherId;
        this.teacherName = teacherName;
    }
    public Teacher() {
    }
    @Override
    public String toString() {
        return "Teacher{" +
            "teacherId=" + teacherId +
            ", teacherName='" + teacherName + '\'' +
            '}';
    }
}

在Student类中添加以下代码:

private Map<String, Teacher> teacherMap;
public Map<String, Teacher> getTeacherMap() {
    return teacherMap;
}
public void setTeacherMap(Map<String, Teacher> teacherMap) {
    this.teacherMap = teacherMap;
}

配置bean:

<bean id="studentFive" class="com.atguigu.spring.pojo.Student">  
    <property name="sid" value="1004"></property>  
    <property name="sname" value="赵六"></property>  
    <property name="age" value="26"></property>  
    <property name="gender" value=""></property>  
    <property name="clazz">  
        <!-- 在一个bean中再声明一个bean就是内部bean -->  
        <!-- 内部bean只能用于给属性赋值,不能在外部通过IOC容器获取,因此可以省略id属性 -->  
        <bean id="clazzInner" class="com.atguigu.spring.pojo.Clazz">  
            <property name="clazzId" value="2222"></property>  
            <property name="clazzName" value="远大前程班"></property>  
        </bean>  
    </property>  
    <property name="hobby">  
        <array>  
            <value>抽样</value>  
            <value>喝酒</value>  
            <value>烫头</value>  
        </array>  
    </property>  
    <property name="teacherMap">  
        <map>  
            <entry key="10086" value-ref="teacherOne"></entry>  
            <entry key="1992" value-ref="teacherTwo"></entry>  
        </map>  
    </property>  
</bean>  
  
<bean id="teacherOne" class="com.atguigu.spring.pojo.Teacher">  
    <property name="teacherId" value="10010"></property>  
    <property name="teacherName" value="大宝"></property>  
</bean>  
<bean id="teacherTwo" class="com.atguigu.spring.pojo.Teacher">  
    <property name="teacherId" value="10086"></property>  
    <property name="teacherName" value="二宝"></property>  
</bean>

image.png

引用map集合的bean

<util:map id="teacherMap">  
    <entry key="10086" value-ref="teacherOne"></entry>  
    <entry key="10010" value-ref="teacherTwo"></entry>  
</util:map>

2.2.9、实验九:p命名空间

引入p命名空间后,可以通过以下方式为bean的各个属性赋值

<bean id="studentSix" class="com.atguigu.spring.pojo.Student"
      p:id="1006" p:name="小明" p:clazz-ref="clazzOne" p:teacherMap-ref="teacherMap"></bean>

2.2.10、实验十:引入外部属性文件

①加入依赖
<!-- MySQL驱动 -->
<dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
    <version>8.0.16</version>
</dependency>
<!-- 数据源 -->
<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>druid</artifactId>
    <version>1.0.31</version>
</dependency>
②创建外部属性文件

10aaa

jdbc.user=root
jdbc.password=atguigu
jdbc.url=jdbc:mysql://localhost:3306/ssm?serverTimezone=UTC
jdbc.driver=com.mysql.cj.jdbc.Driver
③引入属性文件

[!note]
<! --引入jdbc.properties,之后可以通过${key}的方式访问value–>

<!-- 引入外部属性文件 -->
<context:property-placeholder location="classpath:jdbc.properties"/>
④配置bean
<bean id="druidDataSource" class="com.alibaba.druid.pool.DruidDataSource">
    <property name="url" value="${jdbc.url}"/>
    <property name="driverClassName" value="${jdbc.driver}"/>
    <property name="username" value="${jdbc.user}"/>
    <property name="password" value="${jdbc.password}"/>
</bean>
⑤测试
@Test
public void testDataSource() throws SQLException {
    ApplicationContext ac = new ClassPathXmlApplicationContext("spring-datasource.xml");
    DataSource dataSource = ac.getBean(DataSource.class);
    Connection connection = dataSource.getConnection();
    System.out.println(connection);
}

2.2.11、实验十一:bean的作用域

①概念

在Spring中可以通过配置bean标签的scope属性来指定bean的作用域范围,各取值含义参加下表:

[!note]
scope:设置bean的作用域
scope=“singleton / prototype”
singleton(单例):表示获取该bean所对应的对象都是同一个
protcype(多例):表示获取该bean所对应的对象都不是同一个

取值含义创建对象的时机
singleton(默认)在IOC容器中,这个bean的对象始终为单实例IOC容器初始化时
prototype这个bean在IOC容器中有多个实例获取bean时
单例的情况有可能会出问题
image.png

11:10

  1. action是被ioc管理了,所以数据都是同一个
    但是通过网页上传的修改对象的功能已经修改的bean中的user类对象,user类对象中已经有id了,所以他使用的就是上一次的id,然后把后面添加的数据覆盖了第一次修改的数据

如果是在WebApplicationContext环境下还会有另外两个作用域(但不常用):

取值含义
request在一个请求范围内有效
session在一个会话范围内有效

2.2.12、实验十二:bean的生命周期

①具体的生命周期过程
  • bean对象创建(调用无参构造器)
  • 给bean对象设置属性
  • bean对象初始化之前操作(由bean的后置处理器负责)
  • bean对象初始化(需在配置bean时指定初始化方法)
  • bean对象初始化之后操作(由bean的后置处理器负责)
  • bean对象就绪可以使用
  • bean对象销毁(需在配置bean时指定销毁方法)
  • IOC容器关闭
②修改类User
public class User {
    private Integer id;
    private String username;
    private String password;
    private Integer age;
    public User() {
        System.out.println("生命周期:1、创建对象");
    }
    public User(Integer id, String username, String password, Integer age) {
        this.id = id;
        this.username = username;
        this.password = password;
        this.age = age;
    }
    public Integer getId() {
        return id;
    }
    public void setId(Integer id) {
        System.out.println("生命周期:2、依赖注入");
        this.id = id;
    }
    public String getUsername() {
        return username;
    }
    public void setUsername(String username) {
        this.username = username;
    }
    public String getPassword() {
        return password;
    }
    public void setPassword(String password) {
        this.password = password;
    }
    public Integer getAge() {
        return age;
    }
    public void setAge(Integer age) {
        this.age = age;
    }
    public void initMethod(){
        System.out.println("生命周期:3、初始化");
    }
    public void destroyMethod(){
        System.out.println("生命周期:5、销毁");
    }
    @Override
    public String toString() {
        return "User{" +
            "id=" + id +
            ", username='" + username + '\'' +
            ", password='" + password + '\'' +
            ", age=" + age +
            '}';
    }
}

注意其中的initMethod()和destroyMethod(),可以通过配置bean指定为初始化和销毁的方法

③配置bean

[!note]
使用init-method属性指定初始化方法
使用destroy-method属性指定销毁方法

<!-- 使用init-method属性指定初始化方法 -->
<!-- 使用destroy-method属性指定销毁方法 -->
<bean class="com.atguigu.bean.User" scope="prototype" init-method="initMethod"destroy-method="destroyMethod">
    <property name="id" value="1001"></property>
    <property name="username" value="admin"></property>
    <property name="password" value="123456"></property>
    <property name="age" value="23"></property>
</bean>
④测试

[!note]
1,实例化
2、依赖注入
3,初始化
4、IOC容器关时销毁
,
ConfiqurableApplicationContext是ApplicationContext的子接口,其中扩展了刷新和关闭容器的方法

@Test
public void testLife(){
    ConfiqurableApplicationContext ac = newClassPathXmlApplicationContext("spring-lifecycle.xml");
    User bean = ac.getBean(User.class);
    System.out.println("生命周期:4、通过IOC容器获取bean并使用");
    ac.close();
}

单例的时候在获取容器的时候就把对象给创建好,多例的时候是在获取bean的时候创建

⑤bean的后置处理器

bean的后置处理器会在生命周期的初始化前后添加额外的操作,需要实现BeanPostProcessor接口,

且配置到IOC容器中,需要注意的是,bean后置处理器不是单独针对某一个bean生效,而是针对IOC容器中所有bean都会执行

创建bean的后置处理器:

package com.atguigu.spring.process;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanPostProcessor;
public class MyBeanProcessor implements BeanPostProcessor {
    @Override
    public Object postProcessBeforeInitialization(Object bean, String beanName)
        throws BeansException {
        System.out.println("☆☆☆" + beanName + " = " + bean);
        return bean;
    }
    @Override
    public Object postProcessAfterInitialization(Object bean, String beanName)
        throws BeansException {
        System.out.println("★★★" + beanName + " = " + bean);
        return bean;
    }
}

在IOC容器中配置后置处理器:

<bean id="myBeanProcessor"class="com.atguigu.spring.process.MyBeanProcessor"/>

2.2.13、实验十三:FactoryBean

配置到ioc容器,获取的时候可以直接获取这个工厂提供的对象,不用先获取工厂再获取对象

①简介

FactoryBean是Spring提供的一种整合第三方框架的常用机制。和普通的bean不同,配置一个

FactoryBean类型的bean,在获取bean的时候得到的并不是class属性中配置的这个类的对象,而是

getObject()方法的返回值。通过这种机制,Spring可以帮我们把复杂组件创建的详细过程和繁琐细节都屏蔽起来,只把最简洁的使用界面展示给我们。

当把FactoryBean的实现类醒置为bean时,会将当前类中getobject()所返回的对象交给Ioc容器管理

将来我们整合Mybatis时,Spring就是通过FactoryBean机制来帮我们创建SqlSessionFactory对象的。

/*
* Copyright 2002-2020 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.beans.factory;
import org.springframework.lang.Nullable;
/**
* Interface to be implemented by objects used within a {@link BeanFactory}
which
* are themselves factories for individual objects. If a bean implements this
* interface, it is used as a factory for an object to expose, not directly as a
* bean instance that will be exposed itself.
*
* <p><b>NB: A bean that implements this interface cannot be used as a normal
bean.</b>
* A FactoryBean is defined in a bean style, but the object exposed for bean
* references ({@link #getObject()}) is always the object that it creates.
*
* <p>FactoryBeans can support singletons and prototypes, and can either create
* objects lazily on demand or eagerly on startup. The {@link SmartFactoryBean}
* interface allows for exposing more fine-grained behavioral metadata.
*
* <p>This interface is heavily used within the framework itself, for example
for
* the AOP {@link org.springframework.aop.framework.ProxyFactoryBean} or the
* {@link org.springframework.jndi.JndiObjectFactoryBean}. It can be used for
* custom components as well; however, this is only common for infrastructure
code.
*
* <p><b>{@code FactoryBean} is a programmatic contract. Implementations are not
* supposed to rely on annotation-driven injection or other reflective
facilities.</b>
* {@link #getObjectType()} {@link #getObject()} invocations may arrive early in
the
* bootstrap process, even ahead of any post-processor setup. If you need access
to
* other beans, implement {@link BeanFactoryAware} and obtain them
programmatically.
*
* <p><b>The container is only responsible for managing the lifecycle of the
FactoryBean
* instance, not the lifecycle of the objects created by the FactoryBean.</b>
Therefore,
* a destroy method on an exposed bean object (such as {@link
java.io.Closeable#close()}
* will <i>not</i> be called automatically. Instead, a FactoryBean should
implement
* {@link DisposableBean} and delegate any such close call to the underlying
object.
*
* <p>Finally, FactoryBean objects participate in the containing BeanFactory's
* synchronization of bean creation. There is usually no need for internal
* synchronization other than for purposes of lazy initialization within the
* FactoryBean itself (or the like).
*
* @author Rod Johnson
* @author Juergen Hoeller
* @since 08.03.2003
* @param <T> the bean type
* @see org.springframework.beans.factory.BeanFactory
* @see org.springframework.aop.framework.ProxyFactoryBean
* @see org.springframework.jndi.JndiObjectFactoryBean
*/
public interface FactoryBean<T> {
    /**
* The name of an attribute that can be
* {@link org.springframework.core.AttributeAccessor#setAttribute set} on a
* {@link org.springframework.beans.factory.config.BeanDefinition} so that
* factory beans can signal their object type when it can't be deduced from
* the factory bean class.
* @since 5.2
*/
    String OBJECT_TYPE_ATTRIBUTE = "factoryBeanObjectType";
    /**
* Return an instance (possibly shared or independent) of the object
* managed by this factory.
* <p>As with a {@link BeanFactory}, this allows support for both the
* Singleton and Prototype design pattern.
* <p>If this FactoryBean is not fully initialized yet at the time of
* the call (for example because it is involved in a circular reference),
* throw a corresponding {@link FactoryBeanNotInitializedException}.
* <p>As of Spring 2.0, FactoryBeans are allowed to return {@code null}
* objects. The factory will consider this as normal value to be used; it
* will not throw a FactoryBeanNotInitializedException in this case anymore.
* FactoryBean implementations are encouraged to throw
* FactoryBeanNotInitializedException themselves now, as appropriate.
* @return an instance of the bean (can be {@code null})
* @throws Exception in case of creation errors
* @see FactoryBeanNotInitializedException
*/
    @Nullable
    T getObject() throws Exception;
    /**
* Return the type of object that this FactoryBean creates,
* or {@code null} if not known in advance.
* <p>This allows one to check for specific types of beans without
* instantiating objects, for example on autowiring.
* <p>In the case of implementations that are creating a singleton object,
* this method should try to avoid singleton creation as far as possible;
* it should rather estimate the type in advance.
* For prototypes, returning a meaningful type here is advisable too.
* <p>This method can be called <i>before</i> this FactoryBean has
* been fully initialized. It must not rely on state created during
* initialization; of course, it can still use such state if available.
* <p><b>NOTE:</b> Autowiring will simply ignore FactoryBeans that return
* {@code null} here. Therefore it is highly recommended to implement
* this method properly, using the current state of the FactoryBean.
* @return the type of object that this FactoryBean creates,
* or {@code null} if not known at the time of the call
* @see ListableBeanFactory#getBeansOfType
*/
    @Nullable
    Class<?> getObjectType();
    /**
* Is the object managed by this factory a singleton? That is,
* will {@link #getObject()} always return the same object
* (a reference that can be cached)?
* <p><b>NOTE:</b> If a FactoryBean indicates to hold a singleton object,
* the object returned from {@code getObject()} might get cached
* by the owning BeanFactory. Hence, do not return {@code true}
* unless the FactoryBean always exposes the same reference.
* <p>The singleton status of the FactoryBean itself will generally
* be provided by the owning BeanFactory; usually, it has to be
* defined as singleton there.
* <p><b>NOTE:</b> This method returning {@code false} does not
* necessarily indicate that returned objects are independent instances.
* An implementation of the extended {@link SmartFactoryBean} interface
* may explicitly indicate independent instances through its
* {@link SmartFactoryBean#isPrototype()} method. Plain {@link FactoryBean}
* implementations which do not implement this extended interface are
* simply assumed to always return independent instances if the
* {@code isSingleton()} implementation returns {@code false}.
* <p>The default implementation returns {@code true}, since a
* {@code FactoryBean} typically manages a singleton instance.
* @return whether the exposed object is a singleton
* @see #getObject()
* @see SmartFactoryBean#isPrototype()
*/
    default boolean isSingleton() {
        return true;
    }
}
②创建类UserFactoryBean
public class UserFactoryBean implements FactoryBean<User> {
    @Override
    public User getObject() throws Exception {
        return new User();
    }
    @Override
    public Class<?> getObjectType() {
        return User.class;
    }
}
③配置bean
<bean id="user" class="com.atguigu.bean.UserFactoryBean"></bean>
④测试
@Test
public void testUserFactoryBean(){
    //获取IOC容器
    ApplicationContext ac = new ClassPathXmlApplicationContext("spring-factorybean.xml");
    User user = (User) ac.getBean("user");
    System.out.println(user);
}

image.png

2.2.14、实验十基于xml的自动装配

自动装配:
根据指定的策略,在IOC容器中匹配某一个bean,自动为指定的bean中所依赖的类类型或接口类型属性赋值

ioc容器中所有类类型的属性,都可以在ioc容器中找到匹配的bean为属性赋值,之前是配置proprieties引用

①场景模拟

创建类UserController

public class UserController {
    private UserService userService;
    public void setUserService(UserService userService) {
        this.userService = userService;
    }
    public void saveUser(){
        userService.saveUser();
    }
}

创建接口UserService

public interface UserService {
	void saveUser();
}

创建类UserServiceImpl实现接口UserService

public class UserServiceImpl implements UserService {
    private UserDao userDao;
    public void setUserDao(UserDao userDao) {
        this.userDao = userDao;
    }
    @Override
    public void saveUser() {
        userDao.saveUser();
    }
}

创建接口UserDao

public interface UserDao {
	void saveUser();
}

创建类UserDaoImpl实现接口UserDao

public class UserDaoImpl implements UserDao {
    @Override
    public void saveUser() {
        System.out.println("保存成功");
    }
}

bean引用

[!danger]
引用的bean的实现类,当使用的实现类发生改变,换个新的实现类引用即可

<?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="userController" class="com.atguigu.spring.controller.UserController">  
    <property name="userService" ref="userService"></property>  
</bean>  
  
  
    <bean id="userService" class="com.atguigu.spring.service.impl.UserServiceImpl">  
        <property name="userDao" ref="userDao"></property>  
    </bean>  
    <bean id="userDao" class="com.atguigu.spring.dao.imlp.UserDaoImpl"></bean>  
</beans>

测试

public class AutoWireTest {  
    @Test  
    public void testAutoWire(){  
        ApplicationContext ioc= new ClassPathXmlApplicationContext("spring-autowire.xml");  
        UserController bean = ioc.getBean(UserController.class);  
        bean.saveUser();  
    }  
}

image.png

②配置bean

使用bean标签的autowire属性设置自动装配效果

自动装配方式:byType

byType:根据类型匹配IOC容器中的某个兼容类型的bean,为属性自动赋值

若在IOC中,没有任何一个兼容类型的bean能够为属性赋值,则该属性不装配,即值为默认值

null

若在IOC中,有多个兼容类型的bean能够为属性赋值,则抛出异常

NoUniqueBeanDefinitionException

image.png

[!note]
总结:当使用byType实现自动装配时,IoC容器中有且只有一个类型匹配的bean能够为属性赋值,
而且相同类型的bean只需配一次,如果需要多个只需要设置作用域使之成为多例模式即可

<bean id="userController"class="com.atguigu.autowire.xml.controller.UserController" autowire="byType">
</bean>
<bean id="userService"class="com.atguigu.autowire.xml.service.impl.UserServiceImpl" autowire="byType">
</bean>
<bean id="userDao" class="com.atguigu.autowire.xml.dao.impl.UserDaoImpl"></bean>

自动装配方式:byName

byName:将自动装配的属性的属性名,作为bean的id在IOC容器中匹配相对应的bean进行赋值

<bean id="userController"class="com.atguigu.autowire.xml.controller.UserController" autowire="byName">
</bean>
<bean id="userService"class="com.atguigu.autowire.xml.service.impl.UserServiceImpl" autowire="byName">
</bean>
<bean id="userServiceImpl"class="com.atguigu.autowire.xml.service.impl.UserServiceImpl" autowire="byName">
</bean>
<bean id="userDao" class="com.atguigu.autowire.xml.dao.impl.UserDaoImpl">
</bean>
<bean id="userDaoImpl" class="com.atguigu.autowire.xml.dao.impl.UserDaoImpl">
</bean>

2.3、基于注解管理bean

2.3.1、实验一:标记与扫描

①注解

和 XML 配置文件一样,注解本身并不能执行,注解本身仅仅只是做一个标记,具体的功能是框架检测

到注解标记的位置,然后针对这个位置按照注解标记的功能来执行具体操作。

本质上:所有一切的操作都是Java代码来完成的,XML和注解只是告诉框架中的Java代码如何执行。

举例:元旦联欢会要布置教室,蓝色的地方贴上元旦快乐四个字,红色的地方贴上拉花,黄色的地方贴上气球。

1sss1

班长做了所有标记,同学们来完成具体工作。墙上的标记相当于我们在代码中使用的注解,后面同学们做的工作,相当于框架的具体操作。

②扫描

Spring 为了知道程序员在哪些地方标记了什么注解,就需要通过扫描的方式,来进行检测。然后根据注解进行后续操作。

③新建Maven Module
<dependencies>
    <!-- 基于Maven依赖传递性,导入spring-context依赖即可导入当前所需所有jar包 -->
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-context</artifactId>
        <version>5.3.1</version>
    </dependency>
    <!-- junit测试 -->
    <dependency>
        <groupId>junit</groupId>
        <artifactId>junit</artifactId>
        <version>4.12</version>
        <scope>test</scope>
    </dependency>
</dependencies>
④创建Spring配置文件

12s

⑤标识组件的常用注解

@Component:将类标识为普通组件

@Controller:将类标识为控制层组件

@Service:将类标识为业务层组件

@Repository:将类标识为持久层组件

问:以上四个注解有什么关系和区别?

ss13

通过查看源码我们得知,@Controller、@Service、@Repository这三个注解只是在@Component注解的基础上起了三个新的名字。

对于Spring使用IOC容器管理这些组件来说没有区别。所以@Controller、@Service、@Repository这

三个注解只是给开发人员看的,让我们能够便于分辨组件的作用。,其本质其实都是一样的

注意:虽然它们本质上一样,但是为了代码的可读性,为了程序结构严谨我们肯定不能随便胡乱标记。

⑥扫描组件

直接设置注解,然后ioc里面配置扫描主件

<context:component-scan base-package="com.atguigu.spring">  
  
</context:component-scan>

image.png

情况一:最基本的扫描方式

<context:component-scan base-package="com.atguigu">
</context:component-scan>

情况二:指定要排除的组件

<context:component-scan base-package="com.atguigu">
    <!-- context:exclude-filter标签:指定排除规则 -->
    <!--
        type:设置排除或包含的依据
        type="annotation",根据注解排除,expression中设置要排除的注解的全类名
        type="assignable",根据类型排除,expression中设置要排除的类型的全类名
    -->
    <context:exclude-filter type="annotation"expression="org.springframework.stereotype.Controller"/>
    <!--<context:exclude-filter type="assignable"expression="com.atguigu.controller.UserController"/>-->
</context:component-scan>

情况三:仅扫描指定组件
默认情况下 use-default-filters=“true” 即包内所有类都扫描

<context:component-scan base-package="com.atguigu" use-default-filters="false">
    <!-- context:include-filter标签:指定在原有扫描规则的基础上追加的规则 -->
    <!-- use-default-filters属性:取值false表示关闭默认扫描规则 -->
    <!-- 此时必须设置use-default-filters="false",因为默认规则即扫描指定包下所有类 -->
    <!--
        type:设置排除或包含的依据
        type="annotation",根据注解排除,expression中设置要排除的注解的全类名
        type="assignable",根据类型排除,expression中设置要排除的类型的全类名
     -->
    <context:include-filter type="annotation"expression="org.springframework.stereotype.Controller"/>
    <!--<context:include-filter type="assignable"expression="com.atguigu.controller.UserController"/>-->
</context:component-scan>

[!note]
一般我们使用的是指定排除的组件,情况二和情况三不能同时存在,只能选择一个

⑨组件所对应的bean的id

在我们使用XML方式管理bean的时候,每个bean都有一个唯一标识,便于在其他地方引用。现在使用

注解后,每个组件仍然应该有一个唯一标识。

默认情况

类名首字母小写就是bean的id。例如:UserController类对应的bean的id就是userController。

自定义bean的id

可通过标识组件的注解的value属性设置自定义的bean的id

@Service(“userService”)//默认为userServiceImpl
public class UserServiceImpl implements UserService {}

[!note]
一般我们都是根据类型获取,因为自动装配只装配当前类型的唯一一个

2.3.2、实验二:基于注解的自动装配

之前是基于xml的自动装配,但是现在我们使用注解了,就不能使用之前的那种自动装配了

使用注解的方式,不用为当前的成员变量设置get和set方法

①场景模拟

参考基于xml的自动装配

在UserController中声明UserService对象

在UserServiceImpl中声明UserDao对象

②@Autowired注解

成员变量上直接标记@Autowired注解即可完成自动装配,不需要提供setXxx()方法。以后我们在项

目中的正式用法就是这样。

@Controller
public class UserController {
    @Autowired
    private UserService userService;
    public void saveUser(){
        userService.saveUser();
    }
}
public interface UserService {
    void saveUser();
}
@Service
public class UserServiceImpl implements UserService {
    @Autowired
    private UserDao userDao;
    @Override
    public void saveUser() {
        userDao.saveUser();
    }
}
public interface UserDao {
	void saveUser();
}	
@Repository
public class UserDaoImpl implements UserDao {
    @Override
    public void saveUser() {
        System.out.println("保存成功");
    }
}
③@Autowired注解其他细节

@Autowired注解可以标记在构造器和set方法上
====

@Controller
public class UserController {
    private UserService userService;
    @Autowired
    public UserController(UserService userService){
        this.userService = userService;
    }
    public void saveUser(){
        userService.saveUser();
    }
}
@Controller
public class UserController {
    private UserService userService;
    @Autowired
    public void setUserService(UserService userService){
        this.userService = userService;
    }
    public void saveUser(){
        userService.saveUser();
    }
}

[!note]
@Autowired注解的原理
a>默认通过byType的方式,在IOC容器中通过类型匹配某个bean为属性赋值

④@Autowired工作流程

14dd

  • 首先根据所需要的组件类型到IOC容器中查找
    • 能够找到唯一的bean:直接执行装配
    • 如果完全找不到匹配这个类型的bean:装配失败
    • 和所需类型匹配的bean不止一个
      • 没有@Qualifier注解:根据@Autowired标记位置成员变量的变量名作为bean的id进行匹配
      • 能够找到:执行装配
      • 找不到:装配失败
      • 使用@Qualifier注解:根据@Qualifier注解中指定的名称作为bean的id进行匹配
      • 能够找到:执行装配
      • 找不到:装配失败
@Controller
public class UserController {
    @Autowired
    @Qualifier("userServiceImpl")
    private UserService userService;
    public void saveUser(){
        userService.saveUser();
    }
}

@Autowired中有属性required,默认值为true,因此在自动装配无法找到相应的bean时,会装

配失败
image.png

可以将属性required的值设置为true,则表示能装就装,装不上就不装,此时自动装配的属性为

默认值

但是实际开发时,基本上所有需要装配组件的地方都是必须装配的,用不上这个属性。

3、AOP

3.1、场景模拟

3.1.1、声明接口

声明计算器接口Calculator,包含加减乘除的抽象方法

public interface Calculator {
    int add(int i, int j);
    int sub(int i, int j);
    int mul(int i, int j);
    int div(int i, int j);
}

3.1.2、创建实现类

ttt15

public class CalculatorPureImpl implements Calculator {
    @Override
    public int add(int i, int j) {
        int result = i + j;
        System.out.println("方法内部 result = " + result);
        return result;
    }
    @Override
    public int sub(int i, int j) {
        int result = i - j;
        System.out.println("方法内部 result = " + result);
        return result;
    }
    @Override
    public int mul(int i, int j) {
        int result = i * j;
        System.out.println("方法内部 result = " + result);
        return result;
    }
    @Override
    public int div(int i, int j) {
        int result = i / j;
        System.out.println("方法内部 result = " + result);
        return result;
    }
}

3.1.3、创建带日志功能的实现类

www16

public class CalculatorLogImpl implements Calculator {
    @Override
    public int add(int i, int j) {
        System.out.println("[日志] add 方法开始了,参数是:" + i + "," + j);
        int result = i + j;
        System.out.println("方法内部 result = " + result);
        System.out.println("[日志] add 方法结束了,结果是:" + result);
        return result;
    }
    @Override
    public int sub(int i, int j) {
        System.out.println("[日志] sub 方法开始了,参数是:" + i + "," + j);
        int result = i - j;
        System.out.println("方法内部 result = " + result);
        System.out.println("[日志] sub 方法结束了,结果是:" + result);
        return result;
    }
    @Override
    public int mul(int i, int j) {
        System.out.println("[日志] mul 方法开始了,参数是:" + i + "," + j);
        int result = i * j;
        System.out.println("方法内部 result = " + result);
        System.out.println("[日志] mul 方法结束了,结果是:" + result);
        return result;
    }
    @Override
    public int div(int i, int j) {
        System.out.println("[日志] div 方法开始了,参数是:" + i + "," + j);
        int result = i / j;
        System.out.println("方法内部 result = " + result);
        System.out.println("[日志] div 方法结束了,结果是:" + result);
        return result;
    }
}

3.1.4、提出问题

这些方法中的日志公共实现逻辑是重复的,我们最好把方法单独拿出来,但是又不是像jdbc中连续执行的代码,这是在方法体中分散执行的代码,我们并不好抽取出来

①现有代码缺陷

针对带日志功能的实现类,我们发现有如下缺陷:

  • 对核心业务功能有干扰,导致程序员在开发核心业务功能时分散了精力
  • 附加功能分散在各个业务功能方法中,不利于统一维护
②解决思路

解决这两个问题,核心就是:解耦。我们需要把附加功能从业务功能代码中抽取出来。

③困难

解决问题的困难:要抽取的代码在方法内部,靠以前把子类中的重复代码抽取到父类的方式没法解决。所以需要引入新的技术。

3.2、代理模式

3.2.1、概念

①介绍

二十三种设计模式中的一种,属于结构型模式。它的作用就是通过提供一个代理类,让我们在调用目标方法的时候,不再是直接对目标方法进行调用,而是通过代理类间接调用。让不属于目标方法核心逻辑的代码从目标方法中剥离出来——解耦。调用目标方法时先调用代理对象的方法,减少对目标方法的调用和打扰,同时让附加功能能够集中在一起也有利于统一维护。

1ss7

使用代理后:

18ss

②生活中的代理
  • 广告商找大明星拍广告需要经过经纪人
③相关术语
  • 代理:将非核心逻辑剥离出来以后,封装这些非核心逻辑的类、对象、方法。
  • 目标:被代理“套用”了非核心逻辑代码的类、对象、方法。

3.2.2、静态代理

创建静态代理类:

public class CalculatorStaticProxy implements Calculator {
    // 将被代理的目标对象声明为成员变量
    private Calculator target;
    public CalculatorStaticProxy(Calculator target) {
        this.target = target;
    }
    @Override
    public int add(int i, int j) {
        // 附加功能由代理类中的代理方法来实现
        System.out.println("[日志] add 方法开始了,参数是:" + i + "," + j);
        // 通过目标对象来实现核心业务逻辑
        int addResult = target.add(i, j);
        System.out.println("[日志] add 方法结束了,结果是:" + addResult);
        return addResult;
    }
}

静态代理确实实现了解耦,但是由于代码都写死了,完全不具备任何的灵活性。就拿日志功能来

说,将来其他地方也需要附加日志,那还得再声明更多个静态代理类,那就产生了大量重复的代

码,日志功能还是分散的,没有统一管理

提出进一步的需求:将日志功能集中到一个代理类中,将来有任何日志需求,都通过这一个代理

[!question]
还是只能在方法执行前后添加额外的东西,不能在方法中间,而且还是多个方法都得写这个逻辑功能相同的"日志"的代码

3.2.3、动态代理

动态代理有两种:

  1. jdk动态代理,要求必须有接口,最终生成的代理类和目标类实现相同的接口在com.sun. proxy包下,类名为$proxy2
  2. cglib动态代理,最终生成的代理类会继承目标类,并且和目标类在相同的包下

ss19
#java/重要方法

[!note]
这里的功能主要依赖反射来实现

  • newProxyInstance():创建一个代理实例
    • 其中有三个参数:
      • 1、classLoader:加载动态生成的代理类的类加载器 [[javase#类的加载与ClassLoader]]
        • 2、interfaces:目标对象实现的所有接口的class对象所组成的数组
        • 3、invocationHandler:设置代理对象实现目标对象方法的过程,即代理类中如何重写接口中的抽象方法

?怎么知道要执行的方法是什么

public class ProxyFactory {
    private Object target;
    public ProxyFactory(Object target) {
        this.target = target;
    }
    public Object getProxy(){
        
        ClassLoader classLoader = target.getClass().getClassLoader();
        Class<?>[] interfaces = target.getClass().getInterfaces();
        InvocationHandler invocationHandler = new InvocationHandler() {
            @Override
            public Object invoke(Object proxy, Method method, Object[] args)
                throws Throwable {
                /**
                 * proxy:代理对象
                 * method:代理对象需要实现的方法,即其中需要重写的方法
                 * args:method所对应方法的参数
                 */
                Object result = null;
                try {
                    System.out.println("[动态代理][日志] "+method.getName()+",参数:"+ Arrays.toString(args));
                     result = method.invoke(target, args);
                     System.out.println("[动态代理][日志] "+method.getName()+",结 果:"+ result);
                 } catch (Exception e) {
                   e.printStackTrace();
                   System.out.println("[动态代理][日志] "+method.getName()+",异常:"+e.getMessage());
                  } finally {
                      System.out.println("[动态代理][日志] "+method.getName()+",方法执行完毕");
                  }
                  return result;
               }   
            };    
            return Proxy.newProxyInstance(classLoader, interfaces,invocationHandler);
      }
}                                     

3.2.4、测试

[!note]
我们不知道代理类的类型,但是我们知道代理类所实现的接口,直接向上转型即可

@Test
public void testDynamicProxy(){
    ProxyFactory factory = new ProxyFactory(new CalculatorLogImpl());
    Calculator proxy = (Calculator) factory.getProxy();
    proxy.div(1,0);
    //proxy.div(1,1);
}

image.png

3.3、AOP概念及相关术语

在AOP中切面就是与业务逻辑独立,但又垂直存在于业务逻辑的代码结构中的通用功能组合;切面与业务逻辑相交的点就是切点;连接点就是把业务逻辑离散化后的关键节点;切点属于连接点,是连是连接点的子集;Advice(增强)就是切面在切点上要执行的功能增加的具体操作;在切点上可以把要完成增强操作的目标对象(Target)连接到切面里,这个连接的方式就叫织入。

3.3.1、概述

AOP(Aspect Oriented Programming)是一种设计思想,是软件设计领域中的面向切面编程,它是面向对象编程的一种补充和完善,它以通过预编译方式和运行期动态代理方式实现在不修改源代码的情况下给程序动态统一添加额外功能的一种技术。

3.3.2、相关术语

①横切关注点

从每个方法中抽取出来的同一类非核心业务。==(就比如之前的日志功能)==在同一个项目中,我们可以使用多个横切关注点对相关方法进行多个不同方面的增强。

这个概念不是语法层面天然存在的,而是根据附加功能的逻辑上的需要:有十个附加功能,就有十个横切关注点。2rrr0

②通知

横切关注点在切面中就叫做通知
每一个横切关注点上要做的事情都需要写一个方法来实现,这样的方法就叫通知方法。

  • 前置通知:在被代理的目标方法执行
  • 返回通知:在被代理的目标方法成功结束后执行(寿终正寝
  • 异常通知:在被代理的目标方法异常结束后执行(死于非命
  • 后置通知:在被代理的目标方法最终结束后执行(盖棺定论
  • 环绕通知:使用try…catch…finally结构围绕整个被代理的目标方法,包括上面四种通知对应的所

有位置2rr1

③切面

封装通知方法(横切关注点)的类。
rr

④目标

被代理的目标对象。

⑤代理

向目标对象应用通知之后创建的代理对象。

⑥连接点

连接点就是我们抽取方法的位置,即抽取横切关注点的位置
这也是一个纯逻辑概念,不是语法定义的。

把方法排成一排,每一个横切位置看成x轴方向,把方法从上到下执行的顺序看成y轴,x轴和y轴的交叉点就是连接点。

23r

⑦切入点

一个表达式,然后来定位连接点从而能让横切关注点通过切入点表达式插入在正确的位置
定位连接点的方式。

每个类的方法中都包含多个连接点,所以连接点是类中客观存在的事物(从逻辑上来说)。

如果把连接点看作数据库中的记录,那么切入点就是查询记录的 SQL 语句。

Spring 的 AOP 技术可以通过切入点定位到特定的连接点。

切点通过 org.springframework.aop.Pointcut 接口进行描述,它使用类和方法作为连接点的查询条

件。

3.3.3、作用

  • 简化代码:把方法中固定位置的重复的代码抽取出来,让被抽取的方法更专注于自己的核心功能,提高内聚性。

  • 代码增强:把特定的功能封装到切面类中,看哪里有需要,就往上套,被套用了切面逻辑的方法就被切面给增强了。

3.4、基于注解的AOP

3.4、基于注解的AOP

3.4.1、技术说明

2ss4

  • 动态代理(InvocationHandler):JDK原生的实现方式,需要被代理的目标类必须实现接口。因

为这个技术要求代理对象和目标对象实现同样的接口(兄弟两个拜把子模式)。

  • cglib:通过继承被代理的目标类(认干爹模式)实现代理,所以不需要目标类实现接口。

  • AspectJ:本质上是静态代理,将代理逻辑“织入”被代理的目标类编译得到的字节码文件,所以最终效果是动态的。weaver就是织入器。Spring只是借用了AspectJ中的注解。

3.4.2、准备工作

①添加依赖

在IOC所需依赖基础上再加入下面依赖即可:

<!-- spring-aspects会帮我们传递过来aspectjweaver -->
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-aspects</artifactId>
    <version>5.3.1</version>
</dependency>
②准备被代理的目标资源

接口:

public interface Calculator {
    int add(int i, int j);
    int sub(int i, int j);
    int mul(int i, int j);
    int div(int i, int j);
}

实现类:

@Component
public class CalculatorPureImpl implements Calculator {
    @Override
    public int add(int i, int j) {
        int result = i + j;
        System.out.println("方法内部 result = " + result);
        return result;
    }
    @Override
    public int sub(int i, int j) {
        int result = i - j;
        System.out.println("方法内部 result = " + result);
        return result;
    }
    @Override
    public int mul(int i, int j) {
        int result = i * j;
        System.out.println("方法内部 result = " + result);
        return result;
    }
    @Override
    public int div(int i, int j) {
        int result = i / j;
        System.out.println("方法内部 result = " + result);
        return result;
    }
}

3.4.3、创建切面类并配置

在Spring的配置文件中配置:

   <!--
        基于注解的AOP的实现:
        1、将目标对象和切面交给IOC容器管理(注解+扫描)
        2、开启AspectJ的自动代理,为目标对象自动生成代理
        3、将切面类通过注解@Aspect标识
	-->
<context:component-scan base-package="com.atguigu.spring" >  
  
</context:component-scan>  
<!--开启基于注解的aop功能-->  
    <aop:aspectj-autoproxy />

把前置通知通过切入点表达式放入切入点

[!note]
1.loggeraspect类相当于一个工具类,不过它会在被引用时通过@Before(切入点)引用这个方法插入到代理类,
而此时代理类并没有在aop.annotation包下
2.注意。此时aop.annotation包中并没有代理类对象,代理类对象和前面讲的动态代理一样,
代理类在底层代码被匿名创建,所以只能在text测试类中被同接口调用

@Aspect //标记为切面


@Component  
@Aspect  //标记为切面  
public class LoggerAspect {  
  
    @Before("execution(public int com.atguigu.spring.pojo.Calculator.add(int,int))")  
    public void beforeMethod(JoinPoint joinPoint){  
  
        System.out.println("Logger-->前置通知:");  
    }  
}

测试

image.png

[!note]
只能通过接口来获取代理类,并且通过目标类的类型获取不到对象

joinPoint.getSignature() 用来获取连接点所对应方法的签 名信息

[!note]
切入点表达式:设置在标识通知的注解的value属性中
execution(public int com.atguigu.spring . aop.annotation.caLcuLatorImpl.add(int,int)execution(* com.atguigu.spring.aop.annotation.calculatorImpl. *(. .)
第一个*表示任意的访问修饰符和返回值类型
第二个*表示类中任意的方法
..表示任意的参数列表
类的地方也可以使用*,表示包下所有的类

[!note]
在返回通知中若要获取目标对象方法的返回值
只需要通过@AfterReturning注解的leturning属性
就可以将通知方法的某个参数指定为接收目标对象方法的返回值的参数

@AfterReturning(value = "execution(*com.atguigu.aop.annotation.CalculatorImpl.*(..))", returning = "result")
设置返回值名.下面方法的参数列表中要有同名的返回值名,returning就是来设置目标对象返回值的一个参数的参数名的
public void afterReturningMethod(JoinPoint joinPoint, Object result)

[!note]
throwing属性可以将通知方法的某个参数指定为接收异常返回值的参数,在异常通知中若要获取目标对象方法的异常
只需要通过AfterThrowing注解的throwing属性
就可以将通知方法的某个参数指定为接收目标对象方法出现的异常的参数

@AfterThrowing(value = "execution(*com.atguigu.aop.annotation.CalculatorImpl.*(..))", throwing = "ex")
public void afterThrowingMethod(JoinPoint joinPoint, Throwable ex)
`

// @Aspect表示这个类是一个切面类
@Aspect
// @Component注解保证这个切面类能够放入IOC容器
@Component
public class LogAspect {
    @Before("execution(public int com.atguigu.aop.annotation.CalculatorImpl.*(..))")
public void beforeMethod(JoinPoint joinPoint){
	String methodName = joinPoint.getSignature().getName();
	String args = Arrays.toString(joinPoint.getArgs());
	System.out.println("Logger-->前置通知,方法名:"+methodName+",参数:"+args);
	}
    @After("execution(* com.atguigu.aop.annotation.CalculatorImpl.*(..))")
	public void afterMethod(JoinPoint joinPoint){
		String methodName = joinPoint.getSignature().getName();
		System.out.println("Logger-->后置通知,方法名:"+methodName);
	}
    @AfterReturning(value = "execution(*com.atguigu.aop.annotation.CalculatorImpl.*(..))", returning = "result")
	public void afterReturningMethod(JoinPoint joinPoint, Object result){
		String methodName = joinPoint.getSignature().getName();
		System.out.println("Logger-->返回通知,方法名:"+methodName+",结果:"+result);
	} 
    @AfterThrowing(value = "execution(*com.atguigu.aop.annotation.CalculatorImpl.*(..))", throwing = "ex")
	public void afterThrowingMethod(JoinPoint joinPoint, Throwable ex){
		String methodName = joinPoint.getSignature().getName();
		System.out.println("Logger-->异常通知,方法名:"+methodName+",异常:"+ex);
	}
    @Around("execution(* com.atguigu.aop.annotation.CalculatorImpl.*(..))")
    //返回值类型需要和目标方法的返回值类型相同
	public Object aroundMethod(ProceedingJoinPoint joinPoint){
        String methodName = joinPoint.getSignature().getName();
		String args = Arrays.toString(joinPoint.getArgs());
		Object result = null;
        try {
            System.out.println("环绕通知-->目标对象方法执行之前");
            //目标对象(连接点)方法的执行
            result = joinPoint.proceed();
            System.out.println("环绕通知-->目标对象方法返回值之后");
		} catch (Throwable throwable) {
            throwable.printStackTrace();
            System.out.println("环绕通知-->目标对象方法出现异常时");
		} finally {
			System.out.println("环绕通知-->目标对象方法执行完毕");
		}
		return result;
	}
}

@Pointcut声明—个重用的切入点表达式
使用方式:@Before( “pointcut()”)

image.png
共用一个切入点表达式:通过方法获得注解,然后获得value值即共用的切入点表达式来定位切入点

3.4.4、各种通知

  • 前置通知:使用@Before注解标识,在被代理的目标方法执行
  • 返回通知:使用@AfterReturning注解标识,在被代理的目标方法成功结束后执行(寿终正寝(try的最后)
  • 异常通知:使用@AfterThrowing注解标识,在被代理的目标方法异常结束后执行(死于非命
  • 后置通知:使用@After注解标识,在被代理的目标方法最终结束后执行(盖棺定论
  • 环绕通知:使用@Around注解标识,使用try…catch…finally结构围绕整个被代理的目标方法,包

括上面四种通知对应的所有位置

各种通知的执行顺序:

  • Spring版本5.3.x以前:
    • 前置通知
    • 目标操作
    • 后置通知
    • 返回通知或异常通知
  • Spring版本5.3.x以后:
    • 前置通知
    • 目标操作
    • 返回通知或异常通知
    • 后置通知

3.4.9、切面的优先级

相同目标方法上同时存在多个切面时,切面的优先级控制切面的内外嵌套顺序。

  • 优先级高的切面:外面
  • 优先级低的切面:里面

使用@Order注解可以控制切面的优先级:
默认的优先级是Integer的最大值

  • @Order(较小的数):优先级高
  • @Order(较大的数):优先级低

2k7

3.5,基于XML的AOP(了解)

3.5.1、准备工作

参考基于注解的AOP环境

3.5.2、实现

<context:component-scan base-package="com.atguigu.aop.xml"></context:component-scan>
<aop:config>
    <!--配置切面类-->
    <aop:aspect ref="loggerAspect"> //因为切面类名就是LoggerAspect扫描进来首字母小写
    <!--        设置一个公共的切入点表达式-->
        <aop:pointcut id="pointCut" expression="execution(*com.atguigu.aop.xml.CalculatorImpl.*(..))"/>
        <aop:before method="beforeMethod" pointcut-ref="pointCut"></aop:before>
        <aop:after method="afterMethod" pointcut-ref="pointCut"></aop:after>
        <aop:after-returning method="afterReturningMethod" returning="result"pointcut-ref="pointCut"></aop:after-returning>
        <aop:after-throwing method="afterThrowingMethod" throwing="ex" pointcut-ref="pointCut"></aop:after-throwing>
        <aop:around method="aroundMethod" pointcut-ref="pointCut"></aop:around>
    </aop:aspect>
    <aop:aspect ref="validateAspect" order="1">
        <aop:before method="validateBeforeMethod" pointcut-ref="pointCut">
        </aop:before>
    </aop:aspect>
</aop:config>

4、声明式事务

4.1、JdbcTemplate

4.1.1、简介

Spring 框架对 JDBC 进行封装,使用 JdbcTemplate 方便实现对数据库操作

4.1.2、准备工作

①加入依赖
    <dependencies>
        <!-- 基于Maven依赖传递性,导入spring-context依赖即可导入当前所需所有jar包 -->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context</artifactId>
            <version>5.3.1</version>
        </dependency>
        <!-- Spring 持久化层支持jar包 -->
        <!-- Spring 在执行持久化层操作、与持久化层技术进行整合过程中,需要使用orm、jdbc、tx三个jar包 -->
        <!-- 导入 orm 包就可以通过 Maven 的依赖传递性把其他两个也导入 -->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-orm</artifactId>
            <version>5.3.1</version>
        </dependency>
        <!-- Spring 测试相关 -->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-test</artifactId>
            <version>5.3.1</version>
        </dependency>
        <!-- junit测试 -->
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.12</version>
            <scope>test</scope>
        </dependency>
        <!-- MySQL驱动 -->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>8.0.16</version>
        </dependency>
        <!-- 数据源 -->
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid</artifactId>
            <version>1.0.31</version>
        </dependency>
    </dependencies>
②创建jdbc.properties
jdbc.user=root
jdbc.password=789632145
jdbc.url=jdbc:mysql://localhost:3306/ssm
jdbc.driver=com.mysql.cj.jdbc.Driver
③配置Spring的配置文件

第三方jar包为我们提供的类,不能用注解加扫描的方式导入IOC容器.得用xml的方式==(毕竟我们不好往别人的jar包中加入注解)==
`classpath:'是类路径的意思

<!-- 导入外部属性文件 -->
<context:property-placeholder location="classpath:jdbc.properties" />
<!-- 配置数据源 -->
<bean id="druidDataSource" class="com.alibaba.druid.pool.DruidDataSource">
    <property name="url" value="${atguigu.url}"/>
    <property name="driverClassName" value="${atguigu.driver}"/>
    <property name="username" value="${atguigu.username}"/>
    <property name="password" value="${atguigu.password}"/>
</bean>
<!-- 配置 JdbcTemplate -->
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
    <!-- 装配数据源 -->
    <property name="dataSource" ref="druidDataSource"/>
</bean>

4.1.3、测试

spring-test让spring在测试环境之运行,这样就不用每一回都获取ioc容器了

①在测试类装配 JdbcTemplate

,我们在ioc容器已经引入的jdbcTemplate

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:spring-jdbc.xml")
public class JDBCTemplateTest {
    @Autowired
    private JdbcTemplate jdbcTemplate;
}
②测试增删改功能

dbutils???

//'指定当前测试类在spring的测试环境中执行,此时就可以通过注入的方式直接获取Ioc容器中bean  
@RunWith(SpringJUnit4ClassRunner.class)  
@ContextConfiguration("classpath:spring-jdbc.xml")  
public class JdbcTemplateTest {  
    @Autowired  
    private JdbcTemplate jdbcTemplate;  
    @Test  
    public void testInsert(){  
        String sql = "insert into t_user values(null,?,?,?,?,?)";  
        int result = jdbcTemplate.update(sql, "ss","123", 23, "男","123@qq.com");  
  
    }  
  
  
}
③查询一条数据为实体类对象
@Test
//查询一条数据为一个实体类对象
public void testSelectEmpById(){
    String sql = "select * from t_user where id = ?";
    Emp emp = jdbcTemplate.queryForObject(sql, new BeanPropertyRowMapper<>(User.class), 1);
    System.out.println(emp);
}
④查询多条数据为一个list集合
@Test
//查询多条数据为一个list集合
public void testSelectList(){
    String sql = "select * from t_user";
    //第二个参数是选择了实体类的类名作为映射
    List<User> list = jdbcTemplate.query(sql, new BeanPropertyRowMapper<>(User.class));
    list.forEach(emp -> System.out.println(emp));
}
⑤查询单行单列的值
@Test
//查询单行单列的值
public void selectCount(){
    String sql = "select count(id) from t_user";
    Integer count = jdbcTemplate.queryForObject(sql, Integer.class);
    System.out.println(count);
}

4.2、声明式事务概念

4.2.1、编程式事务

事务功能的相关操作全部通过自己编写代码来实现:

Connection conn = ...;
try {
    // 开启事务:关闭事务的自动提交
    conn.setAutoCommit(false);
    // 核心操作
    // 提交事务
    conn.commit();
}catch(Exception e){
    // 回滚事务
    conn.rollBack();
}finally{
    // 释放数据库连接
    conn.close();
}

编程式的实现方式存在缺陷:

  • 细节没有被屏蔽:具体操作过程中,所有细节都需要程序员自己来完成,比较繁琐。
  • 代码复用性不高:如果没有有效抽取出来,每次实现功能都需要自己编写代码,代码就没有得到复用。

4.2.2、声明式事务

既然事务控制的代码有规律可循,代码的结构基本是确定的,所以框架就可以将固定模式的代码抽取出来,进行相关的封装。

封装起来后,我们只需要在配置文件中进行简单的配置即可完成操作。

  • 好处1:提高开发效率
  • 好处2:消除了冗余的代码
  • 好处3:框架会综合考虑相关领域中在实际开发环境下有可能遇到的各种问题,进行了健壮性、性

能等各个方面的优化

所以,我们可以总结下面两个概念:

  • 编程式自己写代码实现功能
  • 声明式:通过配置框架实现功能

4.3、基于注解的声明式事务

4.3.1、准备工作

①加入依赖
<dependencies>
    <!-- 基于Maven依赖传递性,导入spring-context依赖即可导入当前所需所有jar包 -->
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-context</artifactId>
        <version>5.3.1</version>
    </dependency>
    <!-- Spring 持久化层支持jar包 -->
    <!-- Spring 在执行持久化层操作、与持久化层技术进行整合过程中,需要使用orm、jdbc、tx三个jar包 -->
    <!-- 导入 orm 包就可以通过 Maven 的依赖传递性把其他两个也导入 -->
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-orm</artifactId>
        <version>5.3.1</version>
    </dependency>
    <!-- Spring 测试相关 -->
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-test</artifactId>
        <version>5.3.1</version>
    </dependency>
    <!-- junit测试 -->
    <dependency>
        <groupId>junit</groupId>
        <artifactId>junit</artifactId>
        <version>4.12</version>
        <scope>test</scope>
    </dependency>
    <!-- MySQL驱动 -->
    <dependency>
        <groupId>mysql</groupId>
        <artifactId>mysql-connector-java</artifactId>
        <version>8.0.16</version>
    </dependency>
    <!-- 数据源 -->
    <dependency>
        <groupId>com.alibaba</groupId>
        <artifactId>druid</artifactId>
        <version>1.0.31</version>
    </dependency>
</dependencies>
②创建jdbc.properties
jdbc.user=root
jdbc.password=atguigu
jdbc.url=jdbc:mysql://localhost:3306/ssm?serverTimezone=UTC
jdbc.driver=com.mysql.cj.jdbc.Driver
③配置Spring的配置文件
<!--扫描组件-->
<context:component-scan base-package="com.atguigu.spring.tx.annotation">
</context:component-scan>
<!-- 导入外部属性文件 -->
<context:property-placeholder location="classpath:jdbc.properties" />
<!-- 配置数据源 -->
<bean id="druidDataSource" class="com.alibaba.druid.pool.DruidDataSource">
    <property name="url" value="${jdbc.url}"/>
    <property name="driverClassName" value="${jdbc.driver}"/>
    <property name="username" value="${jdbc.username}"/>
    <property name="password" value="${jdbc.password}"/>
</bean>
<!-- 配置 JdbcTemplate -->
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
    <!-- 装配数据源 -->
    <property name="dataSource" ref="druidDataSource"/>
</bean>
④创建表
CREATE TABLE `t_book` (
    `book_id` int(11) NOT NULL AUTO_INCREMENT COMMENT '主键',
    `book_name` varchar(20) DEFAULT NULL COMMENT '图书名称',
    `price` int(11) DEFAULT NULL COMMENT '价格',
    `stock` int(10) unsigned DEFAULT NULL COMMENT '库存(无符号)',
    PRIMARY KEY (`book_id`)
) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8;
insert into `t_book`(`book_id`,`book_name`,`price`,`stock`) values (1,'斗破苍穹',80,100),(2,'斗罗大陆',50,100);
CREATE TABLE `t_user` (
    `user_id` int(11) NOT NULL AUTO_INCREMENT COMMENT '主键',
    `username` varchar(20) DEFAULT NULL COMMENT '用户名',
    `balance` int(10) unsigned DEFAULT NULL COMMENT '余额(无符号)',
    PRIMARY KEY (`user_id`)
) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8;
insert into `t_user`(`user_id`,`username`,`balance`) values (1,'admin',50);
⑤创建组件

创建BookController:

@Controller
public class BookController {
    @Autowired
    private BookService bookService;
    public void buyBook(Integer bookId, Integer userId){
        bookService.buyBook(bookId, userId);
    }
}

创建接口BookService:

public interface BookService {
	void buyBook(Integer bookId, Integer userId);
}

创建实现类BookServiceImpl:

@Service
public class BookServiceImpl implements BookService {
    @Autowired
    private BookDao bookDao;
    @Override
    public void buyBook(Integer bookId, Integer userId) {
        //查询图书的价格
        Integer price = bookDao.getPriceByBookId(bookId);
        //更新图书的库存
        bookDao.updateStock(bookId);
        //更新用户的余额
        bookDao.updateBalance(userId, price);
    }
}

创建接口BookDao:

public interface BookDao {
    Integer getPriceByBookId(Integer bookId);
    void updateStock(Integer bookId);
    void updateBalance(Integer userId, Integer price);
}

创建实现类BookDaoImpl:

@Repository
public class BookDaoImpl implements BookDao {
    @Autowired
    private JdbcTemplate jdbcTemplate;
    @Override
    public Integer getPriceByBookId(Integer bookId) {
        String sql = "select price from t_book where book_id = ?";
        return jdbcTemplate.queryForObject(sql, Integer.class, bookId);
    }
    @Override
    public void updateStock(Integer bookId) {
        String sql = "update t_book set stock = stock - 1 where book_id = ?";
        jdbcTemplate.update(sql, bookId);
    }
    @Override
    public void updateBalance(Integer userId, Integer price) {
        String sql = "update t_user set balance = balance - ? where user_id =?";
            jdbcTemplate.update(sql, price, userId);
    }
}

4.3.2、测试无事务情况

①创建测试类

用自动装配的方式获取ioc容器中的bean

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:tx-annotation.xml")
public class TxByAnnotationTest {
    @Autowired
    private BookController bookController;
    @Test
    public void testBuyBook(){
        bookController.buyBook(1, 1);
    }
}
②模拟场景

用户购买图书,先查询图书的价格,再更新图书的库存和用户的余额

假设用户id为1的用户,购买id为1的图书

用户余额为50,而图书价格为80

购买图书之后,用户的余额为-30,数据库中余额字段设置了无符号,因此无法将-30插入到余额字段

此时执行sql语句会抛出SQLException

③观察结果

因为没有添加事务,图书的库存更新了,但是用户的余额没有更新

显然这样的结果是错误的,购买图书是一个完整的功能,更新库存和更新余额要么都成功要么都失败

4.3.3、加入事务

①添加事务配置

在Spring的配置文件中添加配置:
事务管理器依赖数据源处理数据库的提交,关闭等操作

//配置事务管理器
<bean id="transactionManager"
      class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
    <property name="dataSource" ref="dataSource"></property>
</bean>
<!--
    开启事务的注解驱动
    通过注解@Transactional所标识的方法或标识的类中所有的方法,都会被事务管理器管理事务
-->
<!-- transaction-manager属性的默认值是transactionManager,如果事务管理器bean的id正好就
是这个默认值,则可以省略这个属性 -->
<tx:annotation-driven transaction-manager="transactionManager" />

通过注解@Transactional所标识的方法或标识的类中所有的方法(标识类就会作用于所有方法),都会被事务管理器管理事务
注意:导入的名称空间需要 tx 结尾的那个。

[!note]
事务管理器才是切面,里面的东西才是通知, 开启事务的注解驱动作用是把事务管理器的通知作用到连接点上面

2aa8

②添加事务注解

因为service层表示业务逻辑层,一个方法表示一个完成的功能,因此处理事务一般在service层处理

在BookServiceImpl的buybook()添加注解@Transactional

③观察结果

由于使用了Spring的声明式事务,更新库存和更新余额都没有执行

4.3.4、@Transactional注解标识的位置

@Transactional标识在方法上,咋只会影响该方法

@Transactional标识的类上,咋会影响类中所有的方法

总结

声明式事务的配置步骤:

1、在spring的配置文件中配置事务管理器
2、开启事务的注解驱动在需要被事务管理的方法上,添加@Transactional注解,该方法就会被事务管理

4.3.5、事务属性:只读

①介绍

对一个查询操作来说,如果我们把它设置成只读,就能够明确告诉数据库,这个操作不涉及写操作。这样数据库就能够针对查询操作来进行优化。

②使用方式
@Transactional(readOnly = true)
public void buyBook(Integer bookId, Integer userId) {
    //查询图书的价格
    Integer price = bookDao.getPriceByBookId(bookId);
    //更新图书的库存
    bookDao.updateStock(bookId);
    //更新用户的余额
    bookDao.updateBalance(userId, price);
    //System.out.println(1/0);
}
③注意

对增删改操作设置只读会抛出下面异常:

Caused by: java.sql.SQLException: Connection is read-only. Queries leading to data modification

are not allowed

4.3.6、事务属性:超时

①介绍

事务在执行过程中,有可能因为遇到某些问题,导致程序卡住,从而长时间占用数据库资源。而长时间占用资源,大概率是因为程序运行出现了问题(可能是Java程序或MySQL数据库或网络连接等等)。

此时这个很可能出问题的程序应该被回滚,撤销它已做的操作,事务结束,把资源让出来,让其他正常程序可以执行。

概括来说就是一句话:超时回滚,释放资源。

②使用方式
@Transactional(timeout = 3)
public void buyBook(Integer bookId, Integer userId) {
    try {
        TimeUnit.SECONDS.sleep(5);
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
    //查询图书的价格
    Integer price = bookDao.getPriceByBookId(bookId);
    //更新图书的库存
    bookDao.updateStock(bookId);
    //更新用户的余额
    bookDao.updateBalance(userId, price);
    //System.out.println(1/0);
}
③观察结果

执行过程中抛出异常:

org.springframework.transaction.TransactionTimedOutException: Transaction timed out:

deadline was Fri Jun 04 16:25:39 CST 2022

4.3.7、事务属性:回滚策略

①介绍

声明式事务默认只针对运行时异常回滚,编译时异常不回滚。

可以通过@Transactional中相关属性设置回滚策略
(no是不因为什么而撤回)

  • rollbackFor属性:需要设置一个Class类型的对象
  • rollbackForClassName属性:需要设置一个字符串类型的全类名
  • noRollbackFor属性:需要设置一个Class类型的对象
  • noRollbackForClassName属性:需要设置一个字符串类型的全类名
②使用方式
@Transactional(noRollbackFor = ArithmeticException.class)
//@Transactional(noRollbackForClassName = "java.lang.ArithmeticException")
public void buyBook(Integer bookId, Integer userId) {
    //查询图书的价格
    Integer price = bookDao.getPriceByBookId(bookId);
    //更新图书的库存
    bookDao.updateStock(bookId);
    //更新用户的余额
    bookDao.updateBalance(userId, price);
    System.out.println(1/0);
}
③观察结果

虽然购买图书功能中出现了数学运算异常(ArithmeticException),但是我们设置的回滚策略是,当

出现ArithmeticException不发生回滚,因此购买图书的操作正常执行
image.png

4.3.8、事务属性:事务隔离级别

①介绍

数据库系统必须具有隔离并发运行各个事务的能力,使它们不会相互影响,避免各种并发问题。一个事

务与其他事务隔离的程度称为隔离级别。SQL标准中规定了多种事务隔离级别,不同隔离级别对应不同

的干扰程度,隔离级别越高,数据一致性就越好,但并发性越弱。

隔离级别一共有四种:

  • 读未提交:READ UNCOMMITTED==(有脏读的问题)==

允许Transaction01读取Transaction02未提交的修改。

  • 读已提交:READ COMMITTED、

要求Transaction01只能读取Transaction02已提交的修改。

  • 可重复读:REPEATABLE READ

确保Transaction01可以多次从一个字段中读取到相同的值,即Transaction01执行期间禁止其它

事务对这个字段进行更新。

  • 串行化:SERIALIZABLE

确保Transaction01可以多次从一个表中读取到相同的行,在Transaction01执行期间,禁止其它

事务对这个表进行添加、更新、删除操作。可以避免任何并发问题,但性能十分低下。

各个隔离级别解决并发问题的能力见下表:

隔离级别脏读不可重复读幻读
READ UNCOMMITTED
READ COMMITTED
REPEATABLE READ
SERIALIZABLE

各种数据库产品对事务隔离级别的支持程度:

隔离级别OracleMySQL
READ UNCOMMITTED×
READ COMMITTED√(默认)
REPEATABLE READ×√(默认)
SERIALIZABLE

[!note]
幻读和不可重复读区别:不可重复读的重点是修改;同样的条件,第1次和第2次读取的值不一样。幻读的重点在于新增或者删除;同样的条件, 第1次和第2次读出来的记录数不一样。从控制角度来看,不可重复读只需要锁住满足条件的记录,幻读要锁住满足条件及其相近的记录。

②使用方式
@Transactional(isolation = Isolation.DEFAULT)//使用数据库默认的隔离级别
@Transactional(isolation = Isolation.READ_UNCOMMITTED)//读未提交
@Transactional(isolation = Isolation.READ_COMMITTED)//读已提交
@Transactional(isolation = Isolation.REPEATABLE_READ)//可重复读
@Transactional(isolation = Isolation.SERIALIZABLE)//串行化

4.3.9、事务属性:事务传播行为

image.png
如果用checkout的事务(结账,包含两个buybook),如果一本书买不成功就,总体不成功,如果用buybook的事务,一本书购买不成功然后回滚,对买另外一本书没有影响

①介绍

当事务方法被另一个事务方法调用时,必须指定事务应该如何传播。例如:方法可能继续在现有事务中运行,也可能开启一个新事务,并在自己的事务中运行。

②测试

创建接口CheckoutService:

public interface CheckoutService {
	void checkout(Integer[] bookIds, Integer userId);
}

创建实现类CheckoutServiceImpl:

@Service
public class CheckoutServiceImpl implements CheckoutService {
    @Autowired
    private BookService bookService;
    @Override
    @Transactional
    //一次购买多本图书
    public void checkout(Integer[] bookIds, Integer userId) {
        for (Integer bookId : bookIds) {
            bookService.buyBook(bookId, userId);
        }
    }
}

在BookController中添加方法:

@Autowired
private CheckoutService checkoutService;
public void checkout(Integer[] bookIds, Integer userId){
    checkoutService.checkout(bookIds, userId);
}

在数据库中将用户的余额修改为100元

③观察结果

可以通过@Transactional中的propagation属性设置事务传播行为

修改BookServiceImpl中buyBook()上,注解@Transactional的propagation属性

@Transactional(propagation = Propagation.REQUIRED),默认情况,表示如果当前线程上有已经开

启的事务可用,那么就在这个事务中运行。经过观察,购买图书的方法buyBook()在checkout()中被调

用,checkout()上有事务注解,因此在此事务中执行。所购买的两本图书的价格为80和50,而用户的余额为100,因此在购买第二本图书时余额不足失败,导致整个checkout()回滚,即只要有一本书买不

了,就都买不了
@Transactional(propagation=Propagation.REQUIRES_NEW), 表示不管当前线程上是否有已经开启的事务,都要开启新事务。同样的场景,每次购买图书都是在buyBook()的事务中执行,因此第一本图书购买成功,事务结束,第二本图书购买失败,只在第二次的buyBook()中回滚,购买第一本图书不受影响,即能买几本就买几本

image.png

4.4、基于XML的声明式事务

4.3.1、场景模拟

参考基于注解的声明式事务

4.3.2、修改Spring配置文件

是service实现类的每一个方法
将Spring配置文件中去掉tx:annotation-driven 标签,并添加配置:
事务通知 :是用来配置事务的一些细节问题的,这样就可以通过切入点表达式作用于连接点了

<aop:config>
    <!-- 配置事务通知和切入点表达式 -->
    <aop:advisor advice-ref="txAdvice" pointcut="execution(*com.atguigu.spring.tx.xml.service.impl.*.*(..))"></aop:advisor>
</aop:config>
<!-- tx:advice标签:配置事务通知 -->
<!-- id属性:给事务通知标签设置唯一标识,便于引用 -->
<!-- transaction-manager属性:关联事务管理器 -->
<tx:advice id="txAdvice" transaction-manager="transactionManager">
    <tx:attributes>
        <!-- tx:method标签:配置具体的事务方法 -->
        <!-- name属性:指定方法名,可以使用星号代表多个字符 -->
        <tx:method name="get*" read-only="true"/>
        <tx:method name="query*" read-only="true"/>
        <tx:method name="find*" read-only="true"/>
        <!-- read-only属性:设置只读属性 -->
        <!-- rollback-for属性:设置回滚的异常 -->
        <!-- no-rollback-for属性:设置不回滚的异常 -->
        <!-- isolation属性:设置事务的隔离级别 -->
        <!-- timeout属性:设置事务的超时属性 -->
        <!-- propagation属性:设置事务的传播行为 -->
        <tx:method name="save*" read-only="false" rollback-for="java.lang.Exception" propagation="REQUIRES_NEW"/>
        <tx:method name="update*" read-only="false" rollback-for="java.lang.Exception" propagation="REQUIRES_NEW"/>
        <tx:method name="delete*" read-only="false" rollback-for="java.lang.Exception" propagation="REQUIRES_NEW"/>
    </tx:attributes>
</tx:advice>

[!danger]
没设置的方法不会被事务所管理

注意:基于xml实现的声明式事务,必须引入aspectJ的依赖
`

> <dependency>
>  <groupId>org.springframework</groupId>
>  <artifactId>spring-aspects</artifactId>
>  <version>5.3.1</version>
> </dependency>
> ```

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值