文章目录
1. LocaleResolver
1.1 继承关系类图
在LocaleResolver的实现类中,AcceptHeaderLocaleResolver直接使用了Header里的“acceptlanguage”值,不可以在程序中修改;FixedLocaleResolver用于解析出固定的Locale,也就是说在创建时就设置好确定的Locale,之后无法修改;SessionLocaleResolver用于将Locale保存到Session中,可以修改;CookieLocaleResolver用于将Locale保存到Cookie中,可以修改。
接口内有两个方法:
public interface LocaleResolver {
/**
*通过给定的请求解析当前的语言环境。
*可以在任何情况下返回默认的语言环境作为回退。
* @param请求解析语言环境的请求
* @return当前语言环境(never {@code null})
*/
Locale resolveLocale(HttpServletRequest request);
/**
*将当前语言环境设置为给定的语言环境。
* @param请求用于语言环境修改的请求
* @param响应用于语言环境修改的响应
* @param locale是新的locale,或者{@code null}清除locale
* @抛出UnsupportedOperationException如果LocaleResolver
*实现不支持语言环境的动态更改
*/
void setLocale(HttpServletRequest request, @Nullable HttpServletResponse response, @Nullable Locale locale);
}
其还有一个子接口LocaleContextResolver,其中增加了获取和设置LocaleContext的能力:
public interface LocaleContextResolver extends LocaleResolver {
/ * *
*通过给定请求解析当前语言环境上下文。
*
主要用于框架级处理;考虑使用
* {@link org.springframework.web.servlet.support。RequestContextUtils}或
* {@link org.springframework.web.servlet.support。RequestContext},
应用程序级访问当前地区和/或时区。
返回的上下文可以是a
* {@link org.springframework.context.i18n.TimeZoneAwareLocaleContext},
*包含有相关时区信息的地区设置。
简单地应用一个{@code instanceof}检查并相应地向下强制转换。
自定义解析器实现也可能返回额外的设置
*返回的上下文,同样可以通过向下转换访问。
* @param请求解析语言环境上下文的请求
* @return当前地区上下文(never {@code null})
* @see # resolveLocale (HttpServletRequest)
* @see org.springframework.web.servlet.support.RequestContextUtils # getLocale
* @see org.springframework.web.servlet.support.RequestContextUtils # getTimeZone
* /
LocaleContext resolveLocaleContext(HttpServletRequest request);
/ * *
*将当前语境上下文设置为给定语境,
*可能包含带有相关时区信息的地区设置。
* @param请求用于语言环境修改的请求
* @param响应用于语言环境修改的响应
* @param localeContext新语言环境上下文,或{@code null}清除语言环境
* @抛出UnsupportedOperationException,如果LocaleResolver实现
*不支持动态更改地区或时区
* @see #setLocale(HttpServletRequest, HttpServletResponse, Locale)
* @see org.springframework.context.i18n.SimpleLocaleContext
* @see org.springframework.context.i18n.SimpleTimeZoneAwareLocaleContext
* /
void setLocaleContext(HttpServletRequest request, @Nullable HttpServletResponse response,
@Nullable LocaleContext localeContext);
}
1.2 AcceptHeaderLocaleResolver
@Override
public Locale resolveLocale(HttpServletRequest request) {
Locale defaultLocale = getDefaultLocale();
if (defaultLocale != null && request.getHeader("Accept-Language") == null) {
return defaultLocale;
}
Locale requestLocale = request.getLocale();
List<Locale> supportedLocales = getSupportedLocales();
if (supportedLocales.isEmpty() || supportedLocales.contains(requestLocale)) {
return requestLocale;
}
Locale supportedLocale = findSupportedLocale(request, supportedLocales);
if (supportedLocale != null) {
return supportedLocale;
}
return (defaultLocale != null ? defaultLocale : requestLocale);
}
其实现也是非常简单,就不照着解释了。
1.3 AbstractLocaleResolver
@Nullable
private Locale defaultLocale;
/ * *
*设置默认语言环境,如果找不到其他语言环境,该解析器将返回该语言环境。
* /
public void setDefaultLocale(@Nullable Locale defaultLocale) {
this.defaultLocale = defaultLocale;
}
/ * *
*返回该解析器应该返回的默认语言环境(如果有的话)。
* /
@Nullable
protected Locale getDefaultLocale() {
return this.defaultLocale;
}
这个类只是添加了一个defaultLocale。
1.4 AbstractLocaleContextResolver
这个类就很完善了,它里面做了两件事:①添加默认时区的属性defaultTimeZone,以及其getter/setter方法;②提供LocaleResolver接口的默认实现,实现方法是使用LocaleContext。
public abstract class AbstractLocaleContextResolver extends AbstractLocaleResolver implements LocaleContextResolver {
@Nullable
private TimeZone defaultTimeZone;
/ * *
*设置默认时区,如果没有找到其他时区,该解析器将返回该时区。
* /
public void setDefaultTimeZone(@Nullable TimeZone defaultTimeZone) {
this.defaultTimeZone = defaultTimeZone;
}
/ * *
*返回该解析器应该返回的默认时区(如果有的话)。
* /
@Nullable
public TimeZone getDefaultTimeZone() {
return this.defaultTimeZone;
}
@Override
public Locale resolveLocale(HttpServletRequest request) {
Locale locale = resolveLocaleContext(request).getLocale();
return (locale != null ? locale : request.getLocale());
}
@Override
public void setLocale(HttpServletRequest request, @Nullable HttpServletResponse response, @Nullable Locale locale) {
setLocaleContext(request, response, (locale != null ? new SimpleLocaleContext(locale) : null));
}
}
1.5 FixedLocaleResolver
public class FixedLocaleResolver extends AbstractLocaleContextResolver {
/**
*创建默认FixedLocaleResolver,公开配置的默认值
语言环境(或JVM默认的语言环境作为回退)。
* @see # setDefaultLocale
* @see # setDefaultTimeZone
*/
public FixedLocaleResolver() {
setDefaultLocale(Locale.getDefault());
}
/ * *
*创建一个公开给定语言环境的FixedLocaleResolver。
* @param locale要公开的locale
* /
public FixedLocaleResolver(Locale locale) {
setDefaultLocale(locale);
}
/ * *
*创建一个FixedLocaleResolver,它公开给定的语言环境和时区。
* @param locale要公开的locale
* @param时区要暴露的时区
* /
public FixedLocaleResolver(Locale locale, TimeZone timeZone) {
setDefaultLocale(locale);
setDefaultTimeZone(timeZone);
}
@Override
public Locale resolveLocale(HttpServletRequest request) {
Locale locale = getDefaultLocale();
if (locale == null) {
locale = Locale.getDefault();
}
return locale;
}
@Override
public LocaleContext resolveLocaleContext(HttpServletRequest request) {
return new TimeZoneAwareLocaleContext() {
@Override
@Nullable
public Locale getLocale() {
return getDefaultLocale();
}
@Override
public TimeZone getTimeZone() {
return getDefaultTimeZone();
}
};
}
@Override
public void setLocaleContext( HttpServletRequest request, @Nullable HttpServletResponse response,
@Nullable LocaleContext localeContext) {
throw new UnsupportedOperationException("Cannot change fixed locale - use a different locale resolution strategy");
}
}
FixedLocaleResolver继承自AbstractLocaleContextResolver,也就具有了defaultLocale和defaultTimeZone属性。
FixedLocaleResolver在构造方法里对这两个属性进行了设置,它一共有三个构造方法,其中,如果有Locale、TimeZone参数则将其设置为默认值,无参数的构造方法会使用Locale.getDefault()作为默认Locale,这时一般为Java虚拟机所在环境的Locale,也可以人为修改。
resolveLocaleContext方法返回新建的TimeZoneAwareLocaleContext匿名类,其中getLocale和getTimeZone使用了defaultLocale和defaultTimeZone。
1.6 SessionLocaleResolver
SessionLocaleResolver和FixedLocaleResolver的实现差不多,只不过把从默认值获取变成从Session中获取,不过如果获取不到还会使用默认值。
@Override
public Locale resolveLocale(HttpServletRequest request) {
Locale locale = (Locale) WebUtils.getSessionAttribute(request, this.localeAttributeName);
if (locale == null) {
locale = determineDefaultLocale(request);
}
return locale;
}
@Override
public LocaleContext resolveLocaleContext(final HttpServletRequest request) {
return new TimeZoneAwareLocaleContext() {
@Override
public Locale getLocale() {
Locale locale = (Locale) WebUtils.getSessionAttribute(request, localeAttributeName);
if (locale == null) {
locale = determineDefaultLocale(request);
}
return locale;
}
@Override
@Nullable
public TimeZone getTimeZone() {
TimeZone timeZone = (TimeZone) WebUtils.getSessionAttribute(request, timeZoneAttributeName);
if (timeZone == null) {
timeZone = determineDefaultTimeZone(request);
}
return timeZone;
}
};
}
其中解析TimeZone的过程和解析Locale的过程一样,先从Session中获取,如果获取不到则使用默认值。
2. ThemeResolver
ThemeResolver用于根据request解析Theme
2.1 类继承关系图
其实和Locale差不多。
public interface ThemeResolver {
/**
*通过给定的请求解析当前的主题名称。
*应该在任何情况下返回默认的主题作为回退。
* @param请求用于解析的请求
* @返回当前主题名称
*/
String resolveThemeName(HttpServletRequest request);
/**
*设置当前主题的名称为给定的。
用于修改主题名称的请求
* @param响应用于修改主题名称
* @param themeName新主题名称({@code null}或空重置)
* @抛出UnsupportedOperationException如果ThemeResolver实现
*不支持动态更改主题
*/
void setThemeName(HttpServletRequest request, @Nullable HttpServletResponse response, @Nullable String themeName);
}
就不多赘述了。
3. FlashMapManager
FlashMapManager用来管理FlashMap,FlashMap用于在redirect时传递参数
看明白没?这是最不复杂的类了。接口,抽象类,类。
抽象类采用模板模式定义了整体流程,具体实现类SessionFlashMapManager通过模板方法提供了具体操作FlashMap的功能。
public interface FlashMapManager {
/**
*查找先前保存的与当前请求匹配的FlashMap
*请求,删除它从底层存储,也删除其他
*过期的FlashMap实例。
相比之下,该方法在每个请求开始时被调用
*到{@link #saveOutputFlashMap},只有在存在时才调用
*要保存的flash属性-例如,在重定向之前。
* @param请求当前请求
* @param响应当前响应
* @return匹配当前请求的FlashMap或{@code null}
*/
@Nullable
FlashMap retrieveAndUpdate(HttpServletRequest request, HttpServletResponse response);
/**
*保存给定的FlashMap,在一些底层存储并设置开始
*其有效期。
*
注意:在重定向前按顺序调用此方法
*允许在HTTP会话或响应中保存FlashMap
* cookie在响应提交之前。
* @param flashMap要保存的flashMap
* @param请求当前请求
* @param响应当前响应
*/
void saveOutputFlashMap(FlashMap flashMap, HttpServletRequest request, HttpServletResponse response);
}
有两点需要注意:
第一,实际在Session中保存的FlashMap是List<FlashMap>类型,也就是说,一个Session可以保存多个FlashMap,一个FlashMap保存着一套Redirect转发所传递的参数;
第二,FlashMap继承自HashMap,它除了具有HashMap的功能和设置有效期,还可以保存Redirect后的目标路径和通过url传递的参数,这两项内容主要用来从Session保存的多个FlashMap中查找当前请求的FlashMap。
3.1 AbstractFlashMapManager
@Override
public final void saveOutputFlashMap(FlashMap flashMap, HttpServletRequest request, HttpServletResponse response) {
if (CollectionUtils.isEmpty(flashMap)) {
return;
}
//首先对flashMap中转发地址和参数进行编码,这里的request主要用来获取当前编码方式
String path = decodeAndNormalizePath(flashMap.getTargetRequestPath(), request);
flashMap.setTargetRequestPath(path);
//设置有效期
flashMap.startExpirationPeriod(getFlashMapTimeout());
//加锁
Object mutex = getFlashMapsMutex(request);
if (mutex != null) {
synchronized (mutex) {
//没有获取到就新建一个
List<FlashMap> allFlashMaps = retrieveFlashMaps(request);
allFlashMaps = (allFlashMaps != null ? allFlashMaps : new CopyOnWriteArrayList<>());
allFlashMaps.add(flashMap);
updateFlashMaps(allFlashMaps, request, response);
}
}
else {
List<FlashMap> allFlashMaps = retrieveFlashMaps(request);
allFlashMaps = (allFlashMaps != null ? allFlashMaps : new LinkedList<>());
allFlashMaps.add(flashMap);
updateFlashMaps(allFlashMaps, request, response);
}
}
编码格式使用当前request获取;其次设置有效期,有效期可以通过flashMapTimeout参数配置,默认值是180秒;然后将flashMap添加到整体的List<FlashMap>中并更新。
其中,retrieveFlashMaps、getFlashMapsMutex和updateFlashMaps方法都在子类SessionFlashMapManager中实现。
接下来看retrieveAndUpdate方法:
@Override
@Nullable
public final FlashMap retrieveAndUpdate(HttpServletRequest request, HttpServletResponse response) {
List<FlashMap> allFlashMaps = retrieveFlashMaps(request);
if (CollectionUtils.isEmpty(allFlashMaps)) {
return null;
}
List<FlashMap> mapsToRemove = getExpiredFlashMaps(allFlashMaps);
FlashMap match = getMatchingFlashMap(allFlashMaps, request);
if (match != null) {
mapsToRemove.add(match);
}
if (!mapsToRemove.isEmpty()) {
Object mutex = getFlashMapsMutex(request);
if (mutex != null) {
synchronized (mutex) {
allFlashMaps = retrieveFlashMaps(request);
if (allFlashMaps != null) {
allFlashMaps.removeAll(mapsToRemove);
updateFlashMaps(allFlashMaps, request, response);
}
}
}
else {
allFlashMaps.removeAll(mapsToRemove);
updateFlashMaps(allFlashMaps, request, response);
}
}
return match;
}
首先使用retrieveFlashMaps模板方法获取List<FlashMap>;然后检查其中已经过期的FlashMap并保存,检查方法通过保存时设置的过期时间进行判断;接着调用getMatchingFlashMap方法从获取的List<FlashMap>中找出和当前request相匹配的FlashMap;最后将过期的和与当前请求相匹配的FlashMap从List<FlashMap>中删除并更新到Session中,将与当前request匹配的返回。
isFlashMapForRequest()方法如下:
/**
*给定的FlashMap是否与当前请求匹配。
*使用FlashMap中保存的预期请求路径和查询参数。
*/
protected boolean isFlashMapForRequest(FlashMap flashMap, HttpServletRequest request) {
String expectedPath = flashMap.getTargetRequestPath();
if (expectedPath != null) {
String requestUri = getUrlPathHelper().getOriginatingRequestUri(request);
if (!requestUri.equals(expectedPath) && !requestUri.equals(expectedPath + "/")) {
return false;
}
}
MultiValueMap<String, String> actualParams = getOriginatingRequestParams(request);
MultiValueMap<String, String> expectedParams = flashMap.getTargetRequestParams();
for (Map.Entry<String, List<String>> entry : expectedParams.entrySet()) {
List<String> actualValues = actualParams.get(entry.getKey());
if (actualValues == null) {
return false;
}
for (String expectedValue : entry.getValue()) {
if (!actualValues.contains(expectedValue)) {
return false;
}
}
}
return true;
}
这里的检查方法就是通过FlashMap中保存的目标地址和url参数与Request进行比较的,如果保存的目标地址和Request的url以及url+“/”都不一样则返回false,如果FlashMap中保存的url参数在Request中没有也返回false,如果这两项都合格则返回true。
3.2 SessionFlashMapManager
public class SessionFlashMapManager extends AbstractFlashMapManager {
private static final String FLASH_MAPS_SESSION_ATTRIBUTE = SessionFlashMapManager.class.getName() + ".FLASH_MAPS";
/**
*从HTTP会话中检索保存的FlashMap实例(如果有的话)。
*/
@Override
@SuppressWarnings("unchecked")
@Nullable
protected List<FlashMap> retrieveFlashMaps(HttpServletRequest request) {
HttpSession session = request.getSession(false);
return (session != null ? (List<FlashMap>) session.getAttribute(FLASH_MAPS_SESSION_ATTRIBUTE) : null);
}
/**
*在HTTP会话中保存给定的FlashMap实例。
*/
@Override
protected void updateFlashMaps(List<FlashMap> flashMaps, HttpServletRequest request, HttpServletResponse response) {
WebUtils.setSessionAttribute(request, FLASH_MAPS_SESSION_ATTRIBUTE, (!flashMaps.isEmpty() ? flashMaps : null));
}
/**
*公开最好的可用会话互斥量。
* @see org.springframework.web.util.WebUtils # getSessionMutex
* @see org.springframework.web.util.HttpSessionMutexListener
*/
@Override
protected Object getFlashMapsMutex(HttpServletRequest request) {
return WebUtils.getSessionMutex(request.getSession());
}
}