1.1. 第一章 Spring框架

1.1.1. 1、Spring架构体系

image-20210126213340724

Core Container

Core、Bean
Spring Core Spring Bean核心容器提供 Spring 框架的基本功能。核心容器的主要组件是 BeanFactory,它是工厂模式的实现。BeanFactory 使用控制反转 (IOC)模式将应用程序的配置和依赖性规范与实际的应用程序代码分开

Context
Spring 上下文是一个配置文件,向 Spring 框架提供上下文信息。Spring 上下文包括企业服务,例如 JNDI、EJB、电子邮件、国际化、事件机制、校验和调度功能

SpEL
Spring 表达式语言全称为 “Spring Expression Language”,缩写为 “SpEL” ,类似于 Struts2  中使用的 OGNL 表达式语言,能在运行时构建复杂表达式、存取对象图属性、对象方法调用等等,并且能与 Spring 功能完美整合,如能用来配置  Bean 定义

Data Access

JDBC
Spring 对 JDBC 的封装模块,提供了对关系数据库的访问

ORM
Spring ORM 模块,提供了对 hibernate5 和 JPA 的集成

Transaction
Spring 简单而强大的事务管理功能,包括声明式事务和编程式事务

Web

WebMVC
MVC 框架是一个全功能的构建 Web 应用程序的 MVC 实现。通过策略接口,MVC 框架变成为高度可配置的

WebFlux
基于 Reactive 库的响应式的 Web 开发框架,springcloud里面的gateway采用的就是webflux

WebSocket
Spring 4.0 的一个最大更新是增加了对 Websocket 的支持,Websocket 提供了一个在 Web 应用中实现高效、双向通讯,需考虑客户端(浏览器)和服务端之间高频和低延时消息交换的机制,一般的应用场景有:在线交易、网页聊天、游戏、协作、数据可视化等

AOP

aop
通过配置管理特性,Spring AOP 模块直接将面向方面的编程功能集成到了 Spring 框架中。所以,可以很容易地使 Spring 框架管理的任何对象支持 AOP。 Spring AOP 模块为基于 Spring 的应用程序中的对象提供了事务管理服务。通过使用 Spring AOP,不用依赖 EJB 组件,就可以将声明性事务管理集成到应用程序中。

Aspects
该模块为与 AspectJ 的集成提供支持

Instrumentation
该层为类检测和类加载器实现提供支持

JMS

提供了一个 JMS 集成框架,简化了 JMS API 的使用。

Test

该模块为使用 JUnit 和 TestNG 进行测试提供支持

Messaging

该模块为 STOMP 提供支持。它还支持注解编程模型,该模型用于从 WebSocket 客户端路由和处理 STOMP 消息

1.1.2. 2、Spring框架优势

  • DIDependency Injection(DI) 方法,使得setter方法或构造器管理 JavaBean之间的关系。
  • 轻量级:与 EJB 容器相比较,IoC 容器更加趋向于轻量级。这样一来 IoC 容器在有限的内存和 CPU 资源的情况下,进行应用程序的开发和发布就变得十分有利。
  • 面向切面编程(AOP): Spring 支持面向切面编程,同时把应用的业务逻辑与系统的服务分离开来,比如:缓存、日志
  • 集成主流框架:Spring 并没有闭门造车,Spring 集成了已有的技术栈,比如 ORM 框架、Logging 日期框架、J2EE、Quartz 和 JDK Timer ,以及其他视图技术。
  • 模块化:Spring 框架是按照模块的形式来组织的。由包和类的命名,就可以看出其所属的模块,开发者仅仅需要选用他们需要的模块即可。
  • 便捷的测试:要 测试一项用Spring开发的应用程序 十分简单,因为测试相关的环境代码都已经囊括在框架中了。更加简单的是,利用 JavaBean 形式的 POJO 类,可以很方便的利用依赖注入来写入测试数据。
  • Web 框架:Spring 的 Web 框架亦是一个精心设计的 Web MVC 框架,为开发者们在 Web 框架的选择上提供了一个除了主流框架比如 Struts 2、过度设计的、不流行 Web 框架的以外的有力选项。
  • 事务管理:Spring 提供了一个便捷的事务管理接口,适用于小型的本地事物处理(比如在单 DB 的环境下)和复杂的共同事物处理(比如利用 JTA 的复杂 DB 环境)。
  • 异常处理:Spring 提供一个方便的 API ,将特定技术的异常(由JDBC, Hibernate, 或 JDO 抛出)转化为一致的、Unchecked 异常。

1.1.3. 3、Spring框架中设计模式

  • 代理模式 — 在 AOP 中被用的比较多,例如CGLIB,JDK
  • 单例模式 — 在 Spring 配置文件中定义的 Bean 默认为单例模式。
  • 模板方法 — 用来解决代码重复的问题。比如 RestTemplate、JmsTemplate、JdbcTemplate 。
  • 前端控制器设计模式— Spring提供了 DispatcherServlet 来对请求进行分发。
  • 策略模式 — 加载资源文件的方式,使用了不同的方法,比如:ClassPathResourece,FileSystemResource,ServletContextResource,UrlResource但他们都有共同的借口Resource;在Aop的实现中,采用了两种不同的方式,JDK动态代理和CGLIB代理
  • 工厂模式 — BeanFactory 用来创建对象的实例

1.1.4. 4、Spring-IOC 容器

案例:spring-01-beanFactory-applicationContext

【1】IOC容器简介

Spring 框架的核心是 Spring IoC 容器。容器创建 Bean 对象,将它们装配在一起,配置它们并管理它们的完整生命周期,

  • Spring 容器使用反转控制和依赖注入来管理组成应用程序的 Bean 对象。
  • 容器通过读取提供的配置元数据 Bean Definition 来接收对象进行实例化,配置和组装的指令。
  • 该配置元数据 Bean Definition 可以通过 XML,Java 注解或 Java Config 代码提供

image-20210126220326067

Spring 提供了两种IoC 容器,分别是 BeanFactory、ApplicationContext

img

【2】BeanFactory

Spring里面最顶层的接口,提供了最简单的容器的功能,只定义了实例化对象和拿对象的功能

public interface BeanFactory {

//对FactoryBean的转义定义,因为如果使用bean的名字检索FactoryBean得到的对象是工厂生成的对象
    String FACTORY_BEAN_PREFIX = "&";

//根据bean的名字,在IOC容器中得到bean实例,
*Object getBean(String name) throws BeansException;

//根据bean的名字,在IOC容器中得到bean实例,args:显式参数(必须为非单例模式)
Object getBean(String name, Object... args) throws BeansException;

//根据bean的名字获得对象,并转换为Class类型
*<T> T getBean(String name, Class<T> requiredType);

//根据bean的类型获得对象(必须是拥有唯一实现类)
*<T> T getBean(Class<T> requiredType) throws BeansException;

//根据bean的类型获得对象,args:显式参数
<T> T getBean(Class<T> requiredType, Object... args) throws BeansException;

//这里提供对bean的检索,看看是否在IOC容器有这个名字的bean
*boolean containsBean(String name);

//判断这个bean是不是单例 
*boolean isSingleton(String name) throws NoSuchBeanDefinitionException;

//同时判断这个bean是不是多例 
*boolean isPrototype(String name) throws NoSuchBeanDefinitionException;

//这里得到bean实例的Class类型  
*Class<?> getType(String name) throws NoSuchBeanDefinitionException;

//这里得到bean的别名,如果根据别名检索,那么其原名也会被检索出来  
*String[] getAliases(String name);

ApplicationContext

ApplicationContext 接口扩展了 BeanFactory 接口,它在 BeanFactory 基础上提供了一些额外的功能。内置如下功能:

  • MessageSource :管理 message ,实现国际化等功能。
  • ApplicationEventPublisher :事件发布。
  • ResourcePatternResolver :多资源加载。
  • EnvironmentCapable :系统 Environment(profile + Properties)相关。
  • Lifecycle :管理生命周期。
  • Closable :关闭,释放资源
  • InitializingBean:自定义初始化。
  • BeanNameAware:设置 beanName 的 Aware 接口。
BeanFactory ApplicationContext
它使用懒加载【单例】 它使用即时加载【单例】
它使用语法显式提供资源对象 它自己创建和管理资源对象
不支持国际化 支持国际化
不支持基于依赖的注解 支持基于依赖的注解

image-20210317091609303

【3】ApplicationContext

以下是三种较常见的 ApplicationContext 实现方式:

  • ClassPathXmlApplicationContext :从 ClassPath 的 XML 配置文件中读取上下文,并生成上下文定义。应用程序上下文从程序环境变量中取得。示例代码如下:

    ApplicationContext context = new ClassPathXmlApplicationContext(“bean.xml”);
    
  • FileSystemXmlApplicationContext :由文件系统中的XML配置文件读取上下文。示例代码如下:

    ApplicationContext context = new FileSystemXmlApplicationContext(“bean.xml”);
    
  • AnnotationConfigApplicationContext:由 注解的方式读取上下文:

    ApplicationContext applicationContext = new AnnotationConfigApplicationContext(SpringConfig.class)
    

当然,目前我们更多的是使用 Spring Boot 为主,所以使用的是第四种 ApplicationContext 容器,AnnotationConfigServletWebServerApplicationContext 。

1.1.5. 5、Spring-IOC 实现机制

案例:spring-02-custom-ioc

Spring 中的 IoC 的实现原理,就是工厂模式反射机制。代码如下:

package com.itheima.spring.factory;

import java.io.IOException;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Map;
import java.util.Properties;

/**
 * @ClassName BeanFactory.java
 * @Description bean的工厂
 */
public class BeanFactory {

