• 第02篇_Spring基础应用

    第01章_SpringIOC

    第一节 SpringIOC简介

    1. 什么是SpringIOC?

    IOC(Inversion of Control,控制反转)是一种编程范式,指将程序的某些流程(如对象创建、方法调用等)交由外部容器来控制。

    SpringIOC是 IOC 的一种实现,通过容器来管理和控制应用程序中对象的生命周期和依赖关系,从而实现对象之间的解耦和灵活的配置。

    image-20250619204144457

    注意:

    1. Guice 是由 Google 开发的一个轻量级依赖注入框架,通过注解和简单的配置来实现依赖注入,也是 IOC 的一种实现。

     

     

    2. SpringIOC入门案例

    1) 导入IOC依赖

    注意:

    1. 如果使用 Spring 5 版本,需要保证JDK 1.8+Tomcat 8.5+;如果使用 Spring 6 版本,则需要 JDK17+。

     

    2) 配置IOC容器

    需要一个配置文件用来配置 key 和全类名的映射关系,这个文件名一般为application.xml,放在任意类路径下即可。

     

    3) 使用IOC容器

    在业务代码中通过容器来获取所需的依赖,而不是直接创建依赖对象。

     

     

    第二节 容器详解

    1. 什么是容器?

    容器用于管理控制应用程序中对象的生命周期和依赖关系,顶层接口为ApplicationContext,常见实现类如下:

    注意:

    1. ApplicationContext 继承 BeanFactory 接口,扩展了AOP、Web、国际化、事件传递等功能,并在启动时初始化所有单例Bean

     

     

    2. 容器的创建和关闭

    1) 通过XML配置创建

    加载XML配置文件,创建一个 Spring 的 IOC 容器:

    XML配置文件示例如下:

     

    2) 通过注解配置创建

    加载配置类上的注解创建容器:

    注意:

    1. 配置类必须带有 @Configuration@Component 等注解。

     

    3) 通过Java代码创建

     

    4) 关闭容器

    一般只会在程序退出时关闭容器,可以向 JVM 注册一个关闭钩子,让 Spring 在程序退出时调用 Bean 的生命周期相关方法

    注意:

    1. 在 Web 环境中,基于 Web 的容器实现默认注册了关闭钩子,无需重复注册。

    2. 关闭钩子生效场景有:正常退出、System.exit()、Ctrl+C中断、OutofMemory宕机、Kill pid杀死进程(kill -9除外)、系统关闭等。

     

     

    3. 容器的基本使用

    1) 获取对象

    可以通过T getBean(String name, Class<T> requiredType)等方法获取 Bean 的实例

     

    2) 获取容器信息

     

     

    4. 容器的环境配置

    1) 环境与策略

    Environment接口用来表示整个应用运行时的环境,是当前Bean集合相关属性在容器中的一个抽象。

    Profile用于控制哪些 Bean 被注册,哪些 Bean 不被注册,只有处于活动状态的 Bean 才会被注册。

     

    2) 配置环境(@Profile)

    @Profile 注解可以作用于配置方法之上,表示只有对应的环境被激活时,被注解的Bean才会被注册到Spring容器。

    提示

    1. 可以使用一些基本运算符来配置Bean的环境。如 production & (us-east | eu-central)

    2. 可以同时配置多个环境。如@Profile({"p1", "!p2"})表示在p1活动状态或p2未活动状态进行注册。

    3. 如果 @Profile 注解作用于同名的@Bean方法(方法重载)之上,则它们之间的配置最好相同。

    在XML配置中,可以使用 beans 标签的profile属性来配置Bean的环境,但可能会有一些限制。

     

    3) 激活环境

    我们在启动容器时,可以指定激活的环境,最直接的方法是通过 Environment API 以编程方式进行配置。

    当然,也可以使用声明式的方式,通过系统环境变量、JVM系统属性等方式设置 spring.profiles.active 属性的值。

    在特定环境,如WEB开发中也可以设置 web.xml 中的 servlet 上下文参数,或测试环境中通过 @ActiveProfiles 注解来声明。

     

    4) 默认环境

    spring.profiles.active没有被设置时,那么 Spring 会根据spring.profiles.default属性的值(默认为default)来激活环境,可以使用 setDefaultProfiles() 方法或 spring.profiles.default 属性来修改。

     

     

    5. 容器的事件处理

    1) 事件处理简介

    ApplicationContext 中的事件处理是通过ApplicationEvent类和ApplicationListener接口提供的,属于典型的观察者设计模式

    支持的标准事件如下:

    事件类型描述
    ContextRefreshedEvent在Spring上下文初始化或刷新时发布,例如在应用启动或上下文重新加载时触发
    ContextStartedEvent当Spring上下文启动时发布,表示上下文已准备好并可以开始使用
    ContextStoppedEvent当Spring上下文停止时发布,表示上下文即将关闭
    ContextClosedEvent在Spring上下文关闭时发布,通常在应用关闭时触发
    RequestHandledEvent在Spring MVC框架中,当一个HTTP请求被处理完成后发布

    下面是一个监听示例

    @EventListener注解可以对接收的事件进行更加精细的控制,示例如下:

    注意:

    1. 默认情况下,事件侦听器会同步接收事件,这意味着publishEvent()方法将阻塞,直到所有侦听器都已完成对事件的处理为止。

    2. 这种同步和单线程方法的优点是:当侦听器收到事件时,如果有可用的事务上下文,它将在发布者的事务中进行操作。

     

    2) 自定义事件

    可以实现ApplicationEvent接口自定义事件:

    并通过ApplicationEventPublisher来发布:

     

    3) 监听器配置
    异步监听

    注意:

    1. 如果事件监听器抛出Exception,则它不会传播到调用者。有关更多详细信息,请参见AsyncUncaughtExceptionHandler。

    2. 此类事件侦听器无法发送答复。如果您需要发送另一个事件作为处理结果,请注入ApplicationEventPublisher以手动发送事件。

     

    调用顺序

    如果需要先调用一个侦听器,则可以将@Order注解添加到方法声明中,如以下示例所示:

     

     

    6. 容器的扩展接口

    1) BeanFactoryPostProcessor

    BeanFactoryPostProcessor 可以在容器初始化阶段动态地调整 Bean 的配置信息,而无需修改原始的配置文件或注解。

    注意:

    1. 可以实现Ordered接口或使用@Order 注解来控制多个 BeanFactoryPostProcessor 实例的执行顺序。

    2. AOP 等功能是基于 BeanFactoryPostProcessor 实现的,因此不能对 BeanFactoryPostProcessor 的实现类对象进行织入。

     

    PropertyPlaceholderConfigurer

    PropertyPlaceholderConfigurer用于解析配置文件中的占位符,并进行字符串替换操作。

     

    PropertyOverrideConfigurer

    PropertyOverrideConfigurer用于对配置的属性进行覆盖操作。

    一个 override.properties 配置文件的示例如下:

     

    2) BeanPostProcessor

    BeanPostProcessor 可以在创建 Bean 实例之后对其进行进一步的处理,植入一些自定义逻辑。

     

    3) Lifecycle/SmartLifecycle接口

    在容器启动和关闭时,会检索所有实现了LifecycleSmartLifecycle接口的Bean,对其进行批量处理。

     

    Lifecycle

     

    SmartLifecycle

     

     

    第三节 Bean详解

    1. 什么是Bean?

    Bean 是被 Spring 容器管理的 Java 对象,是实现 Spring IoC 和依赖注入的基础。

     

    2. Bean的定义方式

    1) 基于构造方法的定义

    一般用于可以直接通过构造方法实例化并配置的对象:

     

    2) 基于FactoryBean的定义

    一般用于构建复杂对象,如DataSourceSqlSessionFactory等。首先需要一个FactoryBean,用来描述复杂对象的构建过程:

    然后,基于FactoryBean来定义复杂对象:

    实例化复杂对象时,如果发现是FactoryBean类型,则会调用其getObject()方法创建复杂对象。

    注意:

    1. FactoryBean本质上是一个工厂类,如果需要获取FactoryBean本身,可以通过ctx.getBean("&dataSource")的方式获取。

     

    3) 基于普通工厂类定义

    一般用于第三方模块复杂对象的构建,基于未实现 FacotryBean 接口的普通工厂类:

     

    静态工厂

    静态工厂类如下:

    通过工厂类的静态方法构建对象:

     

    实例工厂

    实例工厂类如下:

    通过工厂类的实例方法构建对象:

     

     

    3. Bean的生命周期

    Bean的生命周期指 Bean 从创建销毁的整个过程,Spring提供了一些机制允许我们对生命周期的管理进行干预。

    img

     

    1) @PostConstruct/@PreDestroy注解

    @PostConstruct@PreDestroy 基于内置的CommonAnnotationBeanPostProcessor实现,在Bean的初始化前后调用相应方法。

    注意:

    1. @PostConstruct@PreDestroy注解的方法不能是抽象方法,必须无参无返回值,建议使用public static修饰。

     

    2) InitializingBean/DisposableBean接口

    如果实现了InitializingBeanDisposableBean接口,则会在初始化销毁时分别调用其相应方法。

     

    3) init-method/destroy-method属性

    使用init-methoddestroy-method属性可以指定无参无返回值方法的名称作为初始化方法和销毁方法。

    使用Java代码配置元数据时,使用@BeaninitMethoddestroyMethod属性来指定初始化和销毁方法:

    注意:

    1. <beans>标签的default-init-method/default-destroy-method属性可以指定所有Bean的默认初始化和销毁方法。

     

    4) 执行顺序总结

    如果一个 Bean 同时配置了多种方式的初始化和销毁方法,初始化和销毁都会按照注解->接口->属性的顺序执行:

    注意:

    1. 同一个初始化或销毁方法即使被多种方式配置,也只会被执行1次。

     

     

    3. Bean的作用范围

    Bean的作用范围决定了 Bean 的作用域以及在获取对象时是否创建新的实例等,通过scope属性进行配置。

     

    1) singleton

    singleton表示单例范围(默认),只会创建一次,一般在容器启动时创建,容器销毁时随之销毁,适合无状态的 Bean。

     

    2) prototype

    prototype表示原型范围(多例),在每次从容器获取对象时,始终会创建新的实例返回,适合有状态的 Bean。

    注意:

    1. 对于原型Bean,Spring不会管理其完整的生命周期,不会调用已配置的生命周期销毁回调

     

    3) web

    在 Web 应用中,提供了一些特殊的作用范围:

     

    4) 自定义作用范围

    可以通过实现 Scope 接口来创建自定义的作用范围:

    注册自定义作用范围:

    使用自定义作用范围:

    注意:

    1. Spring内部提供了一些未启用的作用范围,如SimpleThreadScope,可以手动注册并使用它们。

     

     

    4. Bean的常见属性

    1) 别名(name/alias)

    Bean除了具有唯一性的 id 属性外,还可以定义若干个别名,以兼容不同的业务系统:

     

    2) 依赖项(depends-on)

    depends-on属性可以指定初始化时的依赖项,也可以在Bean是单例的的情况下指定销毁时的依赖项。

     

    3) 懒加载(lazy-init)

    单例Bean默认在容器创建时进行实例化,如果想让Bean在使用时再创建,则可指定lazy-init属性为true。

    如果需要对多个Bean进行配置,也可以选择beans标签的default-lazy-init属性,为其内部的所有Bean设置可覆盖的默认值。

    注意:

    1. @Lazy 注解还可以放置在标有@Autowired的注入点上,在这种情况下,它会注入一个惰性解析代理。

     

    4) 继承(abstract/parent)

    在 Bean 的配置中,可以通过parent属性来指定另一个 Bean 作为该 Bean 的 ParentBean, 从而继承一些可复用的配置数据,并可以覆盖某些值或根据需要添加其他值,这是一种模板方法模式的一种体现。

    ChildBean 可以从 ParentBean 继承Bean的作用范围生命周期属性注入等信息,但依赖项自动装配模式等一些信息始终从子类获取,这需要我们在开发过程中多加关注。

    注意:

    1. Bean的继承是对象层面的继承,子类继承父类对象的属性值。因此,不同类之间可以互相继承。

    2. Java是类层面的继承,继承的是父类的类结构信息。

    3. 如果用 Java 代码的方式来配置元数据,那么可以直接使用 Java 的继承机制来复用元数据的配置信息。

     

     

    第四节 依赖注入详解

    1. 什么是依赖注入?

    依赖注入 (Dependency Inject)是控制反转思想的一种实现方式,指的是程序运行过程中,若需要调用另一个对象协助时,无须在代码中创建被调用者,而是依赖于外部容器,由外部容器创建后传递给程序。

     

    2. 依赖注入的方式

    1) 构造函数注入

    基于构造函数的注入是通过容器调用有参构造函数来完成的,每个参数代表了一个依赖项。

    如果编译时没有带上调试信息,还可以通过参数索引进行构造函数的匹配,并且允许在能够推断的情况下省略索引属性。

    此外,还支持使用c名称空间简化构造注入的书写方式 ,了解即可:

    使用Java配置时的注入方式如下:

    提示:

    1. 使用工厂创建Bean对象时,可以通过constructor-arg标签进行工厂方法的参数注入,使用方式同构造函数注入一致。

    2. value属性用于注入基本属性值和String,ref用于注入引用类型,集合、空值、内联Bean的注入应使用子标签来完成。

     

    2) Set方法注入

    基于Set方法的注入是在对象创建完成后,调用对象的set方法来进行属性注入。

    类似的,可以使用p名称空间来进行简化书写。

    使用Java配置时的注入方式如下:

    注意:

    1. @Autowired注解也可用于普通方法上,但该方法必须被public void修饰,且每一个参数都是容器中的某个依赖项。

     

    3) 字段注解注入

    基于 Java 配置的@Autowired注解支持直接对字段进行注入,且无需 Set 方法:

     

    4) 自动注入(不推荐)

    Spring 支持对 Bean 的属性进行自动注入,并且当类中新增依赖项时,无需修改配置即可自动满足该依赖项。

    自动注入的模式共有四种,通过 bean 标签的autowire属性来指定:

    ModeExplanation
    no(默认)不进行自动装配, 仅由 ref 属性来定义 Bean 之间的依赖关系,可以提供更好的控制和清晰度。
    byName属性名称自动装配,Spring通过属性名在容器中查找匹配的依赖 Bean 进行注入。
    byType属性类型自动装配,如果查找到唯一匹配的依赖Bean,则进行注入。但如果查找到多个,则引发致命异常。
    constructor类似于byType,适用于构造函数参数的自动装配。不同的是,如果未查找到匹配的依赖Bean也会引发致命异常。

    使用Java配置时的自动注入方式如下:

    注意:

    1. 对按类型自动注入的方式(byType/Constructor),可以设置候选(autowire-candidate)或优先(primary)属性,减少注入报错。

    2. autowire属性已经被标记为废弃(@Deprecated),并且在实际开发中不推荐使用它。

     

     

    3. 特殊注入场景

    1) 注入集合属性

    顾名思义,就是给类中的集合成员进行属性注入,用的也是set方法注入的方式,只不过变量的数据类型都是集合。

    如果Bean存在继承关系,则可以通过merge属性来合并父类的集合。

    在使用Java配置时,@Autowired注解可以收集所有匹配的 Bean 并注入到指定的集合中,更加方便。

    注意:

    1. 集合数据分两种,单列集合(array,list,set)和双列集合(map,entry,props,prop),同类型集合注入方式可以兼容。

     

    2) 注入原型Bean

    如果某个Bean是单例的,并且依赖了原型Bean,则该原型Bean不能使用上述方式直接注入,必须在每次使用时都创建新的实例。

     

    实现感知接口方式

    通过实现感知接口ApplicationContextAwareBeanFactoryAware获取容器的引用,进而调用getBean方法创建原型Bean。

     

    使用Lookup方式

    上述方式依赖了Spring框架,并且由程序代码主动获取对象,不符合控制反转的原则,下面将使用XML配置方式由Spring来完成上述代码。

    也可以使用注解方式来配置,并且@Lookup的 value 属性可以根据查找方法声明的返回类型来解析。

    注意:

    1. 查找方法的签名必须是<public|protected> [abstract] <return-type> theMethodName(no-arguments)形式。

    2. 查找方法允许是非 abstract 的,Spring会将原来的方法覆盖。

    3. 由于Spring是通过CGLib来实现该方式的,因此该类和查找方法都不能被final修饰。

    4. 查找方法不适用于工厂方法和配置类中的@Bean方法,因此在这种情况下,实例并不是由Spring创建的,无法进行动态代理。

     

    注入ObjectFactory

     

    3) 静态变量注入

    我们封装工具类的时候,大多数提供的是静态方法,而静态方法只能访问静态变量,此时,则需要将我们所需的依赖注入到静态变量中。

     

    通过Set方法注入

     

    使用@PostConstruct注解

     

    4) 普通类使用Bean

    如果某个类是一个普通的Java,并且并没有被Spring容器所管理,那么如何使用Spring容器创建的Bean实例呢?

     

    SpringUtils工具类

    定义一个工具类,实现容器的感知接口,通过静态方法向外部提供获取Bean的功能。

     

    静态化Bean

    修改 Bean 的代码,在初始化回调时将 Bean 的 this 指针设置到一个静态变量中。

     

     

    4. 关于循环依赖

    1) 什么是循环依赖?

    如果创建A对象时,依赖B对象,创建B对象时,又依赖A对象,就会出现循环依赖,抛出BeanCurrentlyInCreationException异常。

    注意:

    1. 容器维护了singletonsCurrentlyInCreationprototypesCurrentlyInCreation集合来检测是否出现循环依赖。

     

    2) 循环依赖的解决方式

    Spring 定义了三级缓存,来解决单例对象非构造函数注入时的循环依赖问题,并且支持AOP代理对象的创建:

     

    3) 循环依赖解决流程分析

    注意:

    1. 引入三级缓存的目的,主要是为了暴漏早期对象时,能够通过 ObjectFactory 创建 AOP 代理对象。

    2. 如果是原型对象或使用构造函数注入,由于无法暴露早期对象,无法解决相应的循环依赖问题。

     

     

    5. 关于感知接口

    1) 什么是感知接口?

    感知接口(Aware)用于注入一些基础 Bean,主要是通过ApplicationContextAwareProcessor实现的,在Bean 初始化之前执行。

     

    2) 常见感知接口
    接口名称主要功能
    ApplicationContextAware注入 ApplicationContext,用于访问 Spring 容器上下文信息。
    BeanFactoryAware注入 BeanFactory,用于直接访问和操作 Bean。
    BeanNameAware注入 Bean 的名称(idname),用于在 Bean 中使用自己的名称。
    ResourceLoaderAware注入 ResourceLoader,用于加载资源文件。
    EnvironmentAware注入 Environment,用于获取环境配置信息。
    MessageSourceAware注入 MessageSource,用于国际化支持。
    ApplicationEventPublisherAware注入 ApplicationEventPublisher,用于发布自定义事件。
    ServletContextAware注入 ServletContext,用于访问 Servlet 容器上下文信息(仅限 Web 应用)。
    ServletConfigAware注入 ServletConfig,用于访问 Servlet 配置信息(仅限 Web 应用)。

     

    3) 使用示例

     

     

    6. 关于类型处理器

    1) 什么是类型处理器

    类型处理器可以将一种类型的对象转换为另一种类型的对象,Spring框架中分为如下两类:

    注意:

    1. 上述三种类型处理器的生效顺序为:Formatter -> Converter -> PropertyEditor

     

    2) 内置类型处理器
    内置PropertyEditor
    PropertyEditor功能描述
    BooleanEditor将字符串 "true""false" 转换为 Boolean 类型。
    IntegerEditor将字符串转换为 Integer 类型。
    LongEditor将字符串转换为 Long 类型。
    FloatEditor将字符串转换为 Float 类型。
    DoubleEditor将字符串转换为 Double 类型。
    StringTrimmerEditor去除字符串前后的空白,并可选地将空字符串转换为 null
    StringArrayPropertyEditor将逗号分隔的字符串转换为字符串数组。
    PropertiesEditor将字符串转换为 java.util.Properties 对象。
    PatternEditor将字符串转换为 java.util.regex.Pattern 对象。
    ResourceEditor将字符串转换为 Resource 对象。
    URLEditor将字符串转换为 java.net.URL 对象。
    URIEditor将字符串转换为 java.net.URI 对象。
    FileEditor将字符串转换为 java.io.File 对象。
    PathEditor将字符串转换为 java.nio.file.Path 对象。
    ClassEditor将字符串转换为 Class 对象。
    LocaleEditor将字符串转换为 java.util.Locale 对象。
    TimeZoneEditor将字符串转换为 java.util.TimeZone 对象。
    ZoneIdEditor将字符串转换为 java.time.ZoneId 对象。
    CustomDateEditor将字符串转换为 java.util.Date 对象,支持自定义日期格式。
    UUIDEditor将字符串转换为 java.util.UUID 对象。

     

    内置Converter
    类型处理器名称功能描述
    StringToBooleanConverter将字符串转换为布尔值(如 "true" 转换为 true
    ObjectToStringConverter将对象转换为字符串,调用对象的 toString 方法
    StringToNumberConverterFactory将字符串转换为数字类型(如 IntegerLong 等)
    NumberToNumberConverterFactory将数字子类型(基本类型)转换为数字包装类型
    StringToCharacterConverter将字符串的第一个字符转换为 Character 类型
    NumberToCharacterConverter将数字转换为 Character 类型
    CharacterToNumberFactoryCharacter 类型转换为数字类型
    StringToEnumConverterFactory将字符串转换为枚举类型(通过 Enum.valueOf
    EnumToStringConverter将枚举类型转换为字符串(返回枚举的 name 值)
    StringToLocaleConverter将字符串转换为 java.util.Locale 对象
    PropertiesToStringConverterjava.util.Properties 转换为字符串(默认使用 ISO-8859-1 编码)
    StringToPropertiesConverter将字符串转换为 java.util.Properties 对象(默认使用 ISO-8859-1 解码)

     

    内置Formatter
    Formatter功能描述
    NumberFormatter格式化和解析数字类型(如 IntegerDouble)。支持国际化。
    DateTimeFormatter格式化和解析日期时间类型(如 LocalDateLocalDateTime)。支持国际化。
    NumberStyleFormatter格式化和解析数字,支持不同的数字风格(如货币、百分比)。支持国际化。
    PercentFormatter格式化和解析百分比值。支持国际化。
    CurrencyFormatter格式化和解析货币值。支持国际化。
    BooleanFormatter格式化和解析布尔值(如 "true""false")。
    EnumFormatter格式化和解析枚举类型。
    StringFormatter格式化和解析字符串类型(通常用于简单的字符串处理)。
    LocalizedFormatter根据 Locale 格式化和解析数据,支持国际化。

     

    3) 自定义类型处理器
    自定义Converter

    实现Converter<S, T>接口,覆盖 convert方法,完成从源类型 S 到目标类型 T 的转换。

    将自定义的 Converter 注册到 Spring 的 ConversionService 中:

    使用 Java 配置方式如下:

     

    自定义Formatter

    实现Formatter<T>接口,覆盖 printparse方法,完成类型 T 和字符串之间的双向转换。

    将自定义的 Formatter 注册到 Spring 的 FormattingConversionService 中:

    使用 Java 配置如下:

     

     

    第五节 基于注解的IOC配置

    1. 开启注解IOC支持

    1) XML配置为主

    在使用基于XML配置的ClassPathXmlApplicationContext来构建 IOC 容器时,需要在XML中开启注解配置开关

    开启注解配置开关后,会自动注册下面一些BeanPostProcessor,并且在定义它的应用程序上下文中扫描Bean上的注解

     

    2) 注解配置为主

    直接使用AnnotationConfigApplicationContext基于注解配置来构建容器,然后使用@ImportResuorce注解导入其它XML配置。

    注意

    1. Spring优先对注解配置的属性进行注入,如果在XML中配置了相同的属性,那么将会对之前的配置进行覆盖。

     

     

    2. 注册组件(Bean)

    1) 组件标记(@Component)

    @Component 注解作用于类、接口、枚举或其它注解之上,标记该元素为 Spring 的一个组件,唯一的 value 属性用于指定组件的名称。

    @Scope注解可跟随组件标记使用,用于指定组件的作用范围,可选:singleton(默认)prototypesession等。

    注意:

    1. 如需区分控制层、服务层、持久层组件,可使用@Controller@Service@Repository这三个语义性注解。

     

    2) 组件扫描(@ComponnetScan)

    @ComponentScan 一般作用于 @Configuration 类上,用于自动检测标记的组件,并注册相应的 BeanDefinition 实例。

    等效的XML配置如下:

    注意:

    1. 如未在组件标记时指定组件名称,则会由BeanNameGenerator策略生成,默认为类名的首字母小写

    2. 内置的过滤类型有:按类注解(默认)按基类/接口按AspectJ表达式按正则表达式等,需自定义可实现TypeFilter接口。

    3. 可引入spring-context-indexer包生成组件索引(META-INF/spring.components 文),提高大型应用程序的启动性能。

     

     

    3. 依赖注入

    1)@Autowired(推荐)

    @Autowired 注解一般用于引用类型属性的自动注入,可作用于成员变量构造函数普通方法函数参数注解上。

    @Qualifier注解用于指定限定符(一般是Bean的名称),以在多个类型匹配的对象中找到唯一确定的 Bean 进行注入。

    img

    注意:

    1. 数据注入注解@Autowired@Resource@Value等由 BeanPostProcessor 处理,不可在 BeanPostProcessor 的子类中使用。

    2. 如果无法找到合适的 Bean,则抛出 NoSuchBeanDefinitionException 异常,可以通过 required 属性对此进行修改。

    3. 对于数组或有序集合注入,如果想精确控制注入顺序,可以实现 Ordered接口或使用@Order/@Priority注解。

     

    2) @Resource

    @Resource 是 JSR-250 定义的注解,也用于属性注入,可以作用于类、成员变量和方法上,但不支持在构造函数参数上使用。

    img

    注意:

    1. Spring 中也可使用 JSR-330 注解进行组件扫描和自动注入,如ManagedBean@InjectNamed等,但需引入额外的依赖:

     

    3) @Value

    @Value注解一般用于字面量的注入,并且会解析${}从环境中取值进行替换。

    注意:

    1. @Value注解会优先去容器中查找名称一致的 Bean进行注入,只有未找到合适的 Bean 时,才会进行字面量注入。

     

    4) @Qualifier
    声明和使用

    @Qualifier 用于指定一个限定符,以缩小自动装配的匹配范围,可作用于成员变量、构造方法参数其它方法参数之上。

    假设 MovieCatalog 类型的 Bean 配置如下,两个对象分别声明了 main 和 action 两个限定符,则会注入 SimpleMovieCatalog1 对象。

    提示:

    1. 每个Bean都会将 id 属性作为默认的限定符值,如上例也可以使用@Qualifier("simpleMovieCatalog2")来连接另一个实例。

    2. @Qualifier对集合类型变量也有效,将会在注入前限定匹配的范围(注意:Bean的限定符值并不是唯一的)。

     

    自定义限定符

    可以基于 @Qualifier 自定义一些组合注解,并对属性做一些修改,只有当全部属性都匹配时,才会加入候选列表。

     

    5)@Primary

    @Primary 注解可以指定某个 Bean 作为该类型的优先 Bean,如果候选对象列表中存在唯一的优先 Bean,则使用该值进行注入。

     

    6)@Required(不常用)

    @Required 注解只能作用于 setter 方法之上,标记某个属性在实例化 时必须被注入,否则就会抛出 BeanInitializationException 异常,具体请参考 RequiredAnnotationBeanPostProcessor 的实现。

     

     

    第六节 基于 Java 的IOC配置

    1. 开启Java配置支持

    1) 以XML配置为主

    使用 ClassPathXmlApplicationContext 创建容器,在 XML 中注册被@Configuration标注的配置类

    此外,也可直接通过注解扫描,扫描被@Configuration标注的配置类

     

    2) 以注解配置为主

    使用 AnnotationConfigApplicationContext 创建容器,在@Configuration类上使用@ImportResource注解来导入XML配置:

     

    3) 导入其它组件

    在 Java 配置中,可以使用 @Import 注解来导入其它配置类常规组件类。且无需对应类添加 @Component 组件注解。

    如果需要导入的类数量比较多,还可以使用ImportSelectorImportBeanDefinitionRegistrar接口来辅助导入。

    如果想对某些配置类或Bean进行选择性导入,即在dev环境导入指定类,在test环境导入另外的类,可以使用@Profile注解,详见下文。

    注意:

    1. ImportSelector通常用于简单的条件导入,而 ImportBeanDefinitionRegistrar 用于动态注册或修改 bean 定义等更复杂的场景。

    2. ImportSelector和ImportBeanDefinitionRegistrar实现类要被@Import导入后才会生效,并且该实现类不会被注册到IOC容器中。

    3. ImportSelector的子类DeferredImportSelector用于延迟导入,在配置类的解析完成后才生效,主要用于条件装配场景。

     

    4) 加载属性文件

    @PropertySource 注解用于加载属性配置文件(properties/xml/yml)到容器中,方便通过${}env.getProperty("")方式取值。

    在 SpringBoot 中,还可以将属性文件与一个Java类绑定,非常方便的将属性文件中的变量值注入到该Java类的成员变量中。

     

     

    2. 注册组件(Bean)

    1) @Bean

    @Bean注解用于注册组件,默认情况下,beanName 为方法名称,value 为返回的对象,类型为返回值类型。

    等效的 XML 配置如下:

     

    2) Bean的属性
    名称和别名

    可以使用name属性来指定bean的名称,或通过指定多个名称来设置别名。

    在必要的时候,也可以通过@Description提供更详细的文本描述。

     

    作用范围

    使用 @Scope 注解可以修改 Bean 的作用范围,默认为 singleton

     

    生命周期回调

    @Bean 注解支持指定任意的初始化和销毁回调方法,就像 XML 配置中的init-methoddestroy-method属性一样。

    你也可以在构造期间直接调用 init() 方法同样有效。

    除上之外,任何使用 @Bean 注解定义的类都支持常规的生命周期回调,并且可以使用 JSR-250 中的@PostConstruct@PreDestroy注解。同样的,如果 bean 实现了InitializingBeanDisposableBeanLifecycle,则容器将调用它们各自的方法。

    注意:默认情况下,使用Java 配置时会自动将公共的closeshutdown方法注册为销毁回调,可以使用下面方式去除。

     

     

    3. 依赖注入

    1) 方法参数方式

    创建 Bean 的方法可以具有任意数量的参数,这些参数描述构建该 bean 所需的依赖关系,解析机制与基于构造函数的依赖注入几乎相同。

     

    2) 方法调用方式

    除了使用方法参数来定义依赖外,还可以通过方法调用来定义 Bean 之间的依赖,且每次方法调用返回的是同一个实例

    等效的 XML 配置如下:

    注意:

    1. 方法调用方式仅在@Configuration类中有效,而不能在普通@Component类中使用,因为后者未被 CGLIB 拦截处理。

     

    3) 类成员注入(不推荐)

    @Configuration类也是一个Bean,因此可以使用@Autowired@Value注入需要的依赖,在方法中使用。

    注意:

    1. 不推荐使用此种方式,因为会@Configuration类的处理时机非常早,会导致注入的依赖性被过早的初始化

    2. 相应的,如果创建的BeanPostProcessorBeanFactoryPostProcessor等特殊类,应该使用静态方法。

     

    4) 注入原型Bean

    在单例作用域的 bean 依赖于原型作用域的 bean 的情况下,通过使用 Java 配置注入如下:

     

     

     

    第02章_SpringAOP

    第一节 SpringAOP简介

    1. 什么是SpringAOP?

    AOP(Aspect Oriented Programming,面向切面编程是一种通过预编译动态代理的方式在不修改源代码的情况下给程序动态添加某种特定功能的技术,用来弥补面向对象编程(OOP)中的一些不足,它更关注于多个类之间的共同问题,如日志记录,性能统计,安全控制,事务处理,异常处理等。

    下面表格列举了AOP中的一些常用概念:

    名词解释
    目标(Target)被通知的对象
    代理(Proxy)向目标对象应用通知之后创建的代理对象
    连接点(JoinPoint)目标对象的所属类中,定义的所有方法均为连接点
    切入点(Pointcut)被切面拦截 / 增强的连接点(切入点一定是连接点,连接点不一定是切入点)
    通知(Advice)增强的逻辑 / 代码,也即拦截到目标对象的连接点之后要做的事情
    引介(Introduction)一种特殊的通知,可以为现有对象添加任何接口的实现
    切面(Aspect)切入点(Pointcut)+通知(Advice)
    顾问(Advisors)一种特殊的切面,只包含一个通知(引介),是SpringAOP中独有的概念
    织入(Weaving)将通知应用到目标对象,进而生成代理对象的过程动作

    Spring框架以IOC容器为基础,引入AspectJ相关注解和类库,采用运行期动态代理织入,实现了基于方法拦截的AOP支持。

    Spring AOPAspectJ 框架对比如下:

    特性Spring AOPAspectJ
    增强方式运行时增强(基于动态代理)编译时增强、类加载时增强(直接操作字节码)
    切入点支持方法级(Spring Bean 范围内,不支持 final 和 staic 方法)方法级、字段、构造器、静态方法等
    性能运行时依赖代理,有一定开销,切面多时性能较低运行时无代理开销,性能更高
    复杂性简单,易用,适合大多数场景功能强大,但相对复杂
    使用场景Spring 应用下比较简单的 AOP 需求高性能、高复杂度的 AOP 需求

    注意:

    1. Spring AOP 默认通过运行期的动态代理来实现AOP的,可额外导入 spring-aspects 依赖来支持通过类加载时编织实现AOP。

    2. 如果需要对字段非 Spring 管理的对象进行拦截,需额外在开发和编译过程中引入 AspectJ 编译器/编织器。

     

     

    2. SpringAOP入门案例

    1) 导入AOP依赖

    首先导入SpringAOP开发过程中需要使用的jar包spring-aop.jaraspectjweaver.jar等:

     

    2) 开启AOP支持

    基于Java配置的项目,可以在配置类上加@EnableAspectJAutoProxy注解开启注解AOP支持:

    基于XML配置的项目,可以在Spring配置文件中加入<aop:aspectj-autoproxy/>元素开启注解AOP支持:

     

    3) 业务代码示例(注意包名)

     

    4) 配置切面进行拦截

    通过注解方式配置切面如下:

    等效的XML配置如下:

     

    5) 测试拦截效果

     

     

    第二节 相关组件详解

    1. 切面

    1) 声明切面

    切面切入点通知(引介)的结合,可通过@Aspect注解声明切面:

    等效的XML配置如下:

    注意:

    1. Spring在创建 Bean 对象时,如果发现带有@Aspect注解,则会使用动态代理织入拦截逻辑。

    2. 切面本身不能成为其他切面的目标,标记某个类为切面的同时,会自动从其它切面的动态代理列表排除。

     

    2) 切面的实例化

    切面本身是一个Bean,默认是单例的,通过perthispertarget参数,可以为每个符合条件的对象创建一个新的切面实例:

    注意:

    1. 使用CGLIB代理时,代理对象和目标对象是同一个对象(代理类继承目标类),但使用JDK动态代理时不是(同接口不同类)。

     

     

    2. 切入点

    切入点指想要拦截的方法或字段,通过切入点表达式来确定哪些连接点作为切入点。

     

    1) 声明切入点

    可以直接在通知上配置切入点表达式来声明内联切入点

    也可以在 void 方法上加@Pointcut注解来定义共享切入点,然后在通知上进行引用(注意加小括号):

    等效的XML配置如下:

    注意

    1. 如果共享切入点需要被其它切面类引用,则需要被public void修饰,不能只是private void

    2. Spring 还提供了Pointcut、AspectJExpressionPointcut、JdkRegexpMethodPointcut等接口来配置切入点,但不推荐使用。

     

    2) 切入点表达式

    SpringAOP 支持多种类型的切入点表达式,并支持..*+等通配符。

    类型示例说明
    executionexecution(* com.example.service.*.*(..))匹配指定类的指定方法
    withinwithin(com.example.service.*)匹配指定类的所有方法
    thisthis(com.example.service.UserService)匹配代理对象类型为指定类型的方法
    targettarget(com.example.service.UserService)匹配目标对象类型为指定类型的方法
    argsargs(java.lang.String)匹配方法参数类型为指定类型的方法
    @annotation@annotation(org.springframework.transaction.annotation.Transactional)匹配方法本身带有指定注解的方法
    @within@within(org.springframework.stereotype.Service)匹配类带有指定注解的方法
    @target@target(org.springframework.stereotype.Repository)匹配目标类带有指定注解的方法
    @args@args(com.example.annotation.Validated)匹配参数类型带有指定注解的方法
    beanbean(userService)匹配指定Bean的所有方法

    其中最常用的切入点表达式类型为execution,常用示例如下:

    注意:

    1. SpringAOP仅支持普通方法的拦截,不支持字段和构造器的拦截。

     

    3) 表达式的组合运算

    可以使用&&, ||!对切入点表达式进行组合运算,如下例所示:

     

     

    3. 通知

    通知指拦截后要执行的公共逻辑,一般与切入点表达式关联使用。

     

    1) 前置通知(Before Advice)

    前置通知在切入点方法执行之前织入一些自定义逻辑,但不能更改返回值,如果前置通知出现异常,则会传播回拦截器链。

    可以使用@Before注解标识切面中的某个方法为前置通知:

    类似的XML配置如下:

    此外,也可以通过实现org.springframework.aop.MethodBeforeAdvice接口定义一个前置通知:

     

    2) 后置通知(AfterReturning Advice)

    后置通知在切入点方法执行之后执行,可以访问返回值(不能被修改),方法的参数和目标对象等运行时信息。

    可以使用@AfterReturning注解标识切面中的某个方法为后置通知,并用returning属性指定参数名称用于接收返回值

    类似的XML配置如下:

    此外,也通过实现org.springframework.aop.AfterReturningAdvice接口定义一个后置通知:

     

    3) 异常通知(AfterThrowing Advice)

    异常通知在切入点方法抛出异常时执行,与 catch 代码块执行时机类似。

    可以使用@AfterThrowing注解标识切面中的某个方法为异常通知,并通过throwing属性来指定一个参数名称接收抛出的异常

    类似的XML配置如下:

    此外,也通过实现org.springframework.aop.ThrowsAdvice接口定义一个异常通知:

    注意:

    1. 如果异常通知本身引发异常,则它将覆盖原始异常,通常重写为 RuntimeException 类型,与任何方法签名都能够兼容。

     

    4) 最终通知(After Advice)

    最终通知在方法执行完毕退出时执行(与finally代码块执行时机类似),可以使用@After注解标识切面中的某个方法为最终通知。

    类似的XML配置如下:

    此外,也通过实现org.springframework.aop.AfterAdvice接口定义一个最终通知:

     

    5) 环绕通知(Around Advice)

    环绕通知可以在方法执行的四个阶段都添加额外逻辑,相当于前四种通知的集合。

    可以使用@Around注解标识切面中的某个方法为环绕通知,通常用于启动和停止计时器等需要方法前后协同的特殊公共逻辑。

    类似的XML配置如下:

    此外,也通过实现org.aopalliance.intercept.MethodInterceptor接口定义一个环绕通知,并通过MethodInvocation来获取运行时相关信息和进行连接点方法的调用。

    注意:

    1. 如果前四种通知能够满足你的需求,那么尽量不要使用环绕通知。

     

    6) 通知的顺序

     

    7) 通知的参数
    获取连接点信息

    可以将通知的第一个参数声明为org.aspectj.lang.JoinPoint类型,用于获取当前连接点对象,包括如下一些信息:

     

    获取方法实参

    如果需要在通知中使用方法实参,可通过args(参数名称...)进行绑定,在调用通知时会传递相应的实参值:

    业务方法的参数可以是泛型参数,Spring AOP会检查实参类型并进行兼容性绑定,但集合中的泛型除外,因为逐个元素检查性能低。

     

    获取其它实参

    代理对象(this),目标对象(target)和注解(@within@target@annotation@args)都可以以类似的方式绑定实参:

     

    修改方法实参

    方法的实参可以被修改,并允许将修改后的实参传递给业务方法进行调用:

     

    注意事项

    如果未开启调试信息,可能无法获取方法参数名,则需要通过argNames参数显示声明方法参数名称。

    注意:

    1. XML配置也可使用类似方式绑定实参,但&&可能会导致XML解析错误,可以使用andornot分别代替&&||!

    2. SpringAOP可以处理方法参数中的泛型,并进行兼容性匹配拦截,但集合中的泛型除外。

     

     

    4. 其它AOP组件

    1) 引介

    引介(Introduction)是一种特殊的通知,可以为现有对象添加任何接口的实现,通过使用@DeclareParents注解定义一个引介:

    等效的XML配置如下:

    此时,服务 Bean 可以直接用作 UsageTracked 接口的实现来使用。

    注意:

    1. 也可以使用IntroductionInterceptorIntroductionAdvisor来定义引介,具体介绍请参考Spring官方文档。

     

    2) 顾问

    顾问(Advisors)是一种特殊的切面,只包含一条通知,通过<aop:advisor>标签配置,最常见的使用场景为Spring的声明式事务配置。

    提示:

    1. 同一般切面类似,顾问同样支持使用 pointcut 属性来定义内联切入点表达式,及使用order属性配置优先级。

    2. 你也可以通过继承org.springframework.aop.support.DefaultPointcutAdvisor类的方式定义Advisor。

    Spring 提供了一个名为RegexpMethodPointcutAdvisor的顾问类封装了切入点和通知,简化了使用:

     

     

    第三节 扩展补充

    1. 代理对象

    1) 选择代理方式

    在 Spring AOP 中,如果目标对象实现了接口,则会优先使用JDK动态代理,否则只能使用CGLIB动态代理

    可以通过proxyTargetClassproxyMode属性来强制使用 CGLIB 代理:

    类似的XML配置如下:

    注意:

    1. 使用CGLIB动态代理时,必须保证目标类及目标方法是非final的,且最好是 public 的。

     

    2) 创建代理对象

    ProxyCreatorSupport是 Spring AOP 中用于创建代理对象的基类,它提供了创建动态代理的通用逻辑AOP配置管理功能

    以下是一个简单的示例代码,展示如何使用 ProxyCreatorSupport 创建一个带有日志功能的代理对象:

    在Spring AOP中,提供了许多创建代理对象的工具类,支持手动或自动扫描的方式创建代理对象:

    注意:

    1. 推荐使用AnnotationAwareAspectJAutoProxyCreator,可通过@EnableAspectJAutoProxy注解来启用。

     

     

    2. 目标源

    1) 什么是目标源

    TargetSource用于提供代理对象使用的目标对象,并且允许在运行时动态地切换目标对象。

     

    2) 可热交换目标源

    基于可热交换目标源HotSwappableTargetSource创建的AOP代理对象,允许对目标对象进行动态替换。

    可以使用 HotSwappableTargetSource 上的swap()方法动态替换目标对象,该操作是线程安全的,并且会立即生效:

     

    3) 池目标源

    池目标源维护了业务Bean的实例池,导入commons-pool.jar包后即可使用默认实现CommonsPool2TargetSource

     

    4) 原型目标源

    原型目标源PrototypeTargetSource与池目标源类似,但其每次方法调用都会创建目标的新实例:

     

    5) ThreadLocal目标源

    如果需要为每个线程创建新的目标对象,则可以使用ThreadLocal目标源:

     

     

    3. AOP工具类

    1) AOPUtils

    AopUtils是 Spring 框架中用于处理 AOP 相关操作的工具类,常用方法如下:

     

    2) AopProxyUtils

    AopProxyUtils 对 AopUtils 的补充,主要在创建代理对象和处理代理相关细节时使用,常用方法如下:

     

    3) AopContext

    AopContext 是 Spring AOP 框架中的一个工具类,用于在方法内部访问当前 AOP 代理对象

     

    4) 其它

     

     

    4. AOP的一些问题

    1) 无法代理非public方法

    基于JDK的动态代理是针对接口的代理,只会有 public 方法,但基于 CGLIB 的代码,默认不会重写非 public 方法,需手动开启。

    注意:

    1. 可通过ProxyFactorysetOptimize(true)支持代理非 public 方法,但是需要注意访问安全问题。

     

    2) 不支持方法的内部调用

    代理方法在同一个类的内部被调用时,不会执行代理逻辑,因为是通过目标对象(而非代理对象)来调用的。

    注意:

    1. 该问题可通过注入代理对象,通过代理对象调用代理方法来解决,或者通过AopContext.currentProxy()方式(不推荐)。

    2. 原生 AspectJ 框架没有此自调用问题,因为它不是基于代理的 AOP 框架。

     

     

    第四节 AOP代码示例

    1. 基于AOP支持自动重试

    对于一些可重复执行的操作,在执行失败时自动进行重试,该功能涉及多个类的逻辑修改,非常适合使用切面实施。

    相应的 Spring 配置如下:

    为了优化切面使用,仅对指定的方法进行重试,我们可以定义一个@Idempotent注解,对需要重试的方法进行标记。

    然后,修改切面的切入点表达式,仅对有@Idempotent注解的方法进行匹配:

    如果你更喜欢通过XML进行AOP配置,可参考如下配置示例,配套的 Java 类去掉 AOP 相关注解即可。

     

     

     

    第03章_SpringMVC

    第一节 SpringMVC简介

    1. 什么是SpringMVC?

    MVC是一种通用软件架构,M是指模型V是指视图C则是控制器,使用MVC的目的是将M和V的实现代码分离,从而使同一个程序可以使用不同的表现形式。MVC架构的工作流程如下:

    1. 用户发送请求到服务器的Controller(C)

    2. Controller调用相应的Model(M)处理请求。

    3. 根据处理结果渲染对应的View视图(V)进行响应。

    image-20220829215822285

    SpringMVC是MVC架构的一种具体实现,为表述层(web页面+Servlet)开发提供了一整套完备的解决方案。

    SpringMVC的主要特点如下:

    1. Spring系列产品,与Spring的IOC容器等基础设施无缝对接。

    2. 基于原生的Servlet,通过功能强大的前端控制器DispatcherServlet,对请求和响应进行统一处理。

    3. 表述层各细分领域需要解决的问题全方位覆盖,提供全面解决方案

    4. 内部组件化程度高,可插拔式组件即插即用,想要什么功能配置相应组件即可。

    5. 性能卓著,尤其适合现代大型、超大型互联网项目要求。

     

     

    2. SpringMVC入门案例

    1) 创建Maven工程

    image-20220808224147905

    注意:

    1. 默认会标记src\main\java为源代码根目录,src\main\resources为资源根目录,若没有则需手动标记。

    2. 在单独使用 SpringMVC 时,需在pom.xml中修改打包方式为war包<packaging>war</packaging>

     

    2) 配置web.xml

    新建src\main\webapp\WEB-INF\web.xml文件,配置DispatcherServlet统一处理前端请求。

    如需在Web工程启动时,加载Spring上下文配置,只需在web.xml中注册ContextLoaderListener监听器即可。

     

    3) 配置spring-mvc-config.xml

    在资源根目录下新建spring-mvc-config.xml文件(与web.xml中对应即可),配置表述层组件扫描默认视图解析器

     

    4) 编写请求控制器

     

    5) 编写视图页面

    新建src\main\webapp\WEB-INF\pages\index.jsp文件(与视图解析器配置的前缀后缀以及视图名称相对应),编写JSP页面如下。

     

    6) 进行测试

    添加一个Tomcat服务器,部署当前应用,设置上下文路径后进行启动。

    image-20220808230158199

    启动完成后访问http://localhost:8080/页面,显示如下即代表测试成功。

    image-20220808230422200

     

     

    第二节 请求映射详解

    请求映射指根据请求路径请求方法请求参数等信息,将请求映射到对应的控制器方法,一般使用@RequestMapping注解配置。

     

    1. 请求路径路由

    1) 声明请求路径

    @RequestMapping注解的path(value)属性用来声明请求路径,类上为一级路径,方法上为二级路经,SpringMVC会自动进行拼接。

    提示:

    1. 一个控制器方法可以配置多个请求路径,用于将不同的请求映射到同一段处理逻辑。

    2. Spring MVC默认开启.*后缀模式匹配,以便映射到/person的控制器也能映射到/person.*

    3. 建议设置useSuffixPatternMatching(false)和favorPathExtension(false)来关闭后缀匹配模式,并通过Accept请求头进行替代。

    4. 请求路径还可以嵌入${…}占位符,这将在启动时从系统环境等其他属性源中解析替换。

     

    2) 请求路径通配符

    可以通过Ant风格的通配符来模糊匹配请求路径:

     

     

    2. 请求方式限定

    1) 声明请求方式

    @RequestMapping注解的method属性用来声明请求方式,如果方式不匹配则报"405:Request method 'POST' not supported"错误。

     

    2) 请求方式组合注解

    为了简化请求方法匹配,SpringMVC设计了一些组合注解,如@GetMapping@PostMapping@PutMapping@DeleteMapping等。

     

    3) 转换请求方式

    浏览器默认只能发送GETPOST请求,如需发送其它类型请求,可用POST请求+_method参数进行伪造。

    首先web.xml文件中配置过滤器如下:

    然后前端发送请求时改为POST请求,并附带_method参数指定要伪造的请求方法。

    这时就可以正确匹配控制器中的PUT方法了。

     

     

    3. 请求参数与请求头匹配

    1) 声明请求参数和请求头(了解)

    @RequestMapping注解的params属性和headers属性分别用来声明控制器方法所需的请求参数请求头,支持的格式如下:

     

    2) 几个特殊的请求头

    特别的,对于Content-Type (请求消息格式)Accept (可接收的消息格式)请求头,可分别通过consumesproduces属性来匹配。

    注意:

    1. 常用的 Content-Type 常量在MediaType类中提供了定义,如:APPLICATION_JSON_VALUEAPPLICATION_XML_VALUE

     

     

    4. 自定义请求映射逻辑(了解)

    SpringMVC可对请求映射进行精确控制,主要有两种方式:

     

     

    5. 静态资源映射

    默认情况下,前端不能访问服务器的静态资源(html/css/js/img等),需要对其进行映射配置。

    注意:

    1. 如需生成“版本化”的URL,可考虑配置VersionResourceResolver。

     

     

    第三节 参数绑定详解

    1. 常用参数类型和注解

    控制器方法常用的参数类型如下:

    参数绑定常用的注解如下:

     

     

    2. 直接获取参数

    HttpServletRequest作为控制器方法的形参,框架在调用时会自动注入为当前请求对象,通过该对象即可获取参数和Cookie等。

     

     

    3. 绑定请求参数

    1) 简单类型

    在控制器方法的形参位置,设置和请求参数同名的形参,当浏览器发送请求,匹配到请求映射时,在DispatcherServlet中就会将请求参数赋值给相应的形参。

    注意

    1. 若有多个同名的请求参数,可以设置形参为字符串数组或者字符串类型。

      • 若使用字符串数组作为形参,则映射为多个数组元素。

      • 若使用字符串作为形参,则映射为一个逗号拼接的字符串。

    2. 使用Map作为参数,它将接收所有未被其它方式映射的参数。

     

    2) POJO类型

    可以在控制器方法的形参位置设置一个POJO类型的形参,此时若浏览器传输的请求参数的参数名和实体类中的属性名一致,那么请求参数就会为此属性赋值。

    相应的HTML页面如下:

     

    3) @RequestParam注解

    @RequestParam注解用来指定请求参数和控制器方法形参的映射关系。该注解一共有三个属性:

    注意:

    1. 如果使用java.util.Optional作为方法参数,并且与@RequestParam和@RequestHeader等具有required属性的注解结合使用,该注解等效于required=false。

     

     

    4. 绑定请求头和Cookie

    @CookieValue注解用来指定Cookie数据和控制器方法形参的映射关系,@RequestHeader注解用来指定请求头信息与控制器方法形参的映射关系,其常用属性及使用方法和@RequestParam注解类似。

     

     

    5. 绑定路径变量

    路径变量指在请求路径中通过{xxx}占位符声明的变量,常用于RESTful风格的请求中,可通过@PathVariable注解进行绑定。

    特别的,路径变量还可以使用正则表达式来进行路径匹配。

     

     

    6. 绑定矩阵变量(了解)

    矩阵变量(Matrix Variable);开头,多个值之间用逗号隔开,SpringMVC中可通过设置<mvc:annotation-driven enable-matrix-variables="true"/>removeSemicolonContent=false来开启矩阵变量功能。

     

     

    7. 绑定请求体(重要)

    1) @RequestBody注解

    @RequestBody注解用来将请求体内容绑定到控制器方法的形参。请求体的格式默认为&拼接的字符串,无需消息转换器,可直接使用String类型形参接收。

    如下页面,发送POST请求时,打印请求体的值:name=zhangsan&money=123

    注意:

    1. 如果请求体不存在(如GET请求方式)或形参类型不匹配(非String类型),则响应400错误。

     

    2) 请求消息转换

    如果请求体为JSON/XML等特殊字符串格式,除了使用String类型形参接收外,还可以通过HttpMessageConverter绑定到相应的实体类。

    首先引入Jackson的依赖如下:

    然后通过开启MVC注解驱动来自动配置MappingJackson2HttpMessageConverter

    则可以编写控制器方法如下:

    使用Postman发送一个POST请求如下,请求体格式为JSON字符串,形参为Account类型,实现自动绑定。

    image-20220816155727126

    注意:

    1. 如果请求体格式为&拼接的字符串,则消息转换器不能将其绑定到POJO类,需要使用前面所述的请求参数绑定方式。

    扩展:使用Jquery来发送POST请求示例如下:

     

    3) RequestEntity

    RequestEntity<T>类可作为控制器方法的形参,封装请求头和转换后的请求体,方便后续使用。

    前端通过POST方式请求http://localhost:8080/message/req2,打印信息如下:

    注意

    1. 使用 RequestEntity 作为形参时,也可以没有请求体,封装后的请求体数据为null。

     

     

    8. 请求参数乱码问题

    1) POST请求参数乱码

    如果POST请求参数出现乱码,可在web.xml中配置SpringMVC提供的编码过滤器CharacterEncodingFilter

    注意

    1. CharacterEncodingFilter一定要配置到其他过滤器之前,否则可能无效!

     

    2) Get请求乱码

    Tomcat8.0之前GET请求也可能会出现乱码问题,需修改Tomcat的server.xml配置文件useBodyEncodingForURI属性为true。

    如果遇到Ajax请求仍然乱码,则把useBodyEncodingForURI="true"改为URIEncoding="UTF-8"

     

     

    9. 参数验证

    若类路径存在Hibernate Validator或类似实现,则在全局注册LocalValidatorFactoryBean,以支持@Vaild注解和Validated接口。

    你可以通过如下方式来配置全局Validator实例

    也可注册控制器私有的Validator实例

    类似的XML配置如下:

     

     

    第四节 域数据共享

    1. Request域数据共享

    1) 通过HttpServletRequest共享

    HttpServletRequest作为控制器方法的形参,框架在调用时会自动注入为当前请求对象,通过该对象即可在Request域共享数据。

     

    2) 通过Model形参共享

     

    3) 通过Map/ModelMap形参共享

     

    4) 通过响应ModelAndView共享

    ModelAndView有Model和View的功能,Model主要用于向请求域共享数据View主要用于设置视图,实现页面跳转

     

    5) 通过@ModelAttribute共享(了解)

    @ModelAttribute注解用来进行Request域数据共享,可以作用于普通方法控制器方法参数之上。

     

    6) @RequestAttribute(了解)

    @RequestAttribute属性可以从Request域获取数据并映射到控制器方法参数。

     

     

    2. Session域数据共享

    1) 通过HttpSession共享

    HttpSession作为控制器方法的形参,框架在调用时会自动注入为当前会话对象,通过该对象即可在Session域共享数据等。

     

    2) 通过@SessionAttributes共享(了解)

    @SessionAttributes注解可作用于控制器所在类上,其从Request域查找配置的属性,并共享到Session域。

    @SessionAttribute注解可从Session域获取数据并映射到控制器方法参数。

     

     

    3. Application域数据共享

    获取会话对象(或请求对象)后,通过该对象可获取ServletContext对象,即可在Application域共享数据等。

     

     

    第五节 结果响应

    1. 常用返回类型和注解

    控制器方法常用的返回值类型如下:

    返回值相关的注解如下:

     

    2. 直接响应结果

    HttpServletRequestHttpServletResponse作为控制器方法的形参,框架在调用时会自动注入为当前请求对象或响应对象,此时可以进行请求转发重定向直接发送响应数据等。

     

     

    3. 请求转发或重定向

    1) 通过forward进行请求转发

    当控制器方法设置的视图名称forward:为前缀时,创建InternalResourceView视图,然后直接通过:后面的路径进行转发,而不经过视图解析器拼接视图前后缀等过程。

    注意:

    1. 使用Forward进行转发时,写转发路径而非视图名称,相当于 request.getRequestDispatcher("url").forward(request,response)

    2. 转发路径以/开头时,则是基于Servlet上下文的绝对路径,否则是相对于当前请求的相对路径。

    3. 可以转发到其它的控制器方法或JSP视图页面,但不能够转发到其它服务器

    4. 当工程引入了JSTL模板库的依赖时,转发视图会自动转换为JstlView

     

    2) 通过redirect进行重定向

    当控制器方法设置的视图名称redirect:为前缀时,创建RedirectView视图,然后直接通过:后面的路径进行重定向,也不经过视图解析器拼接视图前后缀等过程。

    注意:

    1. 使用Redirect进行重定向时,写新路径而非视图名称,相当于response.sendRedirect(url)

    2. 同样的,路径以/开头时,则是基于Servlet上下文的绝对路径,否则是相对于当前请求的相对路径。

    3. 可以重定向到任何可访问URL资源,但WEB-INF目录下的资源除外

     

    3) 重定向数据共享(了解)

    重定向时默认传递所有的Model属性,可以通过RequestMappingHandlerAdapter的ignoreDefaultModelOnRedirect标志来控制。

    如果属性为String类型,则直接拼接在URL之后。如果非String类型,则通过RedirectAttributes的实现类RedirectAttributesModelMap进行传递。RedirectAttributesModelMap有一个flashAttributes属性,在重定向之前(通常在会话中)被临时保存,在重定向之后可供请求使用。

    在每个请求上,都有一个具有从上一个请求(如果有)传递的属性的输入FlashMap,和一个具有为后续请求保存的属性的输出FlashMap,通过RequestContextUtils中的getInputFlashMap/getOutputFlashMap方法,可以从Spring MVC中的任何位置访问这两个FlashMap实例。

     

     

    4. 响应视图页面

    当控制器方法设置的视图名称没有任何前缀时,则由配置的视图解析器按优先级(order属性配置)进行解析,它会将视图名称拼接前后缀,然后以转发的方式实现跳转。

     

    1) 设置视图名称

    在控制器方法结束时,可通过返回StringModelAndView的形式设置视图名称。

    这里也可以直接返回视图的绝对路径,如/WEB-INF/pages/success.jsp,但一般会结合视图解析器使用,只需返回视图名称即可。

     

    2) 默认视图解析器(JSP)

    SpringMVC提供了一个默认视图解析器InternalResourceViewResolver可用于解析JSP视图

    JSP视图的简单示例如下,文件路径为src\main\webapp\WEB-INF\pages\success.jsp

     

    3) 响应Thymeleaf视图

    首先需要引入Thymeleaf视图的相关依赖。

    然后在spring-mvc-config.xml文件中配置Thymeleaf视图解析器,并设置视图前缀视图后缀优先级等关键属性。

    Thymeleaf视图的简单示例如下,文件路径为src\main\webapp\WEB-INF\templates\success.html

     

    4) 响应FreeMarker视图页面

    首先也是引入FreeMarker视图的相关依赖。

    然后在spring-mvc-config.xml文件中配置FreeMarker视图解析器和相关配置类,并设置视图前缀视图后缀优先级等关键属性。

    FreeMarker视图的简单示例如下,文件路径为src\main\webapp\WEB-INF\templates\success.ftl

     

    5) view-controller

    当控制器方法中,仅仅用来实现页面跳转,即只需要设置视图名称时,可以将处理器方法使用view-controller标签进行表示。

    注意:

    1. 当存在view-controller时,控制器方法中的请求映射将全部失效,此时需开启MVC注解驱动:<mvc:annotation-driven/>

     

     

    5. 响应实体类(重要)

    1) @ResponseBody注解

    @ResponseBody注解用来将返回值作为为响应体返回,但由于响应体一般为字符串类型,因此默认只能转换String类型的返回值。

    提示

    1. @ResponseBody注解也可以配置在上,等效于类中的所有控制器方法都加上该注解。

    2. SpringMVC提供了一个@ResponseBody和@Controller的组合注解@RestController,简化注解使用。

     

    2) 响应消息转换

    为了能够转换其它类型的返回值作为响应体,需要引入JSON/XML依赖,并配置相关的HttpMessageConverter

    引入Jackson的依赖如下:

    并通过开启MVC注解驱动来自动配置MappingJackson2HttpMessageConverter

    然后就可以响应不同类型的实体数据了。

     

    3) ResponseEntity

    ResponseEntity可作为控制器方法的返回值类型,它可以设置响应头响应体信息,并经消息转换器转换后响应给浏览器。

     

     

    第六节 文件上传和下载

    1. 文件上传

    1) 前端表单

    文件上传要求FROM表单的请求方式必须为POST,并且添加属性enctype="multipart/form-data"

    注意

    1. 文件输入框的 name 属性值要求和控制器方法中MultipartFile形参名一致。

     

    2) 后台服务

    首先添加commons-fileupload依赖如下:

    然后在SpringMVC的配置文件中添加multipartResolver配置。

    编写控制器代码如下,SpringMVC中将上传的文件封装到MultipartFile对象中,通过此对象可以获取文件相关信息。

    注意:

    1. 可以使用List<MultipartFile>接收多个同名文件,或使用Map<String, MultipartFile>MultiValueMap<String, MultipartFile>映射多个文件。

    2. MultipartFile可以不直接作为方法的参数,放在POJO类参数的某个属性。

     

     

    2. 文件下载

    文件下载关键是设置一个Content-Disposition请求头,以附件的形式进行下载。

     

     

    3. 文件异地存储

     

     

    第七节 常用MVC组件

    1. MVC组件简介

    SpringMVC通过组件化配置来支持不同的功能,主要的一些组件如下:

    img

    下面是MVC组件的一些内置实现,详情可以参考DispatcherServlet.properties

     

     

    2. 视图解析器

    SpringMVC定义了ViewViewResolver接口,用于解绑控制器与特定的视图技术。

     

    1) 内置视图解析器

    SpringMVC对一些场景内置了ViewResolver,如下:

     

    2) 配置ContentNegotiatingViewResolver

    类似的XML配置如下:

     

    3) 配置其它视图解析器

    请参考“响应视图页面”章节!

     

     

    3. 控制器拦截器

    1) 实现一个拦截器

    拦截器用于拦截Controller方法的执行,需要实现HandlerInterceptor接口,并根据情况来实现其抽象方法。

    注意:

    1. 拦截器只能拦截控制器方法,不能拦截其它请求,如静态资源请求。

    2. 特别的,如果使用@ResponseBody和ResponseEntity,则在控制器方法返回前已将响应写入和提交,因此不能再通过postHandle来处理响应,如"添加额外的响应头"等操作。(不过,你可以通过ResponseBodyAdvice或RequestMappingHandlerAdapter解决!)

     

    2) 配置拦截器

     

    3) 拦截器执行顺序

    如果配置了多个拦截器,则按如下原则来执行拦截器方法:

     

     

    4. 异常处理器

    如果在请求映射或处理期间引发了异常,则交给HandlerExceptionResolver链来处理,通常是返回一个友好的错误响应。

     

    1) 内置异常处理器

    SpringMVC对一些场景内置了HandlerExceptionResolver,如下:

    如果当前HandlerExceptionResolver无法处理引发的异常,请返回null值,交给下一个HandlerExceptionResolver进行处理。

    如果所有的HandlerExceptionResolver都无法处理异常,则映射到Servlet容器的默认错误页面。错误页面可通过下面方式配置。

     

    2) 实现一个异常处理器

    自定义CustomRuntimeException异常和CustomExceptionResolver如下。

     

    3) 配置异常处理器

     

     

    5. 类型转换器

    如果控制器方法的参数既不是HttpServletRquest等特殊对象,也不是String类型,则在映射请求参数时,一般需要进行相应的类型转换。

    如将前端传过来的String类型值映射到Date类型的形参。

     

    1) Formatter

    org.springframework.format.Formatter用于处理String类型任意类型的转换。首先定义一个Formatter如下:

    然后在spring-mvc-config.xml文件中进行配置。

     

    2) Converter

    org.springframework.core.convert.converter.Converter用来处理任意类型之间的映射。首先定义一个Converter如下:

    然后在spring-mvc-config.xml文件中进行配置:

     

    3) PropertyEditor

    java.beans.PropertyEditor也可以用来进行属性转换,使用方式请参考“Spring IOC”相关章节。

     

     

    6. Multipart解析器

    MultipartResolver是一种用于解析Multipart请求(如文件上传)的策略,当收到Content Type为multipart/form-dataPOST请求时,将会把HttpServletRequest包装为MultipartHttpServletRequest

    SpringMVC提供了两种实现方式,一种是基于Commons FileUpload的实现,另一种基于Servlet 3.0 Multipart请求解析。

     

    1) Commons FileUpload

    首先导入commons-fileupload依赖,然后配置CommonsMultipartResolver类,Bean名称固定为multipartResolver

    如何使用Multipart解析器,请参考“文件上传”章节。

     

    2) Servlet 3.0 Multipart

    默认情况下,Servlet 3.0 Multipart功能为关闭状态,可通过web.xml的<multipart-config>标签或如下Java配置进行开启。

    同样的,需要配置Bean名称为multipartResolverStandardServletMultipartResolver

     

     

    第八节 常用MVC注解

    1. 通过注解配置MVC

    1) Servlet容器初始化(web.xml)

    在Servlet3.0环境中,容器会查找javax.servlet.ServletContainerInitializer接口的实现类,并用它来初始化Servlet容器。而Spring的SpringServletContainerInitializer类实现了该接口,将初始化逻辑委托给WebApplicationInitializer的实现类。

     

    为了继续简化Servlet容器的初始化过程,Spring又定义了AbstractAnnotationConfigDispatcherServletInitializer抽象类。

    注意:

    1. 如果您需要进一步自定义DispatcherServlet本身,则可以覆盖createDispatcherServlet方法。

     

    2) SpringMVC配置(spring-mvc-config.xml)

     

    3) Spring配置(spring-config.xml)

     

    4) 编写控制器

    一切配置就绪后,就可以使用之前的方式来编写控制器了。

     

     

    2. @ControllerAdvice

    @ControllerAdvice(@RestControllerAdvice)是针对多个控制器的切面,一般用于配置类型转换器和异常处理器等。

     

    1) @InitBinder

    @InitBinder标注的方法用来初始化WebDataBinder实例,该类实例用于多种场景下的属性转换

    注意:

    1. 如果@InitBinder方法在@ControllerAdvice中定义,默认对所有控制器生效,若在@Controller中定义,则只对当前控制器生效。

    2. @Controller中定义的@InitBinder和@ModelAttribute方法将会在@ControllerAdvice中定义的相应方法之后被调用。

    3. @Controller中定义的@ExceptionHandler方法将会在@ControllerAdvice中定义的相应方法之前被调用。

     

    2) @ExceptionHandler

    @ControllerAdvice是控制器方法的切面,配合@ExceptionHandler注解可以定义异常处理通知。

    提示:

    1. 尽量通过注解的value属性或方法参数指定更精确的异常类型。

    2. 异常处理方法参数支持HandlerMethod、ServletRequest、ServletResponse、HttpSession等大多数在控制器所支持的参数。

    3. 返回值也可以是@ResponseBody、ResponseEntity<B>、String、void、ModelAndView等大多数在控制器所支持的返回值。

     

    3) @ModelAttribute

    该注解不常用,使用方式请参考“域数据共享”章节。

     

     

    第九节 扩展补充

    1. 其它MVC配置

    1) 配置HttpMessageConverter

    HttpMessageConverter用于处理@RequestBody和@ResponseBody注解,对HTTP消息进行序列化和反序列化。

    你可以通过覆盖configureMessageConverters()extendMessageConverters()来自定义HttpMessageConverter。

    类似的XML配置如下:

     

    2) 配置内容协商

    SpringMVC根据如下流程来确定请求的Midea类型:

    扩展名和Content Type的对应关系可通过configureContentNegotiation来配置:

    类似的XML配置如下:

     

    3) 配置路径匹配规则

    您可以自定义路径匹配和URL处理有关的选项。

    类似的XML配置如下:

     

    4) DelegatingWebMvcConfiguration

    DelegatingWebMvcConfiguration为SpringMVC提供了一些默认配置,你可以删除@EnableWebMvc并直接从DelegatingWebMvcConfiguration扩展,而不是实现WebMvcConfigurer。

     

     

    2. 配置CROS

    跨域资源共享(CORS)可以解决Ajax只能同源(协议+域名+端口)使用的限制,使用 SpringMVC 可以非常方便的进行配置。

     

    1) 单独配置

    @CrossOrigin注解用于开启单个控制器的跨域请求支持,如以下示例所示:

    默认情况下,@CrossOrigin允许所有的来源、所有的请求头和所有HTTP请求方式,不启用allowedCredentials,并设置maxAge为30分钟。

     

    2) 全局配置(CorsRegistry)

    默认情况下,全局配置允许所有的来源、所有的请求头和GET、HEAD、POST请求方式,不启用allowedCredentials,maxAge为30分钟。

    类似的XML配置如下:

     

    3) 全局配置(CorsFilter)

     

     

    3. 构建URL(了解)

    1) UriBuilderFactory

    UriBuilderFactoryUriBuilder一起提供了一种可插入的机制,基于共享配置(基本URL、编码首选项和其他详细信息)从URI 模板构建URI。

     

    2) UriComponentsBuilder

    UriComponentsBuilder是UriBuilder的实现类,用于构建复杂的URL请求路径。

     

    3) ServletUriComponentsBuilder

    ServletUriComponentsBuilder也是UriBuilder的实现类,用于构建相对于当前请求的URI。

     

    4) MvcUriComponentsBuilder

    MvcUriComponentsBuilder可以按控制器方法名称来构建URI连接。

     

     

    4. 其它说明

    1) 内置过滤器

    SpringMVC内置了一些过滤器,用来处理一些特殊场景:

     

    2) 控制器动态代理

    对控制器进行动态代理时推荐使用基于子类的动态代理。

    主要注意的是,如果继承了非Spring上下文感知接口(例如InitializingBean,*Aware等)的其它接口,则需要手动指定代理方式。

     

    3) MVC日志

    DEBUG和TRACE日志记录可能会记录敏感信息,因此默认情况下屏蔽请求参数和请求头,开启方式如下:

     

    4) 异步请求

    SpringMVC与Servlet 3.0异步请求处理进行了广泛的集成:

    更多信息请参考官方文档!

     

    5) HTTP缓存

    HTTP缓存可显著提高Web应用程序的性能。 HTTP缓存围绕Cache-Control响应头以及随后的条件请求头(例如Last-Modified和ETag)展开。

     

     

     

    第04章_SpringSecurity

    第一节 Spring Security 简介

    1. 什么是 Spring Security ?

    Spring Security 是一个专注于为 Java 应用程序提供身份认证(Authentication)授权(Authorization)的框架。

     

     

    2. 入门案例

    1) 新建 Maven 工程

     

    2) 主页视图

    在路径 resources/templates 中创建index.html

    注意:

    1. 资源地址使用 @{/资源地址} 引用,以适应不同的 Servlet 上下文路径

     

    3) 主页接口

     

    4) 启动应用测试

    启动 SpringBoot 应用,在浏览器中访问:http://localhost:8080/,当未登录时,会自动跳转到登录页面:http://localhost:8080/login

    默认登录用户为 user,登陆密码可在控制台的启动日志中查看,或者通过 SpringBoot 属性进行修改:

    注意:

    1. 由于默认登录页面在线引用了 github.com 的 bootstrap.min.css 样式,可能会加载缓慢及样式缺失。

     

     

    3. 工作流程

    1) 拦截入口

    Spring Security 底层是通过 Servlet 的 过滤器(Filter) 来拦截请求,并通过 SecurityFilterChain 来组装处理流程:

    image-20241220190250816

     

    2) 凭证比对

    在使用用户名密码进行登录时,默认通过 SecurityFilterChain 中的 UsernamePasswordAuthenticationFilter 进行凭证比对

    image-20241222200106113

    image-20241222211246528

     

     

    第二节 身份认证

    1. 基于内存的身份认证

    入门案例就是基于内存的身份认证,我们可以对 SpringSecurity 提供的 InMemoryUserDetailsManager进行自定义配置和注册:

    注意:

    1. 自定义 UserDetailsService 后,spring.security相关属性配置需自行应用。

     

     

    2. 基于数据库的身份认证

    1) 数据库脚本

     

    2) 数据库相关依赖

     

    3) 数据源配置

     

    4) DAO代码

     

    5) DBUserDetailsManager

    基于数据库的身份认证,需要自定义一个 UserDetailsManager (继承自UserDetailsService),以支持从数据查询用户名和密码等信息:

    注意:

    1. UserDetailsManager中也可以增加删除用户,以及修改用户密码等。

     

    6) Service代码

    注意:

    1. SpringSecurity 默认使用基于自适应单向函数BCryptPasswordEncoder 进行密码加密。

     

    7) Controller代码

     

    8) 测试验证

    启动 SpringBoot 应用,测试如下:

     

     

    3. 自定义配置

    新增配置类 WebSecurityConfig 如下,手动注册一个 SecurityFilterChain 来覆盖默认配置:

    注意:

    1. SpringBoot 项目会根据 spring-security 依赖包自动配置 @EnableWebSecurity 注解。

     

     

    4. 访问当前登录用户

    访问http://localhost:8080/user/info 查询当前登录用户信息,返回 json 数据。

     

     

    5. 自定义登录页(前后端不分离)

    1) 登录页

    新增登录页面 templates/login.html 如下:

     

    2) 登录接口

     

    3) 登录配置

    修改登录配置信息如下:

     

     

    6. 自定义登录页面(前后端分离)

    1) 请求登录(表单)

    image-20241222223447908

     

    2) 登录/登出处理器

    修改登录/登出配置如下:

     

    3) 未认证请求处理

    修改错误处理配置如下:

     

    4) 会话管理

    限制用户只能在 1 处登录如下:

    修改会话管理配置如下:

     

     

    第三节 用户授权

    1. 授权管理

    1) 授权管理简介

    授权管理一般有如下两种模型:

     

    2) 未授权请求处理

    修改错误处理配置如下:

     

     

    2. 基于 URL 的授权

    1) 用户-权限-资源模式

    在配置中开启授权保护如下:

    在加载用户时设置用户权限如下:

    测试结果和结论如下:

    注意:

    1. 用户的权限以及各权限对应的资源一般在数据库配置并加载。

     

    2) 用户-角色-权限-资源模式

    在配置中开启授权保护如下:

    在加载用户时设置用户权限如下:

    此时,只有 ADMIN 角色的用户可以访问 /user/** 资源。

     

     

    3. 基于控制器方法的授权

    1) 开启注解配置

    在配置类上开启基于控制器方法的授权:

     

    2) 设置控制器方法访问权限

     

    3) 给用户授予角色和权限

     

     

    第四节 OAuth2+JWT

    1. OAuth2+JWT简介

    1) OAuth2简介

    OAuth(Open Authorization) 是一个关于授权(authorization)的开放网络协议,允许用户授权第三方应用访问存储在其他服务提供者上的信息,并且不需要将用户名和密码直接提供给第三方应用。

    它包括如下四个角色:

    在这里插入图片描述

     

    2) JWT简介

    JSON Web Tokens(JWT)是一种基于 JSON 的开放标准(RFC 7519),一般被用来在身份提供者服务提供者之间传递被认证的用户身份信息,以便于从资源服务器获取资源。每个JWT都是经过数字签名的,因此可以被验证和信任。

    image-20241224213619710

    JWT通常包含三部分:

     

    3) 其他资料

    【小家思想】通俗易懂版讲解JWT和OAuth2,以及他俩的区别和联系(Token鉴权解决方案)-腾讯云开发者社区-腾讯云

    深入 OAuth2.0 和 JWT_oauth2.0与jwt-CSDN博客

    【鉴权】session、token、jwt、oauth2-支付宝开发者社区

     

     

     

    第05章_SpringData

    第一节 数据源

    1. 什么是数据源?

    数据源(DataSource)是 JDBC 规范的一部分,是通用的连接工厂,Spring 通过数据源获得与数据库的连接。

    注意:

    1. 可以从 JNDI 查找数据源,也可以使用第三方提供的连接池实现来配置自己的数据源。

     

     

    2. 创建数据源

    1) DriverManagerDataSource

    DriverManagerDataSource是一个 Spring 内置的数据源,每次获取时都返回新连接,不对连接进行缓存,仅用于测试目的。

    等效的 XML 配置如下:

    注意

    1. 与 DriverManagerDataSource 类似的还有其实现类SingleConnectionDataSource

    2. 更多关于Spring对javax.sql.DataSource的实现可以查看 SmartDataSource 接口和 AbstractDataSource 抽象类。

     

    2) DBCP DataSource

     

    3) C3P0 DataSource

     

    4) Druid DataSource

     

    5) Hikari DataSource

     

    6) jdbc.properties配置

    下面是jdbc.properties文件的常用配置:

     

     

    3. 初始化数据源

    在容器启动时,如果需要对数据源进行初始化,可以使用 Spring 框架提供的数据源初始化功能。

    注意

    1. 如果您需要更精细的控制,可以直接使用DataSourceInitializer并将其定义为应用程序中的组件。

    2. 如果其它 Bean 在 Spring 容器启动过程中依赖数据源的初始化,则必须注意它们的先后顺序。

     

     

    4. 获取JDBC连接

    在 Spring 应用中,一般通过DataSourceUtils工具类的静态方法来获取 JDBC 连接,这样可以参与到 Spring 管理的事务之中。

    注意

    1. 使用DataSourceUtils获取的 JDBC 连接会自动参与 Spring 的事务管理JdbcTemplate 也是使用此种方式获取连接的。

    2. 如果处于事务上下文中,则会在事务结束时,自动归还连接,否则,需要手动调用 releaseConnection 方法手动归还连接。

    3. 谨慎使用jdbcTemplate.getDataSource().getConnection()方式获取连接,它不参与 Spring 管理的事务,需手动关闭。

     

     

    5. 嵌入式数据库

    1) 嵌入式数据库简介

    嵌入式数据库由于其轻量级的特性,易于配置并且启动时间短,在开发和测试的过程中非常有用。

    注意:

    1. Spring 内置了HSQLH2Derby三种嵌入式数据库,此外,也可以使用扩展 API 来支持新的嵌入式数据库类型。

     

    2) 创建嵌入式数据库

    等效的XML配置如下:

    提示

    1. 默认的数据库类型为EmbeddedDatabaseType.HSQL,你可以通过type属性修改为H2DERBY

    2. 请将generate-name属性设置为true,生成唯一名称,否则在创建嵌入式数据库时会被连接到同名的数据库实例上。

     

    2) 使用嵌入式数据库

    嵌入式数据库的使用和其它 JDBC 数据源的使用方式类似。

    注意

    1. 如果需要在多个测试类中使用,应该将它注册到Spring容器中管理。

     

    3) 扩展嵌入式数据库

    您可以通过两种方式扩展 Spring 嵌入式数据库的支持:

     

     

    第二节 事务管理

    1. 事务管理简介

    1) 什么是事务管理?

    Spring 为不同类型的事务管理提供了统一的访问接口,使用起来更加简便。

    注意:

    1. 事务的四大特性:原子性(Atomicity)、一致性(Consistency)、隔离性(Isolation)、持久性(Durability)。

    2. 事物的隔离级别:读未提交、读已提交(解决幻读)、可重复读(解决不可重复度)、串行化(解决幻读)。

     

    2) 事务传播行为

    事务传播行为指在一个事务中,调用另一个事务方法时,两者事务如何处理的问题。

    事务传播行为当前处于事务中当前不在事务中备注
    PROPAGATION_REQUIRED加入当前事务创建新事务需要在事务运行的场景(最常用)
    PROPAGATION_SUPPORTS加入当前事务非事务执行无论是否有事务都可以运行的场景
    PROPAGATION_REQUIRES_NEW挂起当前事务,创建新事务创建新事务需要在独立事务中运行的场景
    PROPAGATION_MANDATORY加入当前事务直接抛异常需要在外部事务中运行的场景
    PROPAGATION_NOT_SUPPORTED挂起当前事务,以非事务执行非事务执行需要以非事务运行的场景
    PROPAGATION_NEVER直接抛异常非事务执行不能存在外部事务的场景
    PROPAGATION_NESTED创建保存点创建新事务支持嵌套事务的场景

     

     

    2. 相关接口简介

    1) PlatformTransactionManager

    PlatformTransactionManager是 Spring 事务管理的核心接口,它定义了事务管理的基本方法,包括开始、提交、回滚事务等。

    针对 JDBC 数据源的事务管理器实现为DataSourceTransactionManager,它通过连接的autoCommit属性来控制事务的开启和提交。

    类似的XML配置如下:

    注意:

    1. 可以通过DataSourceUtils访问 DataSourceTransactionManager 管理的 JDBC 连接,详情可参考上一小节。

    2. 内置的事务管理器实现类还有 HibernateTransactionManager、JtaTransactionManager 等,分别用于不同的场合。

     

    2) TransactionDefinition

    TransactionDefinition用于描述事务的属性,如事务的隔离级别、传播行为、超时时间、是否只读等。

    常用实现类有DefaultTransactionDefinitionTransactionAttribute,前者方便用户自定义事务的属性,后者一般在下文将会讲到事务拦截器中使用。

    注意

    1. 事务的隔离级别超时时间等参数只有在开启新事务时有效,如果是返回当前活动事务,则不会生效。

    2. 如果设置了数据源不支持的事务配置选项,则会事务管理器会抛出异常(read-only标志除外)。

     

    3) TransactionStatus

    TransactionStatus提供了事务状态的相关信息,例如事务是否已经完成、是否是新建事务等。

     

    4) TransactionInterceptor

    TransactionInterceptor是 Spring 事务管理的核心组件之一,它通过拦截器的方式,将事务管理逻辑应用到业务方法上。

     

     

    3. 基于XML的声明式事务管理

    1) 什么是声明式事务管理?

    声明式事务管理是一种通过XML或注解配置来管理事务的方式,将事务管理逻辑与业务逻辑分离,使得代码更加简洁、易于维护。

     

    2) 事务通知配置

    如下示例,为x.y.service包下的类创建 AOP 代理,并应用事务通知:

    简单测试代码如下:

     

    3) 配置多个事务通知

    可以为不同的 Bean 配置不同的事务通知,

     

    4) 事务通知其它属性

    事务通知常用的属性配置如下:

    属性名描述默认值
    name方法名称(可用*表示任意字符) 
    propagation事务传播行为REQUIRED
    isolation事务隔离级别DEFAULT
    timeout事务超时时间(秒)-1
    read-only事务是否只读false
    rollback-for触发回滚的异常列表,以逗号分隔。 
    no-rollback-for不触发回滚的异常列表,以逗号分隔。 

    注意:

    1. 属性 isolationtimeoutread-only只在REQUIREDREQUIRES_NEW传播行为开启新事务时有效。

    2. 默认情况下,只有RuntimeExceptionError会回滚事务,可通过rollback-forno-rollback-for属性修改。

     

     

    4. 基于注解的声明式事务管理

    1) 开启事务注解支持

    在配置类上加@EnableTransactionManagement注解,即可开启注解事务支持

    等效的XML配置如下:

    @EnableTransactionManagement注解常用属性配置如下:

    注解属性XML属性描述默认值
    不适用transaction-manager事务管理器 bean 名称transactionManager
    modemode代理模式,可选proxyaspectjproxy
    proxyTargetClassproxy-target-class是否使用CGLIB动态代理(仅适用于proxy模式)false
    orderorder事务通知的优先级(优先级越低值越大)Ordered.LOWEST_PRECEDENCE

     

    2) 在类或方法上开启事务

    在类或方法上加@Transactional注解,Spring 在创建 Bean 实例时,会自动进行 AOP 代理,应用事务通知。

    @EnableTransactionManagement注解常用的属性配置如下:

    属性名描述默认值
    value事务管理器bean名称或限定符值transactionManager
    propagation事务传播行为PROPAGATION_REQUIRED
    isolation事务隔离级别ISOLATION_DEFAULT
    timeout事务超时时间。-1表示基础事务系统的默认超时时间或无超时-1
    readOnly事务是否只读false
    rollbackFor/rollbackForClassName触发回滚的异常列表(Class/String类型),以逗号分隔。 
    noRollbackFor/noRollbackForClassName不触发回滚的异常列表(Class/String类型),以逗号分隔。 

    注意

    1. 在多数据源等特殊情况下,可能会配置多个不同的事务管理器,可通过value属性指定事务管理器名称。

    2. 如果指定的事务管理器名称不存在,则会选用默认的事务管理器transactionManager

    3. 暂时无法配置事务的名称,但它一般是全类名.方法名,如:com.example.BusinessService.handlePayment。

     

    3) 制作组合注解

    如果需要在多个方法上重复使用@Transactional的相同配置,则可为其自定义一个组合注解

    组合注解定义好后,就可以在代码中使用了:

     

    4) 注意事项

    使用@Transactional注解时有几点注意事项:

    1. @Transactional 注解只会对 public 方法生效,protected 和 private 方法上的该注解将会被忽略。

    2. 不建议将 @Transactional 注解加在接口上,因为它只适用于基于接口的 AOP 代理,而在创建基于子类的 AOP 代理时无效。

    3. 也不建议将 @Transactional 注解加在父类上,因为子类不会继承该注解。

    4. 只有通过代理对象调用 public 方法才会进行事务控制,通过 this (实际对象)的自调用无法执行事务通知,不进行事务控制。

    5. 不能在未完全初始化时依赖 Spring 提供的事务控制,比如在 @PostConstruct 标注的方法中,这时可能尚未应用事务通知。

    6. 默认情况下,事务管理器只会在 RuntimeExceptionError 时回滚事务,在出现检查型异常时不会回滚事务。

     

     

    5. 编程式事务管理

    编程式事务管理指通过代码来显式控制事务的开启、提交和回滚,可以对事务进行更细粒度的控制,Spring 提供了如下两种使用方式:

     

    1) TransactionTemplate

    TransactionTemplate的使用与JdbcTemplate等模板类似,首先配置一个模板实例

    然后采用回调的方式执行业务代码:

    注意:

    1. TransactionTemplate 不维护任何对话状态,是线程安全的,但是保存了配置信息,针对不同的事务配置,则需要不同的实例。

     

    2) PlatformTransactionManager

    可以直接使用org.springframework.transaction.PlatformTransactionManager接口来进行编程式的事务管理:

     

     

    6. 基于事务的事件监听

    在事务中发布的事件,可以选择在事务提交后(或其它阶段)进行监听和处理,如:事务提交后发送邮件通知等。

     

    1) 在事务中发布事件

     

    2) 基于事务的事件监听

    注意:

    1. 其它可用的监听时机还有:BEFORE_COMMITAFTER_ROLLBACKAFTER_COMPLETION

     

     

    第三节 数据访问

    1. JdbcTemplate

    1) 什么是JdbcTemplate?

    JdbcTemplate是 Spring 操作数据库的基本方式,内部对资源的创建和释放(如打开和关闭连接)以及 JDBC 工作流程的基本任务(如PS的创建和执行)进行了处理等,用户只需提供SQL语句对结果集进行处理即可。

     

    2) 配置Jdbctemplate

    基于数据源配置 Jdbctemplate 的示例如下:

    然后,就可以在业务类中注入使用了:

    注意

    1. JdbcTemplate 与 Spring 的其它模板相似,其会话数据是线程安全的,但是配置信息(如数据库连接信息)是共享的。

    2. 数据访问层尽量使用@Repository注解标识,Spring 后续可能会基于此进行 DataAccessException 的转换。

    3. 使 Dao 类继承JdbcDaoSupport类,也可注入数据源和 JdbcTemplate 实例,然后通过getJdbcTemplate()方法访问。

     

    3) 执行DQL语句

    注意:

    1. 如果列名和字段名一致,可以使用new BeanPropertyRowMapper<>(User.class)简化RowMapper实例。

     

    4) 执行DML语句

    注意:

    1. 在 JdbcTemplate 中,insert、update和 delete 语句都视为更新语句,处理逻辑一致!

    2. 插入时检索自增列值需要 JDBC3.0 驱动支持,并且自定义 PreparedStatement,在第二个字段指定自增列名。

     

    4) 执行DDL语句

     

    5) 批量更新

     

     

    2. NamedParameterJdbcTemplate

    1) 什么是NamedParameterJdbcTemplate?

    NamedParameterJdbcTemplate 是 Spring 框架中对 JdbcTemplate 的扩展,它通过使用命名参数(而不是传统的问号占位符)来简化 JDBC 操作,使 SQL 语句更易读、更易于维护。

     

    2) 配置NamedParameterJdbcTemplate

     

    3) 基本使用

    提示:

    1. 除了上述两个实现类,SqlParameterSource还有一个空实现EmptySqlParameterSource,常用来占位使用。

    2. 可通过getJdbcTemplate()getJdbcOperations()方法访问仅在 JdbcTemplate 类中提供的功能。

     

    4) 支持IN子句

    对于in (1,2,3...)形式的 IN 子句,NamedParameterJdbcTemplate 可以自动拼接 IN 子句的参数,只需传入参数列表即可:

    此外,也支持(id, last_name) in ((1, 'Johnson'), (2, 'Harrop'))形式的 IN 子句,在参数传入对象列表即可。

    注意:

    1. 数据库一般对 IN 中的条目数有限制,列如 Oracle 限制为1000。

     

    5) 批量更新

     

    注意

    1. SqlParameterSourceUtils.createBatch可你帮助你将List<BizObject>转换为SqlParameterSource[]类型!

     

     

    3. SQLExceptionTranslator

    1) 什么是SQLExceptionTranslator?

    SQLExceptionTranslator 用于将数据库异常转换为 Spring 中定义的DataAccessException异常,常用实现类如下:

    注意:

    1. 可通过 jdbcTemplate.setExceptionTranslator(new SQLStateSQLExceptionTranslator()) 来配置不同的 SQL 异常转换器。

     

    2) 异常转换流程

    默认情况下,使用SQLErrorCodeSQLExceptionTranslator进行数据库异常的转换,流程如下:

    Spring 在org.springframework.jdbc.support包下提供了 sql-error-codes.xml 文件的默认配置,部分示例如下:

    注意

    1. 该文件可以被类路径根目录下的同名文件覆盖!

     

    3) 自定义异常转换器

    可以基于SQLErrorCodeSQLExceptionTranslator实现自定义异常转换器

    然后通过JdbcTemplatesetExceptionTranslator方法进行设置:

     

     

    4. 扩展补充

    1) SimpleJdbcXxx

    SimpleJdbcInsertSimpleJdbcUpdateSimpleJdbcCall等通过 JDBC 驱动程序提供数据库元数据来简化用户配置,示例如下:

     

    2) JdbcObject

    org.springframework.jdbc.object中包含一些类,可以以更加面向对象的方式访问数据库,示例如下:

     

    3) 关于NULL参数

    在设置 PreparedStatement 的参数时,一般通过 Java 类型来推断 JDBC 类型,但如果参数值为 null ,则需要调用驱动的getParameterType来获取 JDBC 类型,这是一个比较耗时的操作,因此,对于可能为空的参数,尽量显式设置 JDBC 类型:

    注意:

    1. 可通过spring.jdbc.getParameterType.ignore=true来禁用从驱动获取 JDBC 类型。

     

     

     

    第06章_SpringTest

    第一节 SpringTest简介

    1. SpringTest是什么?

    SpringTest 可以方便的对 Spring 程序进行单元测试集成测试,一般与JunitTestNG等测试工具整合使用。

     

    2. SpringTest整合Junit4

    1) 引入相关依赖

     

    2) 编写业务类

     

    3) 编写测试类

     

     

    3. SpringTest整合Junit5

    1) 引入相关依赖

     

    2) 编写业务类

     

    3) 编写测试类

     

     

    第二节 相关注解简介

    1. 测试环境与属性

    1) @ContextConfiguration

    @ContextConfiguration用于指定应用上下文的配置信息,可以来自XML、Groovy、@Configuration类或 Spring 的其它组件等。

    这些配置可以通过类继承的方式来继承和覆盖:

     

    2) @ActiveProfiles

    @ActiveProfiles用于指定测试时激活的Profile

     

    3) @TestPropertySource

    @TestPropertySource用于配置测试属性文件和优先级更高的内联测试属性,并将其添加到 Environment 中的 PropertySources 集合。

    注意

    1. 该注解配置的属性优先级高于系统环境变量@PropertySource或编程方式等添加的属性,并且支持继承和覆盖。

     

     

    2. 事务相关注解

     

     

    3. 数据操作注解

    @Sql@SqlConfig@SqlGroup用于在测试方法之前或之后执行一些SQL脚本,默认执行的脚本为类目录下的.sql文件,如com.example.MyTest 类的默认脚本为 classpath:com/example/MyTest.sql 。

     

    4. 其它常用注解

     

     

    第三节 扩展补充

    1. 常用测试工具

    SpringTest包中提供了一些测试可能用到的工具类,如: