1.学习编程|Spring源码深度解析 读书笔记 第5章:容器的深入深度功能扩展
2.Spring技术内幕:深入解析Spring架构与设计原理作者简介
3.Spring Security 6.x OAuth2登录认证源码分析
4.头秃了,二十三张图带你从源码了解SpringBoot启动流程!理解
5.76 张图,源码源码剖析 Spring AOP 源码,解析小白居然也能看懂,深入深度大神,理解chuzzle deluxe源码请收下我的源码源码膝盖!
6.Spring源码系列-BeanPostProcessor与BeanFactoryPostProcessor
学习编程|Spring源码深度解析 读书笔记 第5章:容器的解析功能扩展
深入理解Spring容器的扩展功能:学习笔记
作者:牛客网-张学友
在Spring框架中,容器功能的深入深度扩展是其强大和灵活的关键。首先,理解ApplicationContext相较于BeanFactory,源码源码提供了更多功能,解析它是深入深度BeanFactory的子类,包含了其所有功能并有所扩充。理解主要区分点在于ApplicationContext的源码源码启动过程和其特有的扩展功能。
通过`ClassPathXmlApplicationContext`的实例化,开启源码探索之旅。在构造函数和`refresh`方法中,Spring对配置文件解析,并实现了一系列扩展,如环境变量处理、配置文件加载、Spring Expression Language (SPEL)的支持、属性编辑器的注册以及ApplicationContextAwareProcessor的使用等。这些扩展不仅增强了容器的灵活性,还为开发者提供了更丰富的控制选项。
例如,`refresh`方法中包含了初始化准备工作、BeanFactory的获取和定制、XML文件解析、bean定义填充、Spring表达式解析、属性编辑器注册、BeanPostProcessor的处理、依赖处理和国际化功能等。这些步骤体现了Spring框架的高度可扩展性,使得用户可以根据项目需求定制容器行为。
总结来说,Spring容器的功能扩展涉及到了配置文件处理、表达式语言、事件监听、国际化等多个方面,使得开发过程更加便捷且易于定制。想了解更多细节,可以参考作者的原文链接和更多读书笔记资源。
Spring技术内幕:深入解析Spring架构与设计原理作者简介
计文柯,资深软件开发专家和项目经理,拥有余年业界经验,对Spring等开源软件的应用和实现原理有深入研究和独到见解。
产品研发和项目管理经验丰富,曾就职于华为、摩托罗拉等知名企业和硅谷移动互联网创业公司,在软件工程和项目管理方面积累了大量最佳实践。
现与同伴一起创立并运营深圳云果科技,专注于云计算解决方案的研究与实施。
在深入解析Spring架构与设计原理方面,计文柯有着丰富的经验和深入的理解。他能够从技术原理、应用场景以及实践案例等多个角度,全面解析Spring的核心理念和设计思想。
Spring框架以其面向切面编程(AOP)、依赖注入(DI)和声明式事务管理等特性,为开发者提供了高效、灵活且易于维护的解决方案。通过深入理解Spring的架构设计,开发者可以更好地利用其功能,提高代码的可复用性和可测试性。
计文柯在文章中详细阐述了Spring的易付呗源码模块结构、核心组件及其之间的关系,帮助读者掌握Spring框架的整体架构。他进一步解释了Spring的依赖注入机制,如何通过配置文件或注解实现组件的自动装配,以及如何利用AOP进行切面编程以实现面向切面的编程模式。
此外,计文柯还分享了Spring在实际项目中的应用案例,通过具体场景的分析,展示了Spring如何在实际开发中解决实际问题,以及如何通过Spring的特性提高开发效率和代码质量。
综上所述,计文柯的文章深入浅出地剖析了Spring架构与设计原理,不仅为开发者提供了理论指导,还通过实践案例展示了Spring在实际开发中的应用,对于希望深入了解和掌握Spring技术的开发者来说,具有极高的参考价值。
Spring Security 6.x OAuth2登录认证源码分析
本文深入剖析了Spring Security框架中OAuth2客户端登录认证的实现源码。首先,我们介绍OAuth2协议的基本概念,它旨在解决互联网安全信任问题,允许第三方合法、安全地访问受保护资源,如用户无卡账户。通过例子解释了如何通过注册第三方、提供授权交互、以及发放交易凭证来实现这一过程。接着,详细阐述了OAuth2协议如何获取访问令牌(accessToken),包括常见的授权模式。
在Spring Security中,OAuth2客户端配置主要涉及添加`SecurityFilterChain`的`oauth2Login`配置项和在`application.yaml`文件中注册客户端。默认配置下,添加`oauth2Login`即可启用过滤器链,包含`OAuth2AuthorizationRequestRedirectFilter`和`OAuth2LoginAuthenticationFilter`。这些过滤器分别处理授权请求和认证过程。在配置文件中注册客户端时,主要配置`client-id`和`client-secret`,这些参数需要在OAuth2服务端注册应用后获取。
`OAuth2AuthorizationRequestRedirectFilter`负责向OAuth2服务端发起认证请求,创建一个重定向到服务端获取code的地址。而`OAuth2LoginAuthenticationFilter`则处理认证过程,通过解析来自服务端的响应生成认证对象。认证过程中,通过`OAuth2AuthorizationExchange`对象以及`ClientRegistration`信息,调用`OAuth2LoginAuthenticationProvider`请求获取访问令牌和用户信息,最终封装成`OAuth2LoginAuthenticationToken`返回。
`OAuth2LoginAuthenticationProvider`扮演关键角色,负责请求服务端获取访问令牌,并获取用户信息以访问受保护资源。在获取用户信息过程中,首先通过`OAuth2AuthorizationRequest`对象获取原始授权请求,然后通过`OAuth2AuthorizationResponse`获取服务端重定向回来的响应,包括state参数。最后,这些信息被封装到`OAuth2LoginAuthenticationToken`中,用于后续认证操作。
在访问受保护资源时,通过`OAuth2UserService`实例请求OAuth2服务端的用户信息相关端点。关键代码在`OAuth2UserRequestEntityConverter#convert`方法中,用于生成请求对象,允许携带访问令牌访问资源。
本文提供了Spring Security框架内OAuth2客户端源码的详细分析,旨在为扩展对接其他OAuth2服务端提供参考。通过理解这些源码,开发者可以更好地集成第三方认证服务,实现更安全、灵活的个人发卡系统源码登录认证机制。
此外,附录部分提供了在GitHub中注册OAuth2客户端的详细步骤,包括注册地址、填写表单、查看客户端信息、以及访问官方文档。遵循这些步骤,开发者可以成功在GitHub上注册自己的OAuth客户端,为应用集成GitHub API做好准备。
头秃了,二十三张图带你从源码了解SpringBoot启动流程!
源码版本
作者使用的是Spring Boot的2.4.0版本。不同版本的Spring Boot可能存在差异,建议读者与作者保持一致,以确保源码的一致性。
从哪入手
Spring Boot源码的研究起点是主启动类,即标注着`@SpringBootApplication`注解并且包含`main()`方法的类。这是Spring Boot启动的核心。
源码如何切分
SpringApplication中的静态`run()`方法是一个复杂的流程,它分为两步:创建`SpringApplication`对象和执行`run()`方法。接下来将分别介绍这两部分。
如何创建`SpringApplication`
创建`SpringApplication`的过程本质上是一个对象的生成,通过调试追踪,最终调用的构造方法如图所示。创建过程主要涉及三个阶段,我们将逐一进行深入。
设置应用类型
创建过程中的重要步骤是确定应用类型,这将直接影响项目的性质,如Web应用或非Web应用。应用类型由WebApplicationType枚举类决定,加载特定类(如DispatcherServlet)来判断。
设置初始化器
初始化器(ApplicationContextInitializer)用于在IOC容器刷新之前进行初始化操作,例如ServletContextApplicationContextInitializer。获取初始化器的方式是从SpringApplication中的方法调用开始的,最终通过`#SpringFactoriesLoader.loadSpringFactories()`方法从类路径加载。
设置监听器
监听器(ApplicationListener)负责监听特定的事件(如IOC容器刷新或关闭)。在Spring Boot中,使用SpringApplicationEvent事件来扩展监听器概念,主要在启动过程中触发。获取监听器的方式与初始化器相同,从spring.factories文件中加载。
总结
SpringApplication的构建为`run()`方法的执行铺平了道路,关键步骤包括设置应用类型、初始化器和监听器。注意,初始化器和监听器需要在spring.factories文件中声明,才能在构建过程中加载,此时IOC容器尚未创建,即使注入到容器中也不会生效。
执行`run()`方法
在构建结束后,到了启动的阶段,`run()`方法将执行一系列操作,分为八个步骤进行详细解析。
步骤1:获取并启动运行过程监听器
SpringApplicationRunListener监听器用于监听应用程序的启动过程,通过调用方法从spring.factories文件中获取运行监听器实例,并执行特定事件的广播。
步骤2:环境构建
构建过程包括加载系统和自定义配置(如application.properties),并广播事件通知监听器。
步骤3:创建IOC容器
执行容器创建过程,根据应用类型选择容器类型,此步骤仅创建容器,未进行其他操作。
步骤4:IOC容器的前置处理
这一步是容器刷新前的准备工作,关键操作是将主启动类注入容器,为后续自动化配置奠定基础。
步骤5:调用初始化器
执行构建过程中设置的初始化器,加载自定义的爱玲影视源码初始化器实现。
步骤6:加载启动类,注入容器
将主启动类加载到IOC容器中,作为自动配置的入口。
步骤7:两次事件广播
这一步涉及两次事件广播,包括ApplicationContextInitializedEvent和ApplicationPreparedEvent。
步骤8:刷新容器
容器刷新由Spring框架完成,包括资源初始化、上下文广播器等。
步骤9:IOC容器的后置处理
这一步是容器刷新后的扩展操作,通常用于打印结束日志等。
步骤:发出结束执行的事件
使用EventPublishingRunListener广播ApplicationStartedEvent事件,允许在IOC容器中注入的监听器响应。
步骤:执行Runners
Spring Boot提供了两种Runner,即CommandLineRunner和ApplicationRunner,用于定制额外操作。
总结
Spring Boot启动流程相对简洁,通过八个步骤详细描述了从创建到执行的整个过程。理解run()方法的执行流程、事件、初始化器和监听器的执行时间点是关键。
张图,剖析 Spring AOP 源码,小白居然也能看懂,大神,请收下我的膝盖!
本文将简要介绍AOP(面向切面编程)的基础知识与使用方法,并深入剖析Spring AOP源码。首先,我们需要理解AOP的基本概念。
1. **基础知识
**1.1 **什么是AOP?
**AOP全称为Aspect Oriented Programming,即面向切面编程。AOP的思想中,周边功能(如性能统计、日志记录、事务管理等)被定义为切面,核心功能与切面功能独立开发,然后将两者“编织”在一起,这就是AOP的核心。
AOP能够将与业务无关、却为业务模块共同调用的逻辑封装,减少系统重复代码,降低模块间的耦合度,有利于系统的可扩展性和可维护性。
1.2 **AOP基础概念
**解释较为官方,以下用“方言”解释:AOP包括五种通知分类。
1.3 **AOP简单示例
**创建`Louzai`类,添加`LouzaiAspect`切面,并在`applicationContext.xml`中配置。程序入口处添加`"睡觉"`方法并添加前置和后置通知。接下来,我们将探讨Spring内部如何实现这一过程。
1.4 **Spring AOP工作流程
**为了便于理解后面的源码,我们将整体介绍源码执行流程。整个Spring AOP源码分为三块,结合示例进行讲解。
第一块是前置处理,创建`Louzai`Bean前,遍历所有切面信息并存储在缓存中。第二块是后置处理,创建`Louzai`Bean时,主要处理两件事。第三块是执行切面,通过“责任链+递归”执行切面。
2. **源码解读
**注意:Spring版本为5.2..RELEASE,否则代码可能不同!这里,我们将从原理部分开始,猎人手游源码逐步深入源码。
2.1 **代码入口
**从`getBean()`函数开始,进入创建Bean的逻辑。
2.2 **前置处理
**主要任务是遍历切面信息并存储。
这是重点!请务必注意!获取切面信息流程结束,后续操作都从缓存`advisorsCache`获取。
2.2.1 **判断是否为切面
**执行逻辑为:判断是否包含切面信息。
2.2.2 **获取切面列表
**进入`getAdvice()`,生成切面信息。
2.3 **后置处理
**主要从缓存拿切面,与`Louzai`方法匹配,创建AOP代理对象。
进入`doCreateBean()`,执行后续逻辑。
2.3.1 **获取切面
**首先,查看如何获取`Louzai`的切面列表。
进入`buildAspectJAdvisors()`,方法用于存储切面信息至缓存`advisorsCache`。随后回到`findEligibleAdvisors()`,从缓存获取所有切面信息。
2.3.2 **创建代理对象
**有了`Louzai`的切面列表,开始创建AOP代理对象。
这是重点!请仔细阅读!这里有两种创建AOP代理对象方式,我们选择使用Cglib。
2.4 **切面执行
**通过“责任链+递归”执行切面与方法。
这部分逻辑非常复杂!接下来是“执行切面”最核心的逻辑,简述设计思路。
2.4.1 **第一次递归
**数组第一个对象执行`invoke()`,参数为`CglibMethodInvocation`。
执行完毕后,继续执行`CglibMethodInvocation`的`process()`。
2.4.2 **第二次递归
**数组第二个对象执行`invoke()`。
2.4.3 **第三次递归
**数组第三个对象执行`invoke()`。
执行完毕,退出递归,查看`invokeJoinpoint()`执行逻辑,即执行主方法。回到第三次递归入口,继续执行后续切面。
切面执行逻辑已演示,直接查看执行方法。
流程结束时,依次退出递归。
2.4.4 **设计思路
**这部分代码研究了大半天,因为这里不是纯粹的责任链模式。
纯粹的责任链模式中,对象内部有一个自身的`next`对象,执行当前对象方法后,启动`next`对象执行,直至最后一个`next`对象执行完毕,或中途因条件中断执行,责任链退出。
这里`CglibMethodInvocation`对象内部无`next`对象,通过`interceptorsAndDynamicMethodMatchers`数组控制执行顺序,依次执行数组中的对象,直至最后一个对象执行完毕,责任链退出。
这属于责任链,实现方式不同,后续会详细剖析。下面讨论类之间的关系。
主对象为`CglibMethodInvocation`,继承于`ReflectiveMethodInvocation`,`process()`的核心逻辑在`ReflectiveMethodInvocation`中。
`ReflectiveMethodInvocation`的`process()`控制整个责任链的执行。
`ReflectiveMethodInvocation`的`process()`方法中,包含一个长度为3的数组`interceptorsAndDynamicMethodMatchers`,存储了3个对象,分别为`ExposeInvocationInterceptor`、`MethodBeforeAdviceInterceptor`、`AfterReturningAdviceInterceptor`。
注意!这3个对象都继承了`MethodInterceptor`接口。
每次`invoke()`调用时,都会执行`CglibMethodInvocation`的`process()`。
是否有些困惑?别着急,我将再次帮你梳理。
对象与方法的关系:
可能有同学疑惑,`invoke()`的参数为`MethodInvocation`,没错!但`CglibMethodInvocation`也继承了`MethodInvocation`,可自行查看。
执行逻辑:
设计巧妙之处在于,纯粹的责任链模式中,`next`对象需要保证类型一致。但这里3个对象内部没有`next`成员,不能直接使用责任链模式。怎么办呢?就单独设计了`CglibMethodInvocation.process()`,通过无限递归`process()`实现责任链逻辑。
这就是我们为什么要研究源码,学习优秀的设计思路!
3. **总结
**本文首先介绍了AOP的基本概念与原理,通过示例展示了AOP的应用。之后深入剖析了Spring AOP源码,分为三部分。
本文是Spring源码解析的第三篇,感觉是难度较大的一篇。图解代码花费了6个小时,整个过程都沉浸在代码的解析中。
难度不在于抠图,而是“切面执行”的设计思路,即使流程能走通,将设计思想总结并清晰表达给读者,需要极大的耐心与理解能力。
今天的源码解析到此结束,有关Spring源码的学习,大家还想了解哪些内容,欢迎留言给楼仔。
Spring源码系列-BeanPostProcessor与BeanFactoryPostProcessor
在Spring框架中,BeanPostProcessor与BeanFactoryPostProcessor各自承担着不同的职责,它们在IoC容器的工作流程中起着关键作用。
BeanFactoryPostProcessor作用于BeanDefinition阶段,对容器中Bean的定义进行处理。这个过程发生在BeanFactory初始化时,对BeanDefinition进行修改或增强,提供了一种在不修改源代码的情况下定制Bean的机制。相比之下,BeanPostProcessor则在Bean实例化之后生效,对已经创建的Bean对象进行进一步处理或替换,提供了更晚、更灵活的扩展点。
以制造杯子为例,BeanFactoryPostProcessor相当于在选择材料和形状阶段进行定制,而BeanPostProcessor则在杯子制造完成后,进行诸如加花纹、抛光等深加工。
在Spring框架中,BeanPostProcessor的使用场景较为广泛,尤其在实现AOP(面向切面编程)时,通过使用代理类替换原始Bean,实现如日志记录、事务管理等功能。
此外,容器在启动后,还会进行消息源初始化、广播器初始化及监听器初始化,为Bean实例化做好准备。完成这些准备工作后,容器会调用registerBeanPostProcessors方法注册BeanPostProcessor,对已创建的Bean进行进一步处理。同时,初始化消息源、广播器和监听器,为后续事件处理做好基础。
总结,BeanFactoryPostProcessor与BeanPostProcessor在Spring IoC容器中的作用各有侧重。前者侧重于对BeanDefinition的定制,后者则是在Bean实例化后的进一步加工,两者共同为构建灵活、可扩展的IoC容器提供了强大的支持。
在深入分析Spring框架的源码时,我们发现refresh()方法的实现中包含了对BeanFactoryPostProcessor和BeanPostProcessor的注册与处理。这些处理步骤确保了容器能够在启动时对Bean进行正确的配置和初始化。
文章中通过一个例子展示了如何使用BeanFactoryPostProcessor替换已注册Bean的实现,以及对其源码的分析。通过例子和源码的结合,读者能够更直观地理解这些后置处理器在Spring框架中的应用和工作原理。
Spring容器之refresh方法源码分析
Spring容器的核心接口BeanFactory与ApplicationContext之间的关系是继承,ApplicationContext扩展了BeanFactory的功能,提供了初始化环境、参数、后处理器、事件处理以及单例bean初始化等更全面的服务,其中refresh方法是Spring应用启动的入口点,负责整个上下文的准备工作。 让我们深入分析AbstractApplicationContext#refresh方法在启动过程中的具体操作:准备刷新阶段: 包括系统属性和环境变量的检查和准备。
获取新的BeanFactory: 初始化并解析XML配置文件。
customizeBeanFactory: 个性化BeanFactory设置,如覆盖定义、处理循环依赖等。
loadBeanDefinitions: 通过解析XML文件,创建BeanDefinition对象并注入到容器中。
填充BeanFactory功能: 设置classLoader、表达式语言处理器,增强Aware接口处理,添加AspectJ支持和默认系统环境bean等。
激活BeanFactory后处理器: 分为BeanDefinitionRegistryPostProcessor和BeanFactoryPostProcessor,分别进行BeanDefinition注册和BeanFactory增强。
注册BeanPostProcessors: 拦截Bean创建的后处理器,按优先级注册。
初始化其他组件: 包括MessageSource、ApplicationEventMulticaster和监听器。
初始化非惰性单例: 预先实例化这些对象。
刷新完成: 通知生命周期处理器并触发ContextRefreshedEvent。
以上是refresh方法在Spring应用启动流程中的关键步骤。以上内容仅为个人理解,如需更多信息,可参考CSDN博客链接。深入浅出Spring原理及实战「IOC容器初始化」彻底让你明白和理解运行原理和源码流程
深入浅出Spring原理及实战
本文旨在揭示Spring框架中核心组件——IOC容器的初始化流程,帮助读者全面理解其运行机制和源码细节。
理解容器初始化流程是掌握Spring框架基础的关键。本文将从构造器分析、重要方法解析、容器创建与配置、Bean实例化等多个角度,为您呈现Spring IOC容器从无到有的全过程。
首先,让我们聚焦于容器初始化的启动点:构造器分析。在初始化过程中,构造器扮演着注册内部Spring容器关键组件的角色。通过创建一个用于读取内部Spring容器内部Bean对象的AnnotatedBeanDefinitionReader,为后续的初始化工作打下基础。
构造器中的this()方法,标志着初始化流程的初步完成。这一阶段的主要工作是注册Spring内部核心组件,并通过register(componentClasses)方法实现这一目标。接着,刷新方法refresh()被调用,这是整个容器初始化的核心步骤。
刷新方法内部包含了创建容器前的准备工作,例如创建Bean容器并加载注册Bean IoC初始化的关键部分。这一阶段中,prepareRefresh()方法负责创建容器前的必要配置,而obtainFreshBeanFactory()方法则是创建并加载Bean容器的关键步骤。
在容器初始化过程中,AbstractApplicationContext#obtainFreshBeanFactory()和AbstractRefreshableApplicationContext#refreshBeanFactory()方法扮演着核心角色,分别创建和初始化BeanFactory。通过这些方法,我们能够理解ApplicationContext与BeanFactory之间的紧密关系,以及ApplicationContext如何委托BeanFactory完成实际的Bean操作。
接下来,本文将深入探讨BeanFactory相关操作的实现,包括customizeBeanFactory方法在配置文件中处理BeanDefinition的覆盖问题。在处理重复定义时,开发者需注意允许覆盖的默认设置,以避免潜在的错误。
最后,本文将带领读者回顾从配置到实例化的整个流程。loadBeanDefinitions方法负责加载BeanDefinition,而ClassPathXmlApplicationContext则通过XmlBeanDefinitionReader按照XML解析方式加载BeanDefinitions。随后,通过一系列调用,最终实现Bean实例化过程,完成Spring IOC容器的核心功能。
本文旨在通过深入分析Spring框架中的关键组件和流程,为读者提供一个全面而直观的理解框架。虽然本文未能详细覆盖所有细节,但通过整体介绍框架的总体深入流程原理,为读者铺平了学习Spring的基础之路。
这篇文章让你搞懂 SpringMVC 国际化!
松哥之前写过 Spring Boot 国际化的问题,不过那一次没讲源码,这次咱们整点源码来深入理解下这个问题。
国际化,也叫 in,为啥叫这个名字呢?因为国际化英文是 internationalization ,在 i 和 n 之间有 个字母,所以叫 in。我们的应用如果做了国际化就可以在不同的语言环境下,方便的进行切换,最常见的就是中文和英文之间的切换,国际化这个功能也是相当的常见。
1. SpringMVC 国际化配置
先来说说用法,再来说源码,这样大家不容易犯迷糊。我们先说在 SSM 中如何处理国际化问题。
首先国际化我们可能有两种需求:一种是通过请求头来判断当前的语言环境,另一种是通过请求参数来传递语言环境。接下来松哥通过一个简单的用法来和大家演示下具体玩法。
在项目的 resources 目录下新建语言文件,分别对应英中文环境,配置文件写好之后,还需要在 SpringMVC 容器中提供一个 ResourceBundleMessageSource 实例去加载这两个实例,然后在控制器中返回 login 视图即可。配置完成后,启动项目进行测试。
默认情况下,系统是根据请求头的中 Accept-Language 字段来判断当前的语言环境的。为了测试方便,可以使用 POSTMAN 进行测试,然后手动设置 Accept_Language 字段。测试中文和英文环境,都没问题,完美!同时观察 IDEA 控制台,也能正确打印出语言文字。
2. Spring Boot 国际化配置
Spring Boot 和 Spring 一脉相承,对于国际化的支持,默认是通过 AcceptHeaderLocaleResolver 解析器来完成的。所以在 Spring Boot 中做国际化,这一块我们可以不用配置,直接就开搞。
创建一个普通的 Spring Boot 项目,添加 web 依赖即可。配置完成后,我们就可以直接开始使用了。在需要使用值的地方,直接注入 MessageSource 实例即可。在接口调用时,通过请求头的 Accept-Language 来配置当前的环境,我这里通过 POSTMAN 来进行测试,结果如下:默认情况下,系统是根据请求头的 Accept-Language 字段来判断当前的语言环境的。通过 POSTMAN 来设置 Accept-Language 的值,然后调用接口,就可以看到返回的语言文字。
2.2 自定义切换
有的小伙伴觉得切换参数放在请求头里边好像不太方便,那么也可以自定义解析方式。例如参数可以当成普通参数放在地址栏上,通过如下配置可以实现我们的需求。在配置中,我们首先提供了一个 SessionLocaleResolver 实例,这个实例会替换掉默认的 AcceptHeaderLocaleResolver,不同于 AcceptHeaderLocaleResolver 通过请求头来判断当前的环境信息,SessionLocaleResolver 将客户端的 Locale 保存到 HttpSession 对象中,并且可以进行修改(这意味着当前环境信息,前端给浏览器发送一次即可记住,只要 session 有效,浏览器就不必再次告诉服务端当前的环境信息)。
好了,配置完成后,启动项目,访问方式如下:通过在请求中添加 lang 来指定当前环境信息。这个指定只需要一次即可,也就是说,在 session 不变的情况下,下次请求可以不必带上 lang 参数,服务端已经知道当前的环境信息了。
2.3 其他自定义
默认情况下,我们的配置文件放在 resources 目录下,如果大家想自定义,也是可以的,例如定义在 resources/in 目录下。但是这种定义方式系统就不知道去哪里加载配置文件了,此时还需要 application.properties 中进行额外配置(注意这是一个相对路径)。另外还有一些编码格式的配置等,内容如下:spring.messages.cache-duration 表示 messages 文件的缓存失效时间,如果不配置则缓存一直有效。spring.messages.fallback-to-system-locale 属性则略显神奇,网上竟然看不到一个明确的答案,后来翻了一会源码才看出端倪。这个属性的作用在org.springframework.context.support.AbstractResourceBasedMessageSource#getDefaultLocale 方法中生效:从这段代码可以看出,在找不到当前系统对应的资源文件时,如果该属性为 true,则会默认查找当前系统对应的资源文件,否则就返回 null,返回 null 之后,最终又会调用到系统默认的 messages.properties 文件。
3. LocaleResolver
国际化这块主要涉及到的组件是 LocaleResolver,这是一个开放的接口,官方默认提供了四个实现。当前该使用什么环境,主要是通过 LocaleResolver 来进行解析的。
我们来看看 LocaleResolver 的继承关系:虽然中间有几个抽象类,不过最终负责实现的其实就四个:AcceptHeaderLocaleResolver、SessionLocaleResolver、FixedLocaleResolver、CookieLocaleResolver。接下来我们就对这几个类逐一进行分析。
3.1 AcceptHeaderLocaleResolver
AcceptHeaderLocaleResolver 直接实现了 LocaleResolver 接口,我们来看它的 resolveLocale 方法:再来看看它的 setLocale 方法,直接抛出异常,意味着通过请求头处理 Locale 是不允许修改的。
3.2 SessionLocaleResolver
SessionLocaleResolver 的实现多了一个抽象类 AbstractLocaleContextResolver,AbstractLocaleContextResolver 中增加了对 TimeZone 的支持。我们先来看下 AbstractLocaleContextResolver:可以看到,多了一个 TimeZone 属性。从请求中解析出 Locale 还是调用了 resolveLocaleContext 方法,该方法在子类中被实现,另外调用 setLocaleContext 方法设置 Locale,该方法的实现也在子类中。
我们来看下它的子类 SessionLocaleResolver:直接从 Session 中获取 Locale,默认的属性名是SessionLocaleResolver.class.getName() + ".LOCALE",如果 session 中不存在 Locale 信息,则调用 determineDefaultLocale 方法去加载 Locale,该方法会首先找到 defaultLocale,如果 defaultLocale 不为 null 就直接返回,否则就从 request 中获取 Locale 返回。再来看 setLocaleContext 方法,就是将解析出来的 Locale 保存起来,保存到 Session 中即可。
3.3 FixedLocaleResolver
FixedLocaleResolver 有三个构造方法,无论调用哪一个,都会配置默认的 Locale:要么自己传 Locale 进来,要么调用 Locale.getDefault() 方法获取默认的 Locale。再来看 resolveLocale 方法:这个应该就不用解释了吧。需要注意的是它的 setLocaleContext 方法,直接抛异常出来,也就意味着 Locale 在后期不能被修改。
3.4 CookieLocaleResolver
CookieLocaleResolver 和 SessionLocaleResolver 比较类似,只不过存储介质变成了 Cookie,其他都差不多,松哥就不再重复介绍了。
4.附录
搜刮了一个语言简称表,分享给各位小伙伴:
5.小结
好啦,今天主要和小伙伴们聊了下 SpringMVC 中的国际化问题,以及 LocaleResolver 相关的源码,相信大家对 SpringMVC 的理解应该又更近一步了吧。