    //【1】事先创建集合容器 map
    private static Map<String,Object> map = new HashMap<>();
    //【2】事先加载properties文件内容 key = values  key:接口的首字母小写 values:实现类的全限定名称
    static {
        Properties properties = new Properties();
        try {
            properties.load(BeanFactory.class.getClassLoader().getResourceAsStream("db.properties"));
            Enumeration<?> enumeration = properties.propertyNames();
            //【3】反射机制实例化bean对象,并且放入集合容器 AccountServiceImpl AccountDaoImpl
            while (enumeration.hasMoreElements()) {
                //接口的首字母消息
                String key = (String) enumeration.nextElement();
                //类的全限定名称
                String value = (String) properties.get(key);
                //3.1、实例化bean【被代理对象】
                Object beanObject = Class.forName(value).newInstance();
                //3.2、为被代理对象生成代理对象,然后把代理对象放入map容器中
                //4、放入容器
                map.put(key,beanObject);
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    //【4】公共的访问方法 getBean(string name)
    public static Object getBean(String className){
        return map.get(className);
    }
}

1.1.6. 6、Spring-IOC 配置方式

单纯从 Spring Framework 提供的方式,一共有三种

案例:spring-03-01-jdbctemplate-xml

  • XML 配置文件

    Bean 所需的依赖项和服务在 XML 格式的配置文件中指定。这些配置文件通常包含许多 bean 定义和特定于应用程序的配置选项。它们通常以 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">

    <!--1、创建数据源(API)-->
    <bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
        <!---驱动、链接、账号、密码-->
        <property name="driverClassName" value="com.mysql.jdbc.Driver"/>
        <property name="url" value="jdbc:mysql://127.0.0.1:3306/springplay"/>
        <property name="username" value="root"/>
        <property name="password" value="root"/>
    </bean>

    <!--2、为jdbctemplate注入数据源(API)-->
    <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
        <property name="dataSource" ref="dataSource"/>
    </bean>

    <!--3、为dao层注入jdbctemplate-->
    <bean id="accountDao" class="com.itheima.spring.dao.impl.AccountDaoImpl">
        <property name="jdbcTemplate" ref="jdbcTemplate"/>
    </bean>

    <!--4、为service层注入dao-->
    <bean id="accountService" class="com.itheima.spring.service.impl.AccountServiceImpl">
        <property name="accountDao" ref="accountDao"/>
    </bean>

    <!--5、为controller层注入service-->
    <bean id="clientController" class="com.itheima.spring.controller.ClientController">
        <property name="accountService" ref="accountService"/>
    </bean>

</beans>

案例:spring-03-02-jdbctemplate-annotation-xml

  • 注解配置

您可以通过在相关的类,方法或字段声明上使用注解,将 Bean 配置为组件类本身,而不是使用 XML 来描述 Bean 装配。默认情况下,Spring 容器中未打开注解装配。因此,您需要在使用它之前在 Spring 配置文件中启用它。例如:

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

    <!--读取外部配置文件-->
    <context:property-placeholder location="classpath:db.properties"/>

    <!--约定大于配置,扫描生效-->
    <context:component-scan base-package="com.itheima.spring"/>

    <!--1、创建数据源(API)
        - c3p0数据源:com.mchange.v2.c3p0.ComboPooledDataSource
        - dbcp数据源:org.apache.commons.dbcp.BasicDataSource
        - druid数据源:com.alibaba.druid.pool.DruidDataSource
    -->
    <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
        <!---驱动、链接、账号、密码-->
        <property name="driverClassName" value="${dataSource.driverClassName}"/>
        <property name="url" value="${dataSource.url}"/>
        <property name="username" value="${dataSource.username}"/>
        <property name="password" value="${dataSource.password}"/>
    </bean>

    <!--2、为jdbctemplate注入数据源(API)-->
    <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
        <property name="dataSource" ref="dataSource"/>
    </bean>

</beans>

案例:spring-03-03-jdbctemplate-annotation

  • Java Config 配置

Spring 的 Java 配置是通过使用 @Bean 和 @Configuration 来实现

@Bean 注解扮演与 <bean /> 元素相同的角色。

@Configuration 类允许通过简单地调用同一个类中的其他 @Bean 方法来定义 Bean 间依赖关系

package com.itheima.spring.config;

import com.alibaba.druid.pool.DruidDataSource;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.PropertySource;
import org.springframework.context.annotation.Scope;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Component;

import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
import javax.sql.DataSource;

/**
 * @ClassName DbConfig.java
 * @Description 数据源的配置
 */
//声明此类为配置类
@Configuration
//读取外部配置文件
@PropertySource("classpath:db.properties")
public class DbConfig {

    @Value("${dataSource.driverClassName}")
    private String driverClassName;

    @Value("${dataSource.url}")
    private String url;

    @Value("${dataSource.username}")
    private String username;

    @Value("${dataSource.password}")
    private String password;

    /***
     * @description 声明三方JAR提供的类交予spring管理
     * 细节:
     *      beand id:
     *          如果不指定,则使用当前方法的方法名最为bean的Id
     * @return: com.alibaba.druid.pool.DruidDataSource
     */
    @Bean("dataSource")
    public DruidDataSource dataSource(){
        DruidDataSource dataSource = new DruidDataSource();
        dataSource.setDriverClassName(driverClassName);
        dataSource.setUrl(url);
        dataSource.setUsername(username);
        dataSource.setPassword(password);
        return dataSource;
    }

    @Bean
    public JdbcTemplate jdbcTemplate(@Qualifier("dataSource") DataSource dataSource){
        return new JdbcTemplate(dataSource);
    }


}

1.1.7. 7、Spring Bean 的创建过程

在spring的初始化过程中,其核心方法是refresh方法,

public void refresh() throws BeansException, IllegalStateException {
        synchronized(this.startupShutdownMonitor) {
            /**
             * 刷新上下文环境
             * 初始化上下文环境,对系统的环境变量或者系统属性进行准备和校验
             * 如环境变量中必须设置某个值才能运行,否则不能运行,这个时候可以在这里加这个校验,
             */
            this.prepareRefresh();
            /**
             * 初始化BeanFactory,解析XML,相当于之前的XmlBeanFactory的操作,
             */
            ConfigurableListableBeanFactory beanFactory = this.obtainFreshBeanFactory();
            /**
             * 为上下文准备BeanFactory,即对BeanFactory的各种功能进行填充,如常用的注解@Autowired @Qualifier等
             * 设置SPEL表达式#{key}的解析器
             * 设置资源编辑注册器,如PerpertyEditorSupper的支持
             * 添加ApplicationContextAwareProcessor处理器
             * 在依赖注入忽略实现*Aware的接口,如EnvironmentAware、ApplicationEventPublisherAware等
             * 注册依赖bean的属性中含有ApplicationEventPublisher(beanFactory),则会将beanFactory的实例注入进去
             */
            this.prepareBeanFactory(beanFactory);

            try {
                //BeanFactory准备工作完成后进行的后置处理工作
                this.postProcessBeanFactory(beanFactory);
                //BeanFactoryPostProcessor的执行方法
                this.invokeBeanFactoryPostProcessors(beanFactory);
                //注册BeanPostProcessor(Bean的后置处理器),在创建bean的前后等执行
                this.registerBeanPostProcessors(beanFactory);
                //初始化MessageSource组件(做国际化功能;消息绑定,消息解析)
                this.initMessageSource();
                //初始化事件派发器
                this.initApplicationEventMulticaster();
                //子类重写这个方法,在容器刷新的时候可以自定义逻辑;如创建Tomcat,Jetty等WEB服务器
                this.onRefresh();
                //注册应用的监听器,ApplicationListener接口的监听器bean注册到ApplicationEventMulticaster中的
                this.registerListeners();
                //初始化所有剩下的非懒加载的单例bean
                this.finishBeanFactoryInitialization(beanFactory);
                //完成context的刷新。
                this.finishRefresh();
            } catch (BeansException var9) {
                if (this.logger.isWarnEnabled()) {
                    ....
                }

                this.destroyBeans();
                this.cancelRefresh(var9);
                throw var9;
            } finally {
                this.resetCommonCaches();
            }

        }
    }

1.1.8. 8、什么叫延迟加载

默认情况下,容器启动之后会将所有作用域为单例的 Bean 都创建好,但是有的业务场景我们并不需要它提前都创建好。此时,我们可以在xml 中设置 lzay-init = "true",或者@Lazy。

  • 这样,当容器启动之后,作用域为单例的 Bean ,就不在创建。
  • 而是在获得该 Bean 时,才真正在创建加载。

1.1.9. 9、循环依赖问题

A-->B-->C-->A

循环依赖问题在Spring中主要有三种情况:

  • (1)通过构造方法进行依赖注入时产生的循环依赖问题。==【处理不了】==
  • (2)通过setter方法进行依赖注入且是在多例(原型)模式下产生的循环依赖问题。==【处理不了】==
  • (3)通过setter方法进行依赖注入且是在单例模式下产生的循环依赖问题。==【可以处理】==

在Spring中,只有第(3)种方式的循环依赖问题被解决了,其他两种方式在遇到循环依赖问题时都会产生异常。其实也很好解释:

  • 第(1)种构造方法注入的情况下,在new对象的时候就会堵塞住了,其实也就是”先有鸡还是先有蛋“的历史难题。
  • 第(2)种setter方法(多例)的情况下,每一次getBean()时,都会产生一个新的Bean,如此反复下去就会有无穷无尽的Bean产生了,最终就会导致内存溢出【OOM】问题的出现。

Spring解决的单例模式下的setter方法依赖注入引起的循环依赖问题,主要是通过两个缓存来解决的,请看下图:

image-20210129212916426

源码 级别 描述
singletonObjects 一级缓存 用于存放完全初始化好的 bean【属性可以不赋值】,从该缓存中取出的 bean 可以直接使用
earlySingletonObjects 二级缓存 存放原始的 bean 对象(尚未填充属性),用于解决循环依赖
singletonFactories 三级缓存 存放 bean 工厂对象,用于解决循环依赖

1.1.10. 10、bean作用范围

案例:spring-04-scope

  • Singleton - 每个 Spring IoC 容器仅有一个单 Bean 实例。默认
  • Prototype - 每次请求都会产生一个新的实例。
  • Request - 每一次 HTTP 请求都会产生一个新的 Bean 实例,并且该 Bean 仅在当前 HTTP 请求内有效。
  • Session - 每一个的 Session 都会产生一个新的 Bean 实例,同时该 Bean 仅在当前 HTTP Session 内有效。
  • Application - 每一个 Web Application 都会产生一个新的 Bean ,同时该 Bean 仅在当前 Web Application 内有效。

1、单例和多里创建方式、内存地址 【singleton单例】:所有请求只创建一个对象,内存地址相同 【prototype多例】:每次请求都创建新的对象,内存地址不同 2、为什么使用单例? 节省内存、CPU的开销,加快对象访问速度 3、为什么使用多例? 如果你给controller中定义很多的属性,那么单例肯定会出现竞争访问,不要在controller层中定义成员变量(dao、service注入的bean可以) 当web层的对象是有状态的时候 使用多例,防止并发情况下的互相干扰 4、单例、多例的场景 单例:spring中的Dao,Service,controller都是单例的 多例:struts2的Action是多实例

1.1.11. 11、bean实例化方式

【1】缺省构造函数方式

<!--空的构造方法实例化-->
<bean id="account" class="com.heima.spring.pojo.Account"></bean>

【2】静态工厂方法方式

<!--静态工厂实例化-->
    <bean id="accountStatic" class="com.heima.spring.factory.StaticFactory" factory-method="createAccount"></bean>

【3】实例工厂方法方式

<!--实例化工厂实例化-->
<bean id="instanceFactory" class="com.heima.spring.factory.InstanceFactory"></bean>
<bean id="accountInstance" factory-bean="instanceFactory" factory-method="createAccount"></bean>

【缺省构造函数方式】 说明: 在默认情况下会根据默认缺省构造函数来创建类对象。如果bean中没有默认无参构造函数,将会创建失败。 场景: 当各个bean的业务逻辑相互比较独立时,或者与外界关联较少时可以使用

【静态工厂方法方式】 说明: 使用工厂中的静态方法创建对象,并装配到 spring的IOC 容器中。 id 属性:指定 bean 的 id,用于从容器中获取
class 属性:指定静态工厂的全限定类名
factory-method 属性:指定生产对象的静态方法 场景: 统一管理各个bean的创建 各个bean在创建之前需要相同的初始化处理,则可用静态factory方法进行统一的处理

【实例工厂方法方式】 说明 使用工厂中的实例方法创建对象,并装配到容器中。 1、先把实例工厂做为一个bean装配到 spring容器中。
2、然后再引用工厂bean 来调用里面的非静态方法来获取bean并装配到spring的IOC容器中。
factory-bean 属性:用于指定实例工厂 bean 的 id。
factory-method 属性:用于指定实例工厂中创建对象的方法 场景: 1.实例factory方法也作为业务bean控制,可以用于集成其他框架的bean创建管理方法, 2.能够使bean和factory的角色互换

1.1.12. 12、基于注解的容器配置

Spring 的 Java 配置是通过使用 @Bean@Configuration 来实现。

  • @Bean 注解,扮演与 元素相同的角色。

  • @Configuration 注解的类,允许通过简单地调用同一个类中的其他 @Bean 方法来定义 Bean 间依赖关系。

    /**
     * @ClassName SpringConfig.java
     * @Description spring的配置类
     */
    //声明此类为一个配置类
    @Configuration
    //约定大于配置,扫描生效
    @ComponentScan(basePackages = "com.itheima.spring")
    public class SpringConfig {
    }
    

invokeBeanFactoryPostProcessors

invokeBeanFactoryPostProcessors方法总结来说:

  • BeanFactoryPostProcessor:用来修改Spring容器中已经存在的bean的定义,使用ConfigurableListableBeanFactory对bean进行处理
  • BeanDefinitionRegistryPostProcessor:继承BeanFactoryPostProcessor,作用跟BeanFactoryPostProcessor一样,只不过是使用BeanDefinitionRegistry对bean进行处理

其中ConfigurationClassPostProcessor这个BeanDefinitionRegistryPostProcessor优先级最高,它会对项目中的@Configuration注解修饰的类(@Component、@ComponentScan、@Import、@ImportResource修饰的类也会被处理)进行解析,解析完成之后把这些bean注册到BeanFactory中。需要注意的是这个时候注册进来的bean还没有实例化。

1.1.13. 13、常用bean声明的注解

  • @Component :它将 Java 类标记为 Bean 。它是任何 Spring 管理组件的通用构造型。
  • @Controller :它将一个类标记为 Spring Web MVC 控制器
  • @Service :此注解是组件注解的特化。它不会对 @Component`注解提供任何其他行为。您可以在服务层类中使用 @Service 而不是 @Component ,因为它以更好的方式指定了意图。
  • @Repository:这个注解是具有类似用途和功能的 @Component 注解的特化。它为 DAO 提供了额外的好处。它将 DAO 导入 IoC 容器,并使未经检查的异常有资格转换为 Spring DataAccessException 。
  • @bean: 定义再方法上,把返回对象交予IOC容器2

1.1.14. 14、DI依赖注入

依赖注入:Dependency Injection(简称DI注入)。它是spring框架核心 ioc容器,bean属性值赋值的具体方案,方式有2种:

  • set方法注入

  • 构造方法注入

【1】@Autowired 注解有什么用

@Autowired 注解,可以更准确地控制应该在何处以及如何进行自动装配。

  • 此注解用于在 setter 方法,构造函数,具有任意名称或多个参数的属性或方法上自动装配 Bean。
  • 默认情况下,它是类型驱动的注入。

示例代码如下:

@Service("accountService")
public class AccountServiceImpl implements AccountService {

    @Autowired
    private EmployeeDao employeeDao;

}

【2】@Qualifier 注解有什么用

与@Autowired配合使用

当你创建多个相同类型的 Bean ,并希望仅使用属性装配其中一个 Bean 时,您可以使用 @Qualifier注解和 @Autowired 通过指定 ID 应该装配哪个确切的 Bean 来消除歧义。

例如,应用中有两个类型为 Employee 的 Bean ID 为 "employeeDao""employeeDaoB" ,此处,我们希望 AccountServiceImpl Bean 注入 "employeeDaoA" 对应的 Bean 对象。代码如下:

@Service("accountService")
public class AccountServiceImpl implements AccountService {

    @Autowired
    @Qualifier(“employeeDaoA”)
    private EmployeeDao employeeDao;

}

@Qualifier单独使用:

/***
 * @description 声明三方JAR提供的类交予spring管理
 * 细节:
 *      beand id:
 *          如果不指定,则使用当前方法的方法名最为bean的Id
 * @return: com.alibaba.druid.pool.DruidDataSource
 */
@Bean("dataSource")
public DruidDataSource dataSource(){
    DruidDataSource dataSource = new DruidDataSource();
    dataSource.setDriverClassName(driverClassName);
    dataSource.setUrl(url);
    dataSource.setUsername(username);
    dataSource.setPassword(password);
    return dataSource;
}

@Bean
public JdbcTemplate jdbcTemplate(@Qualifier("dataSource") DataSource dataSource){
    return new JdbcTemplate(dataSource);
}

【3】@Resource注解有什么用

@Resource不是spring提供的注解,而是javax.annotation.Resource;提供的

默认按照bean的名称注入数据,如果同一个接口有多个实现,可以通过指定属性进行注入 属性: name:指定bean的名称注入数据 type:指定bean的类型注入数据

例如:

@Service("accountService")
public class AccountServiceImpl implements AccountService {


    /***
     * @Resource
     * 作用:
     *     默认按照bean的名称注入数据,如果同一个接口有多个实现,可以通过指定属性进行注入
     * 属性:
     *     name:指定bean的名称注入数据
     *     type:指定bean的类型注入数据
     */
    @Resource
    private AccountDao accountDaoB;
}

1.1.15. 15、什么是AOP

AOP(Aspect-Oriented Programming),即面向切面编程, 它与 OOP( Object-Oriented Programming, 面向对象编程) 相辅相成, 提供了与 OOP 不同的抽象软件结构的视角。

  • 在 OOP 中,以类( Class )作为基本单元
  • 在 AOP 中,以切面( Aspect )作为基本单元。

AOP是【面向切面编程】,使用【动态代理】技术,实现在【不修改java源代码】的情况下,运行时实现方法功能的【增强】,

例如:

image-20191119152120195

大多情况下,都是适用于非功能性需求中:权限控制,日志收集,缓存设置,分布式追踪,异常处理,事务控制

1.1.16. 16、动态代理的方式

【1】基于接口JDK代理

java.lang.reflect.Proxy: Java动态代理机制的主类,提供了一组静态方法来为一组接口动态地生成代理类及其实例。

//方法1: 该方法用于获取指定动态代理对象所关联的调用处理器
static InvocationHandler getInvocationHandler(Object proxy)

//方法2:该方法用于获取关联于指定类装载器和一组接口的动态代理对象
static Class getProxyClass(ClassLoader loader, Class[] interfaces)

//方法3:该方法用于判断指定类对象是否是一个动态代理类
static boolean isProxyClass(Class cl)

//方法4:该方法用于为指定类装载器、一组接口及调用处理器生成动态代理对象:1、类加载器 2、接口数组、调用处理器(增强部分的业务代码)
static Object newProxyInstance(ClassLoader loader, Class[] interfaces, InvocationHandler h)

java.lang.reflect.InvocationHandler: 调用处理器接口,它自定义了一个invoke方法,用于集中处理在动态代理对象上的方法调用,通常在该方法中实现对委托类的代理访问。每次生成动态代理对象时都需要指定一个实现了该接口的调用处理器对象。

InvocationHandler的核心方法:

//该方法负责集中处理动态代理类上的所有方法调用。
//第一个参数是代理对象,第二个参数是被调用的方法对象,第三个方法是调用参数。
//调用处理器根据这三个参数进行预处理或分派到委托类实例上反射执行。
Object invoke(Object proxy, Method method, Object[] args)
/**
 * @Description:租客
 */
public class Customer {

    HouseOwner houseOwner = new HouseOwner();

    @Test
    public void needHouse(){

        HouseAgencyCompany houseProxy = (HouseAgencyCompany) Proxy.newProxyInstance(
                houseOwner.getClass().getClassLoader(),
                houseOwner.getClass().getInterfaces(),
                new InvocationHandler() {
                    @Override
                    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                        System.out.println("中介公司让中介带客户看房");
                        Object object = method.invoke(houseOwner, args);
                        System.out.println("中介公司让中介完成租房业务");
                        return object;
                    }
                });
        houseProxy.rentingHoues();

    }
}

【2】基于子类cglib动态代理

net.sf.cglib.proxy.Enhancer

Enhancer类是CGLib中的一个==字节码增强器==,作用用于生成代理对象,跟上一章所学的Proxy类相似,常用方式为:

 //方法1:该方法用于为指定目标类、回调对象 1、被代理类的类型,2、调用处理器
 public static Object create(Class type, Callback callback)

net.sf.cglib.proxy.MethodInterceptor

//方法1:
Object intercept(Object var1, Method var2, Object[] var3, MethodProxy var4) throws Throwable;

public class Customer {

    @Test
    public void needHouse(){
        System.out.println("=====================");
        //代理模式下的租房
        HouseOwner houseOwnerProxy = (HouseOwner) Enhancer.create(
            HouseOwner.class,new MethodInterceptor() {
            @Override
            public Object intercept(Object o, Method method,
                                    Object[] objects, MethodProxy methodProxy) throws Throwable {
                //增强
                System.out.println("房东儿子:带你看房子");
                //返回结果
                return  methodProxy.invokeSuper(o, objects);
            }
        });

        houseOwnerProxy.renttingHouse();
    }
}

1.1.17. 17、AOP中的专业术语

Joinpoint(连接点): 在spring中,连接点指的都是方法【saveAccount】(指的是那些要被增强功能的候选方法)

Pointcut(切入点): 切入点是指我们要对哪些Joinpoint进行拦截的定义。

Advice(通知): 通知指的是拦截到Joinpoint之后要做的事情。即增强的功能 通知类型:前置通知、后置通知、异常通知、最终通知、环绕通知

Target(目标对象): 被代理的对象。

Proxy(代理对象): 一个类被AOP织入增强后,即产生一个结果代理类。

Weaving(织入): 织入指的是把增强用于目标对象。创建代理对象的过程

Aspect(切面): 切面指的是切入点和通知的结合

1.1.18. 18、Spring-AOP 配置方式

案例:spring-06-01-spring-aop-xml

XML配置:

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

">

    <!--读取外部配置文件-->
    <context:property-placeholder location="classpath:db.properties"/>

    <!--约定大于配置,扫描生效-->
    <context:component-scan base-package="com.itheima.spring"/>

    <!--1、创建数据源(API)
        - c3p0数据源:com.mchange.v2.c3p0.ComboPooledDataSource
        - dbcp数据源:org.apache.commons.dbcp.BasicDataSource
        - druid数据源:com.alibaba.druid.pool.DruidDataSource
    -->
    <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
        <!---驱动、链接、账号、密码-->
        <property name="driverClassName" value="${dataSource.driverClassName}"/>
        <property name="url" value="${dataSource.url}"/>
        <property name="username" value="${dataSource.username}"/>
        <property name="password" value="${dataSource.password}"/>
    </bean>

    <!--2、为jdbctemplate注入数据源(API)-->
    <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
        <property name="dataSource" ref="dataSource"/>
    </bean>

    <!--日志处理类-->
    <bean id="logInfoService" class="com.itheima.spring.service.impl.LogInfoServiceImpl"/>

    <!--AOP日志切面-->
    <!--1、声明AOP的配置-->
    <aop:config>
        <!--1、声明AOP的切面配置-->
        <aop:aspect id="loginfoAspect" ref="logInfoService">
            <!--2、声明AOP的切入点配置-->
            <aop:pointcut
                    id="loginfoPointcut"
                    expression="bean(accountService)"/>
            <!--3、配置通知-->
            <aop:before method="beforeLogInfo" pointcut-ref="loginfoPointcut"/>
            <aop:after-returning method="afterReturningLogInfo" pointcut-ref="loginfoPointcut"/>
            <aop:after-throwing method="afterThrowingLogInfo" pointcut-ref="loginfoPointcut"/>
            <aop:after method="afterLogInfo" pointcut-ref="loginfoPointcut"/>
            <aop:around method="aroundLogInfo" pointcut-ref="loginfoPointcut"/>
        </aop:aspect>
    </aop:config>

</beans>

案例:spring-06-02-spring-aop-annotation-xml

注解配置:

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

">

    <!--读取外部配置文件-->
    <context:property-placeholder location="classpath:db.properties"/>

    <!--约定大于配置,扫描生效-->
    <context:component-scan base-package="com.itheima.spring"/>

    <!--1、创建数据源(API)
        - c3p0数据源:com.mchange.v2.c3p0.ComboPooledDataSource
        - dbcp数据源:org.apache.commons.dbcp.BasicDataSource
        - druid数据源:com.alibaba.druid.pool.DruidDataSource
    -->
    <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
        <!---驱动、链接、账号、密码-->
        <property name="driverClassName" value="${dataSource.driverClassName}"/>
        <property name="url" value="${dataSource.url}"/>
        <property name="username" value="${dataSource.username}"/>
        <property name="password" value="${dataSource.password}"/>
    </bean>

    <!--2、为jdbctemplate注入数据源(API)-->
    <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
        <property name="dataSource" ref="dataSource"/>
    </bean>

    <!--开启切面注解配置-->
    <aop:aspectj-autoproxy />

</beans>

案例:spring-06-03-spring-aop-annotation

JavaConfig配置

package com.itheima.spring.config;

import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.EnableAspectJAutoProxy;

/**
 * @ClassName SpringConfig.java
 * @Description spring的配置文件
 */
//声明此类为配置类
@Configuration
//指定扫描路径
@ComponentScan(basePackages = "com.itheima.spring")
//开启AOP切面的注解配置
@EnableAspectJAutoProxy
public class SpringConfig {
}

1.1.19. 19、Spring-AOP 通知方式

  • 前置通知:在目标方法执行前执行
  • 后置正常通知:在目标方法正常返回后执行。它和异常通知只能执行一个
  • 异常通知:在目标方法发生异常后执行。它和后置通知只能执行一个
  • 最终通知:无论目标方法正常返回,还是发生异常都会执行
  • 环绕通知:在目标方法执行前后执行该增强方法
package com.itheima.spring.service.impl;

import com.itheima.spring.service.LogInfoService;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import java.lang.reflect.Method;

/**
 * @ClassName LogInfoServiceImpl.java
 * @Description 日志处理类接口实现
 */
@Component
//声明切面
@Aspect
public class LogInfoServiceImpl implements LogInfoService {


    @Override
    @Before("bean(accountService)")
    public void beforeLogInfo(JoinPoint joinPoint) {
        MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
        Method method = methodSignature.getMethod();
        String methodName = method.getName();
        System.out.println("前置通知:"+methodName);
    }

    @Override
    @AfterReturning("bean(accountService)")
    public void afterReturningLogInfo(JoinPoint joinPoint) {
        MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
        Method method = methodSignature.getMethod();
        String methodName = method.getName();
        System.out.println("后置正常通知:"+methodName);
    }

    @Override
    @AfterThrowing("bean(accountService)")
    public void afterThrowingLogInfo(JoinPoint joinPoint) {
        MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
        Method method = methodSignature.getMethod();
        String methodName = method.getName();
        System.out.println("后置异常通知:"+methodName);
    }

    @Override
    @After("bean(accountService)")
    public void afterLogInfo(JoinPoint joinPoint) {
        MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
        Method method = methodSignature.getMethod();
        String methodName = method.getName();
        System.out.println("最终通知:"+methodName);
    }

    @Override
    @Around("bean(accountService)")
    public Object aroundLogInfo(ProceedingJoinPoint joinPoint) {
        MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
        Method method = methodSignature.getMethod();
        String methodName = method.getName();
        Object object = null;
        try {
            System.out.println("环绕通知-前:"+methodName);
            //执行的目标方法【saveAccount】
            object = joinPoint.proceed();
            System.out.println("环绕通知-后置正常:"+methodName);
        } catch (Throwable throwable) {
            System.out.println("环绕通知-异常正常:"+methodName);
            //【细节】必须要抛出异常,防止事务失效
            throw new RuntimeException(throwable);
        }finally {
            System.out.println("环绕通知-最终正常:"+methodName);
        }
        return object;
    }
}

1.1.20. 20、事务的ACID原则

image-20210130212419619

事务具有4个基本特性:原子性、一致性、隔离性、持久性。也就是我们常说的ACID原则

  • 原子性(Atomicity):

    一个事务已经是一个不可再分割的工作单位。事务中的全部操作要么都做;要么都不做
    
    例如:A和B两个人一共1000元,A给B转账100元,A付款100元,B收款100元,
         A的付款行为和B的收款行为要么都成功,要么都失败
    
  • 一致性(Consistency):

    事务的执行使数据从一个状态转换为另一个状态,但是对于整个数据的完整性保持稳定。 
    
    例如:A和B两个人一共1000元,无论A,B两人互相转账多少次,A和B两个人总额都应该是1000元
    
  • 隔离性(Isolation):

    事务允许多个用户对同一个数据进行并发访问,而不破坏数据的正确性 和完整性。同时,并行事务的修改必须与其他并行事务的修改相互独立。 
    
    例如:万达影院有《叶问4》电影票100张,允许所有人同时去淘票票上购票,当第100张电影票被A,B,C3人同时购买,如果A拿到第100张电影票,但是还在犹豫要不要付钱,则B,C必须等待A的决定,如果A决定付钱,B.C就无法抢到票,如果A超时不付钱,则这第100张电影票回归票池,从新分配。
    
  • 持久性(Durability): 一个事务一旦提交,它对数据库中数据的改变会永久存储起来。其他操作不会对它产生影响

    例如:万达影院有《叶问4》电影票100张,100张电影票销售完毕,对于每个购买者来说,他的购买记录已经产生,即使退票,他之前的购买记录也不会消失。
    

1.1.21. 21、Spring 事务的常用API

PlatformTransactionManager:

即事务平台管理器,是一个接口,定义了获取事务、提交事务、回滚事务的接口

public interface PlatformTransactionManager {

    //根据事务定义TransactionDefinition,获取事务
    TransactionStatus getTransaction(TransactionDefinition definition);
    //提交事务
    void commit(TransactionStatus status);
    //回滚事务
    void rollback(TransactionStatus status);

}

事务平台管理器PlatformTransactionManager定义了标准,他有如下经常使用的实现

PlatformTransactionManager的实现类 说明
org.springframework.jdbc.datasource.DataSourceTransactionManager DataSource 数据源的事务
org.springframework.orm.hibernateX.HibernateTransactionManager Hibernate 事务管理器。
org.springframework.orm.jpa.JpaTransactionManager JPA 事务管理器
org.springframework.transaction.jta.JtaTransactionManager 多个数据源的全局事务管理器
org.springframework.orm.jdo.JdoTransactionManager JDO 事务管理器

TransactionDefinition:

即事务定义信息:是一个接口,定义了事务隔离级别、事务传播行为、事务超时时间、事务是否只读

public interface TransactionDefinition {
    /**********************事务传播行为类型常量***********************************/
   //事务传播行为类型:如果当前没有事务,就新建一个事务,如果已经存在一个事务中,加入到这个事务中。
    int PROPAGATION_REQUIRED = 0;
    //事务传播行为类型:支持当前事务,如果当前没有事务,就以非事务方式执行。 
    int PROPAGATION_SUPPORTS = 1;
    //事务传播行为类型:当前如果有事务,Spring就会使用该事务;否则会抛出异常
    int PROPAGATION_MANDATORY = 2;
    //事务传播行为类型:新建事务,如果当前存在事务,把当前事务挂起。 
    int PROPAGATION_REQUIRES_NEW = 3;
    //事务传播行为类型:以非事务方式执行操作,如果当前存在事务,就把当前事务挂起。 
    int PROPAGATION_NOT_SUPPORTED = 4;
    //事务传播行为类型:即使当前有事务,Spring也会在非事务环境下执行。如果当前有事务,则抛出异常
    int PROPAGATION_NEVER = 5;
    //事务传播行为类型:如果当前存在事务,则在嵌套事务内执行。
    int PROPAGATION_NESTED = 6;

     /**********************事务隔离级别常量***********************************/
    //MySQL默认采用ISOLATION_REPEATABLE_READ,Oracle采用READ__COMMITTED级别。)
    //隔离级别:默认的隔离级别()
    int ISOLATION_DEFAULT = -1;
    //隔离级别:读未提交(最低)
    int ISOLATION_READ_UNCOMMITTED = Connection.TRANSACTION_READ_UNCOMMITTED;
    //隔离级别:读提交
    int ISOLATION_READ_COMMITTED = Connection.TRANSACTION_READ_COMMITTED;
    //隔离级别:可重复度
    int ISOLATION_REPEATABLE_READ = Connection.TRANSACTION_REPEATABLE_READ;
    //隔离级别:序列化操作(最高)
    int ISOLATION_SERIALIZABLE = Connection.TRANSACTION_SERIALIZABLE;

    //默认事务的超时时间
    int TIMEOUT_DEFAULT = -1;

    //获取事务的传播行为
    int getPropagationBehavior();

    //获取事务的隔离级别
    int getIsolationLevel();

    //获取超时时间
    int getTimeout();

    //是否只读
    boolean isReadOnly();

    //事务名称
    String getName();
}

TransactionStatus

用于保存当前事物状态 ,定义了一个简单的控制事务执行和查询事务状态的方法

public interface TransactionStatus extends SavepointManager, Flushable {
    //是否一个新的事务
    boolean isNewTransaction();
    //是否有存储点(存储过程中用到)
    boolean hasSavepoint();//
    //将事务设置为只能回滚,不允许提交 
    void setRollbackOnly();
    //查询事务是否已有回滚标志
    boolean isRollbackOnly();
    //刷新事务
    void flush();
    //查询事务是否结束
    boolean isCompleted();
 }

1.1.22. 22、为什么要事务隔离?

如果没有定义事务隔离级别:

  • 脏读

    ​ 在一个事务中读取到了另外一个事务修改的【未提交的数据】,而导致多次读取同一个数据返回的结果不一致 (必须要解决的)

例如:
    1.事务1,小明的原工资为1000, 财务人员将小明的工资改为了8000【但未提交事务】
    2.事务2,小明读取自己的工资 ,发现自己的工资变为了8000,欢天喜地!
    3.事务1,财务发现操作有误,回滚了操作,小明的工资又变为了1000
      像这样,小明读取的工资数8000是一个脏数据
  • 不可重复读

    ​ 在一个事务中读取到了另外一个事务修改的【已提交的数据】,而导致多次读取同一个数据返回的结果不一致

例如:
    1.事务1,小明读取了自己的工资为1000,操作还没有完成 
    2.事务2,这时财务人员修改了小明的工资为2000,并提交了事务.
    3.事务1,小明再次读取自己的工资时,工资变为了2000
  • 幻读(虚读):

    ​ 一个事务读取了几行记录后,另一个事务插入一些记录,幻读就发生了。再后来的查询中,第一个事务就会发现有些原来没有的记录

例如:   
   1.事务1,财务统计所有工资为5000的员工有10人。
   2.事务2,人事向user表插入了一条员工记录,工资也为5000
   3.事务1,财务再次读取所有工资为5000的员工 共读取到了11条记录,明明刚刚是10人啊?产生幻觉了?

事务隔离就是帮助我们解决:脏读、不可重复读、幻读(虚读)

1.1.23. 23、Spring中的事务隔离级别

隔离级别由低到高【读未提交】=>【读已提交】=>【可重复读】=>【序列化操作】

隔离级别 说明 脏读 不可重复读 幻读
ISOLATION_DEFAULT spring默认数据库的隔离级别 -- -- --
ISOLATION_READ_UNCOMMITTED 读未提交
ISOLATION_READ_COMMITTED 读已提交 ×
ISOLATION_REPEATABLE_READ 可重复读 × ×
ISOLATION_SERIALIZABLE 序列化操作 × × ×

对大多数数据库来说就是:READ_COMMITTED(读已提交)

MySQL默认采用:REPEATABLE_READ(可重复读),

Oracle采用:READ_COMMITTED()

Spring的隔离级别默认数据库的隔离级别:ISOLATION_DEFAULT

1.1.24. 24、事务传播行为

事务传播行为:指的就是当一个事务方法被另一个事务方法调用时,这个事务方法应该如何进行。

image-20191121143347248

传播行为 说明
REQUIRED 当前如果有事务,Spring就会使用该事务;否则会开始一个新事务(增、删、改)
SUPPORTS 当前如果有事务,Spring就会使用该事务;否则不会开始一个新事务(查询)
MANDATORY 当前如果有事务,Spring就会使用该事务;否则会抛出异常
REQUIRES_NEW 当前如果有事务,把当前事务挂起,新建事务
NOT_SUPPORTED 当前有事务,Spring也会在非事务环境下执行。如果当前有事务,则该事务挂起
NEVER 当前有事务,Spring也会在非事务环境下执行。如果当前有事务,则抛出异常
NESTED 当前有事务,则在嵌套事务中执行。如果没有,那么执行情况与REQUIRED一样

【默认】REQUIRED:当前如果有事务,Spring就会使用该事务;否则会开始一个新事务

image-20191121113130561

image-20191121113554703

SUPPORTS:当前如果有事务,Spring就会使用该事务;否则不会开始一个新事务

image-20191121113456109

image-20191121103809808

MANDATORY:当前如果有事务,Spring就会使用该事务;否则会抛出异常

image-20191121113929494

image-20191121114207857

REQUIRES_NEW:新建事务,如果当前存在事务,把当前事务挂起。

image-20191121110744120

NOT_SUPPORTED:Spring不会执行事务中的代码。代码总是在非事务环境下执行,如果当前有事务,则该事务挂起

image-20191121111614152

NEVER:即使当前有事务,Spring也会在非事务环境下执行。如果当前有事务,则抛出异常

image-20191121115013901

image-20191121115153396

NESTED: 如果当前存在事务,则在嵌套事务内执行,如果当前不存在事务,则创建一个新的事务,同REQUIRED

嵌套事务:

​ 使用数据库中的保存点来实现,即嵌套事务回滚不影响外部事务,但外部事务回滚将导致嵌套事务回滚

image-20191121125109391

1.1.25. 25、@Transactional 注解有哪些属性?如何使用?

@Transactional是spring提供的事务注解,常用属性:

属性 类型 描述
value String 可选的限定描述符,指定使用的事务管理器
propagation enum: Propagation 可选的事务传播行为设置
isolation enum: Isolation 可选的事务隔离级别设置
readOnly boolean 读写或只读事务,默认读写
timeout int (in seconds granularity) 事务超时时间设置
rollbackFor Class对象数组,必须继承自Throwable 导致事务回滚的异常类数组
rollbackForClassName 类名数组,必须继承自Throwable 导致事务回滚的异常类名字数组
noRollbackFor Class对象数组,必须继承自Throwable 不会导致事务回滚的异常类数组
noRollbackForClassName 类名数组,必须继承自Throwable 不会导致事务回滚的异常类名字数组

【使用范围】:

​ 可以作用于接口、接口方法、类以及类方法上(修饰符:公共方法)。

​ 具体用法如下:

  • @Transactional 可以作用于接口、接口方法、类以及类方法上。当作用于类上时,该类的所有==public==方法将都具有该类型的事务属性,同时,我们也可以在方法级别使用该标注来覆盖类级别的定义。
  • 虽然 @Transactional注解可以作用于接口、接口方法、类以及类方法上,但是 Spring 建议不要在接口或者接口方法上使用该注解,因为这只有在使用基于接口的代理时它才会生效。另外, @Transactional 注解应该只被应用到 public 方法上,这是由 Spring AOP 的本质决定的。如果你在 protectedprivate 或者默认可见性的方法上使用 @Transactional 注解,这将被忽略,也不会抛出任何异常。这一点,非常需要注意

1.1.26. 26 、Spring-TX配置方式

案例:spring-07-01-spring-tx-xml

XML配置方式:

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

">

    <!--读取外部配置文件-->
    <context:property-placeholder location="classpath:db.properties"/>

    <!--约定大于配置,扫描生效-->
    <context:component-scan base-package="com.itheima.spring"/>

    <!--1、创建数据源(API)
        - c3p0数据源:com.mchange.v2.c3p0.ComboPooledDataSource
        - dbcp数据源:org.apache.commons.dbcp.BasicDataSource
        - druid数据源:com.alibaba.druid.pool.DruidDataSource
    -->
    <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
        <!---驱动、链接、账号、密码-->
        <property name="driverClassName" value="${dataSource.driverClassName}"/>
        <property name="url" value="${dataSource.url}"/>
        <property name="username" value="${dataSource.username}"/>
        <property name="password" value="${dataSource.password}"/>
    </bean>

    <!--2、为jdbctemplate注入数据源(API)-->
    <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
        <property name="dataSource" ref="dataSource"/>
    </bean>

    <!--定义事务的处理类:细节:transactionManager名称不可以更换-->
    <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <property name="dataSource" ref="dataSource"/>
    </bean>

    <!--事务增强的定义-->
    <tx:advice id="txAdvice" transaction-manager="transactionManager">
        <tx:attributes>
            <!--tx:method标签:配置业务方法的名称规则,说明:
                name:方法名称规则,可以使用通配符*
                isolation:事务隔离级别,使用默认值即可
                propagation:事务传播行为,增/删/改方法使用REQUIRED。查询方法使用SUPPORTS。
                read-only:是否只读事务。增/删/改方法使用false。查询方法使用true。
                timeout:配置事务超时。使用默认值-1即可。永不超时。
                rollback-for:发生某种异常时,回滚;发生其它异常不回滚。没有默认值,任何异常都回滚
                no-rollback-for:发生某种异常时不回滚;发生其它异常时回滚。没有默认值,任何异常都回滚。
            -->
            <tx:method name="find*" isolation="DEFAULT" propagation="SUPPORTS"/>
            <tx:method name="save*" isolation="DEFAULT" propagation="REQUIRED"/>
            <tx:method name="delete*" isolation="DEFAULT" propagation="REQUIRED"/>
            <tx:method name="update*" isolation="DEFAULT" propagation="REQUIRED"/>
            <tx:method name="do*" isolation="DEFAULT" propagation="REQUIRED"/>
        </tx:attributes>
    </tx:advice>

    <!--日志处理类-->
    <bean id="logInfoService" class="com.itheima.spring.service.impl.LogInfoServiceImpl"/>

    <!--AOP日志切面-->
    <!--1、声明AOP的配置-->
    <aop:config>
        <!--事务的切点-->
        <aop:pointcut id="txPointcut" expression="bean(*Service)"/>
        <!--管理事务增强定义与切点的关系-->
        <aop:advisor advice-ref="txAdvice" pointcut-ref="txPointcut"/>
        <!--1、声明AOP的切面配置-->
        <aop:aspect id="loginfoAspect" ref="logInfoService">
            <!--2、声明AOP的切入点配置-->
            <aop:pointcut
                    id="loginfoPointcut"
                    expression="bean(accountService)"/>
            <!--3、配置通知-->
            <aop:before method="beforeLogInfo" pointcut-ref="loginfoPointcut"/>
            <aop:after-returning method="afterReturningLogInfo" pointcut-ref="loginfoPointcut"/>
            <aop:after-throwing method="afterThrowingLogInfo" pointcut-ref="loginfoPointcut"/>
            <aop:after method="afterLogInfo" pointcut-ref="loginfoPointcut"/>
            <aop:around method="aroundLogInfo" pointcut-ref="loginfoPointcut"/>
        </aop:aspect>
    </aop:config>

</beans>

案例:spring-07-02-spring-tx-annotation-xml

注解方式:

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

">

    <!--读取外部配置文件-->
    <context:property-placeholder location="classpath:db.properties"/>

    <!--约定大于配置,扫描生效-->
    <context:component-scan base-package="com.itheima.spring"/>

    <!--1、创建数据源(API)
        - c3p0数据源:com.mchange.v2.c3p0.ComboPooledDataSource
        - dbcp数据源:org.apache.commons.dbcp.BasicDataSource
        - druid数据源:com.alibaba.druid.pool.DruidDataSource
    -->
    <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
        <!---驱动、链接、账号、密码-->
        <property name="driverClassName" value="${dataSource.driverClassName}"/>
        <property name="url" value="${dataSource.url}"/>
        <property name="username" value="${dataSource.username}"/>
        <property name="password" value="${dataSource.password}"/>
    </bean>

    <!--2、为jdbctemplate注入数据源(API)-->
    <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
        <property name="dataSource" ref="dataSource"/>
    </bean>

    <!--定义事务的处理类:细节:transactionManager名称不可以更换-->
    <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <property name="dataSource" ref="dataSource"/>
    </bean>

    <!--开启AOP自定注解配置-->
    <aop:aspectj-autoproxy/>

    <!--开启事务自定注解配置-->
    <tx:annotation-driven/>

</beans>
@Override
@Transactional
public Integer doDeal(Float money, Integer payId, Integer rePayId) {
    //扣款
    int flag= 0;
    flag= accountDao.dealMoney(payId, -money);
    if (flag==0){
        throw  new RuntimeException("扣款失败");
    }
    int i = 1/0;
    //收款
    flag=flag+accountDao.dealMoney(rePayId,money);
    if (flag==1){
        throw  new RuntimeException("收款失败");
    }
    return flag;
}

案例:spring-07-03-spring-tx-annotation

JavaConfig配置:

package com.itheima.spring.config;

import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.EnableAspectJAutoProxy;
import org.springframework.transaction.annotation.EnableTransactionManagement;

/**
 * @ClassName SpringConfig.java
 * @Description springd 配置
 */
//声明此类为配置类
@Configuration
//开启扫描机制
@ComponentScan(basePackages = "com.itheima.spring")
//开启AOP
@EnableAspectJAutoProxy
//开启事务管理器
@EnableTransactionManagement
public class SpringConfig {
}
package com.itheima.spring.config;

import com.alibaba.druid.pool.DruidDataSource;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.PropertySource;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;

import javax.sql.DataSource;

/**
 * @ClassName DbConfig.java
 * @Description 数据源配置
 */
@Configuration
//读取外部配置文件
@PropertySource("classpath:db.properties")
public class DbConfig {


    @Value("${dataSource.driverClassName}")
    private String driverClassName;

    @Value("${dataSource.url}")
    private String url;

    @Value("${dataSource.username}")
    private String username;

    @Value("${dataSource.password}")
    private String password;

    //数据源配置
    @Bean("dataSource")
    public DruidDataSource druidDataSource(){
        DruidDataSource dataSource = new DruidDataSource();
        dataSource.setDriverClassName(driverClassName);
        dataSource.setUrl(url);
        dataSource.setUsername(username);
        dataSource.setPassword(password);
        return dataSource;
    }

    //配置查询模板
    @Bean
    public JdbcTemplate jdbcTemplate(@Qualifier("dataSource") DataSource dataSource){
        return new JdbcTemplate(dataSource);
    }

    //创建事务管理器【名称必须transactionManager】
    @Bean("transactionManager")
    public DataSourceTransactionManager transactionManager(@Qualifier("dataSource") DataSource dataSource){
        return new DataSourceTransactionManager(dataSource);
    }
}

1.1.27. 27、Spring常用注解表

【Spring-IOC相关】

注解 xml 说明
@Component父注解
@Controller:用于响应层的注解
@Service:用于业务层的注解
@Repository:一般用于持久层的注解
< bean id="" class=""> 声明bean交于springIOC管理
@Scope scope=“singleton/prototype” 生命周期
@PostConstruct init-method 初始化方法
@PreDestroy destroy-method 销毁方法

【Spring-DI相关】

注解 xml 说明
@Autowired、@Qualifier ref="类型" 默认按照bean的类型注入数据
@Resource ref="类型" 默认按照bean的名称注入数据
@Value ref="基础数据类型" 给基本数据类型赋值

【Spring-AOP相关】

注解 xml 说明
@Aspect 声明切面
@Before 前置通知
@AfterReturning 后置正常通知
@AfterThrowing 后置异常通知
@After 最终通知
@Around 环绕通知

【Spring-TX相关】

@Transactional

@Transactional 注解可以作用于接口、接口方法、类以及类方法上,但是 Spring 建议不要在接口或者接口方法上使用该注解,因为这只有在使用基于接口的代理时它才会生效。另外, @Transactional 注解应该只被应用到 public 方法上,这是由 Spring AOP 的本质决定的。如果你在 protected、private 或者默认可见性的方法上使用 @Transactional 注解,这将被忽略,也不会抛出任何异常。

【属性】

属性 类型 说明
value String 可选的限定描述符,指定使用的事务管理器
propagation enum: Propagation 可选的事务传播行为设置
isolation enum: Isolation 可选的事务隔离级别设置
readOnly boolean 读写或只读事务,默认读写
timeout int (in seconds granularity) 事务超时时间设置
rollbackFor Class对象数组,必须继承自Throwable 导致事务回滚的异常类数组
rollbackForClassName 类名数组,必须继承自Throwable 导致事务回滚的异常类名字数组
noRollbackFor Class对象数组,必须继承自Throwable 不会导致事务回滚的异常类数组
noRollbackForClassName 类名数组,必须继承自Throwable 不会导致事务回滚的异常类名字数组

【Spring-JUNIT相关】

注解 说明
@RunWith 指定使用SpringJUnit4ClassRunner
@ContextConfiguration 指定加载配置文件

【配置类相关】

注解 XML 说明
@Configuration / 声明此类为配置类
@EnableAspectJAutoProxy 开启AOP
@EnableTransactionManagement 开启事务管理器
@EnableWebMvc 开启springMVC
@PropertySource 导入外部配置
@MapperScan / mybatis的扫描配置
@Bean < bean > 声明bean
@ComponentScan 扫描配置

1.2. 第二章 Spring-MVC框架

1.2.1. 1、介绍下 Spring MVC 的核心组件?

public class DispatcherServlet extends FrameworkServlet {
    ......
    //初始化各个组件
    protected void initStrategies(ApplicationContext context) {
        // 初始化MultipartResolver 用来处理上传文件 默认是没有处理的,需要在上下文添加MultipartResolver处理器
        initMultipartResolver(context);
        // 初始化LocaleResolver 配置国际化的【中-英】
        initLocaleResolver(context);
        // 初始化ThemeResolver 通过主题控制页面风格
        initThemeResolver(context);
        // 初始化HandlerMappings 由定义请求和处理程序对象之间映射的对象实现
        initHandlerMappings(context);POST+/user  GET+/user
        // 初始化HandlerAdapters
        initHandlerAdapters(context);
        // 初始化HandlerExceptionResolvers 异常处理
        initHandlerExceptionResolvers(context);
        // 初始化RequestToViewNameTranslator
        initRequestToViewNameTranslator(context);
        // 初始化ViewResolvers
        initViewResolvers(context);
        // 初始化FlashMapManager
        initFlashMapManager(context);
    }

    //加载默认策略
    protected <T> List<T> getDefaultStrategies(ApplicationContext context, Class<T> strategyInterface) {
        ......
    }
}
  • MultipartResolver :文件上传解析器
  • LocaleResolver:其主要作用在于根据不同的用户区域展示不同的视图,可以实现一种国际化的目的
  • ThemeResolver:就是整个系统的样式和风格。Spring MVC提供的主题(Theme)可以设置应用的整体样式风格
  • ==HandlerMapping:==处理器映射器
  • ==HandlerAdapter:==处理器适配器
  • HandlerExceptionResolver: 异常解析器
  • RequestToViewNameTranslator:将request请求转换为视图名称,比如转换为 index.jsp
  • ==ViewResolver:== 视图解析器
  • FlashMapManager:Spring3.1之后引入了一个叫做Flash Attribute的功能,主要就是为了解决表单重复提交数据的问题,应用POST/Redirect/GET(PRG)模式来防止重复提交数据

1.2.2. 2、DispatcherServlet 的工作流程

image-20201117153332546

发送请求

用户向服务器发送 HTTP 请求,请求被 Spring MVC 的调度控制器 DispatcherServlet 捕获

映射处理器

DispatcherServlet 根据请求 URL ,调用 HandlerMapping 获得该 Handler 配置的所有相关的对象(包括 Handler 对象以及 Handler 对象对应的拦截器),最后以 HandlerExecutionChain 对象的形式返回

处理器适配

DispatcherServlet 根据获得的 Handler,选择一个合适的HandlerAdapter

提取请求 Request 中的模型数据,填充 Handler 入参,开始执行Handler(Controller)。 在填充Handler的入参过程中,根据你的配置,Spring 将帮你做一些额外的工作:

  • HttpMessageConverter :会将请求消息(如 JSON、XML 等数据)转换成一个对象。
  • 数据转换:对请求消息进行数据转换。如 String 转换成 Integer、Double 等。
  • 数据格式化:对请求消息进行数据格式化。如将字符串转换成格式化数字或格式化日期等。
  • 数据验证: 验证数据的有效性(长度、格式等),验证结果存储到 BindingResult 或 Error 中。

Handler(Controller) 执行完成后,向 DispatcherServlet 返回一个 ModelAndView 对象。

解析视图

根据返回的 ModelAndView ,选择一个适合的 ViewResolver(必须是已经注册到 Spring 容器中的 ViewResolver),解析出 View 对象,然后返回给 DispatcherServlet

渲染视图 + 响应请求

ViewResolver 结合 Model 和 View,来渲染视图,并写回给用户( 浏览器 )。

1.2.3. 3、Spring-MVC常用注解

注解 说明
@RequestMapping 配置映射地址
@GetMapping 配置映射地址GET:得到资源
@PutMapping 配置映射地址PUT:修改整体内容
@PostMapping 配置映射地址POST:新增内容
@DeleteMapping 配置映射地址DELETE:删除内容
@PatchMapping 配置映射地址PATCH:修改部分内容
@PathVariable 绑定URL中的参数值 、/user/{userId}
@RequestParam 绑定单个请求数据,既可以是URL中的参数,也可以是表单提交的参数 /user>?userId=1
@RequestBody 请求参数格式为json
@RestController 注释在类上,生命一个bean,且表示此类中返回类型都是json(@Controller+@ResponseBody)
@ResponseBody 注解在方法上,表示此方法返回类型为json
@ExceptionHandler 异常处理
@ControllerAdvice 对controller层进行增强

1.2.4. 4、Spring-MVC异常处理

案例:springmvc-08-exception

Spring MVC 提供了异常解析器 HandlerExceptionResolver 接口,将处理器( handler )执行时发生的异常,解析( 转换 )成对应的 ModelAndView 结果。代码如下:

// HandlerExceptionResolver.java

public interface HandlerExceptionResolver {

    /**
     * 解析异常,转换成对应的 ModelAndView 结果
     */
    @Nullable
    ModelAndView resolveException(
            HttpServletRequest request, HttpServletResponse response, @Nullable Object handler, Exception ex);

}

1.2.5. 5、HttpMessageConverter

HttpMessageConverter 是一种策略接口,它指定了一个转换器,它可以转换 HTTP 请求和响应。Spring REST 用这个接口转换 HTTP 响应到多种格式,例如:JSON 或 XML 。

每个 HttpMessageConverter 实现都有一种或几种相关联的MIME协议。Spring 使用 "Accept" 的标头来确定客户端所期待的内容类型。

然后,它将尝试找到一个注册的 HTTPMessageConverter ,它能够处理特定的内容类型,并使用它将响应转换成这种格式,然后再将其发送给客户端。

1.2.6. 6、什么是拦截器

在请求controller层前后,及视图渲染之后所执行的方法,他是切面思想的一个实现,用于对请求信息进行拦截处理的机制

​ 1、controller方法之前

​ 2、controller方法之后

​ 3、渲染之后

【作用】

​ controller请求前后,处理需要拦截的信息,比如日志信息,权限信息等

【1】过滤器和拦截器

在JavaWEB阶段我们学习了过滤器filter,他也可以在请求servlet前后进行数据的处理,拦截器intercept是不是和他很类似?他们有什么异同呢?

比较项 filter HandlerIntercept
容器 servlet容器 spring容器
配置位置 web.xml spring-mvc.xml
思想 切面思想 切面思想
作用范围 所有请求起作用 controller执行方法前后起作用
spring上下文内容 不可以访问 可以访问
实现方式 函数回调 反射机制

【2】拦截器执行流程

【3】拦截器接口

在springMVC中我们要定义一个拦截器需要实现HandlerInterceptor接口,接口下有三个方法:

public interface HandlerInterceptor {

    /**
     * 预处理回调方法,实现处理器的预处理(如检查登陆)
     * 返回值:true表示继续流程;false表示流程中断,不会继续调用其他的拦截器或处理器,此时我们需要通过         
     * response来产生响应;
   */
    default boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
            throws Exception {

        return true;
    }

    /**
     * 后处理回调方法,实现处理器的后处理(在渲染视图之前),此时我们可以
     * 通过modelAndView(模型和视图对象)对模型数据进行处理或对视图进行处理,modelAndView也可能为         
     * null。
   */
    default void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler,@Nullable ModelAndView modelAndView) throws Exception {
    }

   /**
    * 整个请求处理完毕回调方法,即在视图渲染完毕时回调,如性能监控中我们可以在此记录结束时间并输出消耗时间,    
    * 还可以进行一些资源清理,类似于try-catch-finally中的finally
   */
    default void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler,
            @Nullable Exception ex) throws Exception {
    }

}
#preHandle
调用时间:Controller方法处理之前
备注:若返回false,则中断执行,则不会进入postHandle和afterCompletion

#postHandle
调用前提:preHandle返回true
调用时间:Controller方法处理完之后,DispatcherServlet进行视图的渲染之前,也就是说在这个方法中你可以对ModelAndView进行操作
备注:postHandle虽然post打头,但post、get方法都能处理

#afterCompletion
调用前提:preHandle返回true
调用时间:DispatcherServlet进行视图的渲染之后
备注:多用于清理资源

1.2.7. 7、拦截器链执行顺序

案例:springmvc-09-intercept

xml方式定义:

<mvc:interceptors>
    <mvc:interceptor>
        <mvc:mapping path="/up-load/to-add"/>
        <bean class="com.itheima.project.intercept.BasicIntercept"/>
    </mvc:interceptor>
    <mvc:interceptor>
        <mvc:mapping path="/up-load/to-add"/>
        <bean class="com.itheima.project.intercept.BasicInterceptA"/>
    </mvc:interceptor>
    <mvc:interceptor>
        <mvc:mapping path="/up-load/to-add"/>
        <bean class="com.itheima.project.intercept.BasicInterceptB"/>
    </mvc:interceptor>
</mvc:interceptors>
#preHandle
调用时间:Controller方法处理之前
调用顺序:按配置定义顺序:preHandle==>preHandle--A==>preHandle--B
备注:在拦截器链中,优先执行所有的preHandle()方法

#postHandle
调用前提:preHandle返回true
调用顺序:倒序postHandle--B==>postHandle--A==>postHandle
调用时间:Controller方法处理完之后,DispatcherServlet进行视图的渲染之前,也就是说在这个方法中你可以对ModelAndView进行操作
备注:postHandle虽然post打头,但post、get方法都能处理

#afterCompletion
调用前提:preHandle返回true
调用顺序:倒序afterCompletion--B==>afterCompletion--A==>afterCompletion
调用时间:DispatcherServlet进行视图的渲染之后
备注:多用于清理资源

1.3. 第三章 Mybatis框架

1.3.1. 1、MyBatis 编程开发

image-20210201150933192

package com.itheima.project.test;

import com.itheima.ssm.dao.UserDao;
import com.itheima.ssm.po.User;
import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;

import java.io.InputStream;
import java.util.List;

/**
 * 测试mybatis框架在web项目中独立运行
 */
public class MybatisTest {

    public static void main(String[] args) throws Exception {
        // 1.加载核心配置文件
        InputStream inputStream = Resources.getResourceAsStream("mybatis/sqlMapConfig.xml");

        // 2.读取配置文件内容,获取SqlSessionFactory
        SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();
        SqlSessionFactory sqlSessionFactory = builder.build(inputStream);

        // 3.打开SqlSession
        SqlSession sqlSession = sqlSessionFactory.openSession();

        // 4.获取接口代理对象
        UserDao mapper = sqlSession.getMapper(UserDao.class);

        // 5.查询全部账户列表
        List<User> list = mapper.findAllUsers();
        for(User u:list){
            System.out.println(u);
        }

        // 6.释放资源
        sqlSession.close();
    }
}
  • 读取配置,构建SqlSessionFactoryBuilder
  • 使用SqlSessionFactoryBuilder创建 SqlSessionFactory 对象。
  • 通过 SqlSessionFactory 获取 SqlSession 对象。
  • 通过 SqlSession 获得 Mapper 代理对象。
  • 通过 Mapper 代理对象,执行数据库操作。
  • 执行成功,则使用 SqlSession 提交事务。
  • 执行失败,则使用 SqlSession 回滚事务。
  • 最终,关闭会话。

1.3.2. 2、Mybatis #{}和${}的区别是什么

#{}是预编译处理,${}是字符串替换。 (1)mybatis在处理#{}时,会将sql中的#{}替换为?号,调用PreparedStatement的set方法来赋值。 (2)mybatis在处理${}时,就是把${}替换成变量的值。 (3)使用#{}可以有效的防止SQL注入,提高系统安全性。 (4)一般能用#的就别用$

我们经常使用的是#{},一般解说是因为这种方式可以防止SQL注入,简单的说#{}这种方式SQL语句是经过预编译的,它是把#{}中间的参数转义成字符串,举个例子:

select * from student where student_name = #{name}

预编译后,会动态解析成一个参数标记符?:

select * from student where student_name = ?

而使用${}在动态解析时候,会传入参数字符串

select * from student where student_name = 'lyrics'

1.3.3. 3、结果字段映射处理

  • 通过在查询的 SQL 语句中定义字段名的别名,让字段名的别名和实体类的属性名一致。代码如下
<select id="selectOrder" parameterType="Integer" resultType="Order"> 
    SELECT order_id AS id, orderno AS order_no, order_price AS price 
    FROM orders 
    WHERE order_id = #{id}
</select>
  • 大多数场景下,数据库字段名和实体类中的属性名差别,主要是前者为下划线风格,后者为驼峰风格。在这种情况下,可以直接配置如下,实现自动的下划线转驼峰的功能
<setting name="logImpl" value="LOG4J"/>
    <setting name="mapUnderscoreToCamelCase" value="true" />
</settings>
  • 通过 <resultMap> 来映射字段名和实体类属性名的一一对应的关系。代码如下:
<resultMap type="me.gacl.domain.Order" id=”OrderResultMap”> 
    <!–-id 属性来映射主键字段 -–> 
    <id property="id" column="order_id"> 
    <!–-result 属性来映射非主键字段,property 为实体类属性名,column 为数据表中的属性 -–> 
    <result property="orderNo" column ="order_no" /> 
    <result property="price" column="order_price" /> 
</resultMap>

<select id="getOrder" parameterType="Integer" resultMap="OrderResultMap">
    SELECT * 
    FROM orders 
    WHERE order_id = #{id}
</select>

1.3.4. 4、Mapper 接口绑定有几种实现方式

  • 通过 XML Mapper 里面写 SQL 来绑定。在这种情况下,要指定 XML 映射文件里面的 "namespace"必须为接口的全路径名。

  • 通过注解绑定,就是在接口的方法上面加上 @Select、@Update、@Insert、@Delete注解,里面包含 SQL 语句来绑定。

  • 也是通过注解绑定,在接口的方法上面加上 @SelectProvider、@UpdateProvider、@InsertProvider@DeleteProvider 注解,通过 Java 代码,生成对应的动态 SQL 。

实际场景下,最最最推荐的是第一种方式。因为,SQL 通过注解写在 Java 代码中,会非常杂乱。而写在 XML 中,更加有整体性,并且可以更加方便的使用 OGNL 表达式

1.3.5. 5、Mapper 中如何传递多个参数

第一种,使用 Map 集合,装载多个参数进行传递。代码如下

// 调用方法
Map<String, Object> map = new HashMap();
map.put("start", start);
map.put("end", end);
return studentMapper.selectStudents(map);

// Mapper 接口
List<Student> selectStudents(Map<String, Object> map);

// Mapper XML 代码
<select id="selectStudents" parameterType="Map" resultType="Student">
    SELECT * 
    FROM students 
    LIMIT #{start}, #{end}
</select>

第二种,保持传递多个参数,使用 @Param 注解。代码如下:

// 调用方法
return studentMapper.selectStudents(0, 10);

// Mapper 接口
List<Student> selectStudents(@Param("start") Integer start, @Param("end") Integer end);

// Mapper XML 代码
<select id="selectStudents" resultType="Student">
    SELECT * 
    FROM students 
    LIMIT #{start}, #{end}
</select>

第三种,保持传递多个参数,不使用 @Param 注解。代码如下:

// 调用方法
return studentMapper.selectStudents(0, 10);

// Mapper 接口
List<Student> selectStudents(Integer start, Integer end);

// Mapper XML 代码
<select id="selectStudents" resultType="Student">
    SELECT * 
    FROM students 
    LIMIT #{param1}, #{param2}
</select>

其中,按照参数在方法方法中的位置,从 1 开始,逐个为 #{param1}、#{param2}、#{param3}` 不断向下。

1.3.6. 6、Mapper 接口的工作原理是什么

Mapper 接口,对应的关系如下:

  • 接口的全限名,就是映射文件中的 "namespace" 的值。
  • 接口的方法名,就是映射文件中 MappedStatement 的 "id" 值。
  • 接口方法内的参数,就是传递给 SQL 的参数。

Mapper 接口是没有实现类的,当调用接口方法时,接口全限名 + 方法名拼接字符串作为 key 值,可唯一定位一个对应的 MappedStatement 。举例:com.mybatis3.mappers.StudentDao.findStudentById,可以唯一找到 "namespace" 为 com.mybatis3.mappers.StudentDao下面"id"为 findStudentById` 的 MappedStatement 。

总结来说,在 Mybatis 中,每一个

另外,Mapper 接口的实现类,通过 MyBatis 使用 JDK Proxy 自动生成其代理对象 Proxy ,而代理对象 Proxy 会拦截接口方法,从而“调用”对应的 MappedStatement 方法,最终执行 SQL ,返回执行结果。整体流程如下图:流程

1.3.7. 7、Mybatis的一对一、一对多、多对多

一对一

<!--1对1,创建User的映射,无论属性名和列名是否相同,都需要写-->
<resultMap id="userAndUserInfo" type="com.itheima.springboot.pojo.User">
    <!--主键映射-->
    <id property="id" column="id"/>
    <result property="loginName" column="login_name"  />
    <result property="passWord" column="pass_word" />
    <result property="salt" column="salt"/>
    <result property="enableFlag" column="enable_flag"/>
    <result property="createdTime" column="created_time"/>
    <result property="updatedTime" column="updated_time"/>
    <result property="shardingId" column="sharding_id"/>
    <!--一对一关联-->
    <association property="userInfo"
                 javaType="com.itheima.springboot.pojo.UserInfo" >
        <!--主键映射-->
        <id property="id" column="i_id" />
        <result property="userId" column="i_user_id"/>
        <result property="realName" column="i_real_name"/>
        <result property="nickName" column="i_nick_name"/>
        <result property="sortNo" column="i_sort_no"/>
        <result property="enableFlag" column="i_enable_flag"/>
        <result property="createdTime" column="i_created_time" />
        <result property="updatedTime" column="i_updated_time"/>
        <result property="shardingId" column="i_sharding_id"/>
    </association>
</resultMap>

一对多

<!--1对多,创建User的映射,无论属性名和列名是否相同,都需要写-->
<resultMap id="userAndUserrRole" type="com.itheima.springboot.pojo.User">
    <!--主键映射-->
    <id property="id" column="id"/>
    <result property="loginName" column="login_name"  />
    <result property="passWord" column="pass_word" />
    <result property="salt" column="salt"/>
    <result property="enableFlag" column="enable_flag"/>
    <result property="createdTime" column="created_time"/>
    <result property="updatedTime" column="updated_time"/>
    <result property="shardingId" column="sharding_id"/>
    <!--一对多关联-->

    <collection property="userRoles"
                javaType="java.util.ArrayList"
                ofType="com.itheima.springboot.pojo.UserRole">
        <!--主键映射-->
        <id property="id" column="ur_id" />
        <result property="userId" column="ur_user_id"/>
        <result property="roleId" column="ur_role_id"/>
        <result property="enableFlag" column="ur_enable_flag"/>
        <result property="createdTime" column="ur_created_time" />
        <result property="updatedTime" column="ur_updated_time"/>
        <result property="shardingId" column="ur_sharding_id"/>
    </collection>
</resultMap>

多对多

<!--多对多,创建User的映射,无论属性名和列名是否相同,都需要写-->
<resultMap id="userAndRole" type="com.itheima.springboot.pojo.User">
    <!--主键映射-->
    <id property="id" column="id"/>
    <result property="loginName" column="login_name"  />
    <result property="passWord" column="pass_word" />
    <result property="salt" column="salt"/>
    <result property="enableFlag" column="enable_flag"/>
    <result property="createdTime" column="created_time"/>
    <result property="updatedTime" column="updated_time"/>
    <result property="shardingId" column="sharding_id"/>
    <!--多对多关联-->
    <collection property="roles"
                javaType="java.util.ArrayList"
                ofType="com.itheima.springboot.pojo.Role">
        <!--主键映射-->
        <id property="id" column="r_id" />
        <result property="roleName" column="r_role_name"/>
        <result property="label" column="r_label"/>
        <result property="description" column="r_description"/>
        <result property="sortNo" column="r_sort_no"/>
        <result property="enableFlag" column="r_enable_flag"/>
        <result property="createdTime" column="r_created_time" />
        <result property="updatedTime" column="r_updated_time"/>
        <result property="shardingId" column="r_sharding_id"/>
    </collection>
</resultMap>

1.3.8. 8、mybatis缓存

【1】一级缓存

  Mybatis对缓存提供支持,但是在没有配置的默认情况下,它只开启一级缓存,一级缓存只是相对于同一个SqlSession而言。所以在参数和SQL完全一样的情况下,我们使用同一个SqlSession对象调用一个Mapper方法,往往只执行一次SQL,因为使用SelSession第一次查询后,MyBatis会将其放在缓存中,以后再查询的时候,如果没有声明需要刷新,并且缓存没有超时的情况下,SqlSession都会取出当前缓存的数据,而不会再次发送SQL到数据库。

image-20210203173142066

为什么要使用一级缓存,不用多说也知道个大概。但是还有几个问题我们要注意一下。

一级缓存的生命周期有多长?

  • MyBatis在开启一个数据库会话时,会 创建一个新的SqlSession对象,SqlSession对象中会有一个新的Executor对象。 Executor对象中持有一个新的PerpetualCache对象;当会话结束时,SqlSession对象及其内部的Executor对象还有PerpetualCache对象也一并释放掉。
  • 如果SqlSession调用了close()方法,会释放掉一级缓存PerpetualCache对象,一级缓存将不可用。
  • 如果SqlSession调用了clearCache(),会清空PerpetualCache对象中的数据,但是该对象仍可使用。
  • SqlSession中执行了任何一个update操作(update()、delete()、insert()) ,都会清空PerpetualCache对象的数据,但是该对象可以继续使用

【2】二级缓存

MyBatis的二级缓存是Application级别的缓存,它可以提高对数据库查询的效率,以提高应用的性能

image-20210203173451127

 SqlSessionFactory层面上的二级缓存默认是不开启的,二级缓存的开席需要进行配置,实现二级缓存的时候,MyBatis要求返回的POJO必须是可序列化的。 也就是要求实现Serializable接口,如果我们配置了二级缓存就意味着:

  • 映射语句文件中的所有select语句将会被缓存。
  • 映射语句文件中的所欲insert、update和delete语句会刷新缓存。
  • 缓存会使用默认的Least Recently Used(LRU,最近最少使用的)算法来收回。
  • 根据时间表,比如No Flush Interval,(CNFI没有刷新间隔),缓存不会以任何时间顺序来刷新。
  • 缓存会存储列表集合或对象(无论查询方法返回什么)的1024个引用
  • 缓存会被视为是read/write(可读/可写)的缓存,意味着对象检索不是共享的,而且可以安全的被调用者修改,不干扰其他调用者或线程所做的潜在修改。

results matching ""

    No results matching ""