java源码 - SpringMVC(9)之 其他Resolver

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());
	}
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值