Spring MVC更多家族成员--Theme与ThemeResolver
- 引言
- 提供主题资源的 ThemeSource
- 管理主题的ThemeResolver
- 切换主题的ThemeChangeInterceptor
引言
不管是使用Windows操作系统还是使用Linux操作系统,当我们对某种风格的桌面主题感到厌烦的时候,我们就会安装并切换到某种新的桌面主题上。对于Web应用程序来说,为了能够给用户提供更丰富的交互体验,也同样可以提供类似桌面主题的功能。实际上,不管是什么场景下的主题(Theme)功能,它们在本质上都是类似的,无非就是变更一下显示的材质风格:
- 对于操作系统的桌面主题,可能是鼠标样式或者工具条颜色等变更一下;
- 对于Web应用程序来说,可能就是对影响整体风格显示的背景图片,或者某些固定部位的颜色做一些变更。
这就好像我们人穿衣服一样,每天换上不同风格式样的衣服,实际上就是在变换主题啦!
Spring MVC框架提供了对Web应用程序所需要的主题功能的支持,下面具体介绍完成这一功能的几位角色。
提供主题资源的 ThemeSource
通常,Web应用程序的主题是由一些能够影响整体应用显示的静态资源组成的,比如固定位置的背景图片、能够影响页面显示风格的CSS(层叠样式表)文件等。在Spring MVC中,ThemeSource负责管理针对各个主题的那些静态资源,该接口定义如下:
代码语言:javascript复制public interface ThemeSource {
@Nullable
Theme getTheme(String themeName);
}
ThemeSource可以根据指定的主题名称(themeName)查找并返回对应的Theme实例。这样,客户端就可以使用Theme实例中的相应资源来定制视图的显示了。
代码语言:javascript复制public interface Theme {
String getName();
MessageSource getMessageSource();
}
为了能够通过ThemeSource获取相应的主题资源,DispatcherServlet会在处理Web请求之前获取可用的ThemeSource实例,以便 能够根据客户端的请求返回相应的主题资源。
而实际上DispatcherServlet获取的ThemeSource 实例就是它自身所使用的WebApplicationContext。因为WebApplicationContext本身就是一个 ThemeSource(它自己实现了ThemeSource接口)。事情到这里并没有完,虽然WebApplicationContext身为ThemeSource,但它和它的实现类一概不干实事。当有主题相关的请求需要处理的时候,它们都是将工作委派给某个ThemeSource的具体实现类,比如ResourceBundleThemeSource。
ResourceBundleThemeSource允许我们以properties文件来定义每个主题所持有的各项资源, 比如:
代码语言:javascript复制#default.properties
theme.backgroud.image=../xx.jpg
theme.css=...
...
#blue.properties
theme.backgroud.image=../xx.jpg
theme.css=...
...
我们分别在default.properties和blue.properties中定义default和blue两个主题对应的材质资源。这样,在具体视图中,我们就可以根据这些主题资源文件中的代码,查找相应的主题资源并应用到视图。比如,如果我们使用JSP作为视图技术,那么可以直接使用Spring提供的theme自定义标签对主题资源进行访问,如下所示:
代码语言:javascript复制<*@ taglib prefix="spring"uri="http://www.springframework.org/tags"%> <style type="text/css">
<!-- body(
background-image:url(<spring:theme code="theme.background.image"/>); )
-->
</style>
而如果是使用其他视图技术,比如Velocity/Freemarker等,就可以通过为相应视图公开RequestContext的方式来访问主题的相关信息。下面给出的是Velocity视图模板对应的情况:
代码语言:javascript复制# in velocity template file
<html>
<style type="text/css"> <!--
body{
background-image:url($(rc.getThemeMeseage("theme.background.image")}); }
--> </style>
不管怎么样,只要将当前使用的主题名称告知ResourceBundleThemeSource,它就能返回对应主题名的properties文件中的相应资源。因为ResourceBundleThemeSource是基于Java标准的 ResourceBundle构建的,所以它同样也支持不同Locale下的主题。比如,同样是blue主题,在默认Locale下所使用的主题资源与在其他Locale下所使用的主题资源就可能不同,那么,我们可以按照ResourceBundle国际化支持规则,提供同一主题不同Locale下的主题资源定义,如下所示:
代码语言:javascript复制# blue.properties
theme.background.image=../images/blue-bg-image.jpg
theme.css=...
# blue_zh_CN.properties
theme.background.image=../images/blue-bg-image-with-chinese-characters.jpg
theme.css=...
现在,如果用户对应中文的Locale并且选择使用blue这一主题的话,ResourceBundleThemeSource将从blue_zh_CN.properties资源文件中为其返回对应的主题资源。
为了能让DispatcherServlet获取到ResourceBundleThemeSource的支持,我们需要将某一 ResourceBundleThemeSource实例添加到DispatcherServlet的webApplicationContext中,如下 所示:
代码语言:javascript复制<bean> id="themeSource" class="org.springframework.ui.context.support.ResourceBundleThemeSource" p:basenamePrefix="cn.spring21.simplefx.resources.themes."/>
所配置的ResourceBundleThemeSource实例的bean定义名称必须是“themeSource”,因为默认情况下,WebApplicationContext就是将所有主题相关的请求处理委派给拥有这一名称的ThemeSource实例。默认情况下,ResourceBundleThemeSource将根据主题名称到classpath 的根路径下查找相应的properties文件,这当然就要求我们将所有主题properties资源文件放到classpath的根路径下。不过,我们可以通过其basenamePrefix属性定制查找起始路径,就像我们的代码示例所演示的那样,唯一需要注意的就是前缀需要以“.”结束。
在DispatcherServlet有了可用的ThemeSource之后,就会把它绑定到HttpServletRequest的 属性上,等着后面谁来用了。那么到底是谁来用呢?
管理主题的ThemeResolver
现在,通过指定的主题名称,我们就能够从DispatcherServlet所使用的ThemeSource那里获取 主题对应的各项资源,然后视图就能够根据这些主题资源来定制视图显示。ThemeSource已经准备就绪了,那主题名称又该如何定夺呢?
显然,我们得通过某种方式获取用户当前所选择的主题才行,否则,我们怎么知道使用哪个主题名称到ThemeSource查找要用的主题资源呢?
为了获取并管理用户的Locale信息,Spring MVC提供了LocaleResolver。与此类似,为了获取并管理用户所选择的主题,Spring MVC提供了ThemeResolver。ThemeResolver的主要工作就是解析并获取对应当前请求的主题是什么。如果相应实现机制支持存储的话,也允许对当前请求相关的主题进行设置变更。ThemeResolver的定义如下:
代码语言:javascript复制public interface ThemeResolver {
String resolveThemeName(HttpServletRequest request);
void setThemeName(HttpServletRequest request, @Nullable HttpServletResponse response, @Nullable String themeName);
}
有LocaleResolver在先,我想ThemeResolver并不难理解。显然,DispatcherServlet通过ThemeResolver的resolveThemeName方法就能够获得用户所选择的主题是什么。那么剩下的工 作,当然就是根据这个主题的名称到ThemeSource那里获取相应资源进行显示啦。不过,DispatcherServlet肯定无法直接借助于ThemeResolver这一接口来完成工作,所以,还是来看一下Spring MVC 都提供了哪些可用的ThemeResolver实现类吧!
除了不能像LocaleResolver那样通过HTTP的Accept-Language协议头来获取主题信息之外, ThemeResolver可以使用LocaleResolver所使用的其他三种策略来获取并且管理用户的主题, 如下所述。
- FixedThemeReвoLver。如果我们不明确为DispatcherServlet指定任何ThemeResolver实例供其使用,DispatcherServlet将 默认使用FixedThemeResolver来管理用户的主题。顾名思义,一旦使用FixedThemeResolver指定了主题之后,主题将保持不变。所以,对FixedThemeResolver进行setThemeName操作显然是不行的。
- SessionThemeResolver 。 HttpSession 是 SessionThemeResolver得以生存的土壤,SessionThemeResolver将按照指定的属性名称到Session中获取用户的主题。如果找不到,则使用默认的主题。这可以通过其defaultThemeName属性进行指定。因为我们可以对Session的属性进行设置,所以,SessionThemeResolver可以通过setThemeName方法重新设置用户主题。
- CookieThemeResolver。SessionThemeResolver以HttpSession作为主题信息的载体,而CookieThemeResolver则以Cookie作为主 题信息的载体。只要用户端浏览器不禁止Cookie的使用,我们就可以使用CookieThemeResolver对用户选择的主题进行管理,包括获取和更新。
现在,只要将以上任—ThemeResolver实现添加到Dispatcherservlet的WebapplicationContext中, Dispatcherservlet就能够“左右逢源”了:
代码语言:javascript复制<bean id="themeReBolver"
class="org.springframework. Web.servlet.theme. SessionThemeResolver" p:defaultThemeName="default">
</bean>
我们这里使用了SessionThemeResolver。不管使用哪种ThemeResolver,注册到容器的bean定义的名称为“themeResolver”是必须的,因为DispatcherServlet初始化的时候将根据这一名称到其自己的WebApplicationcontext中获取可用的ThemeResolver实例。
切换主题的ThemeChangeInterceptor
如果用户永远只能使用一种风格的主题,那么显然提供主题的功能就没有了任何的意义。只有允许用户根据喜好切换主题,才能够体现主题功能丰富用户体验的价值。
在Spring MVC中,用户要切换Locale有LocaleChangeInterceptor来相助,而用户要切换主 题的时候,也同样有ThemeChangeInterceptor来帮忙。
只要将用户选择要切换到的主题以某个参数提交到服务器端处理,ThemeChangeInterceptor就能够根据这一参数重新设置用户所使用的主题,之后,视图就可以获取切换后的主题来定制视图的显示了。
当然,要让ThemeChangeInterceptor生效,我们自然需要将它与提交主题变更的Web请求联系到一起,如下所示:
代码语言:javascript复制<bean id="handlerMapping" class="org.springframework.Web.servlet.handler.BeanNameUr1HandlerMapping">
<property name="interceptors">
<list>
<ref bean="themeChangeInterceptor"/>
<ref bean="marketAccessInterceptor"/>
<ref bean="localeChangeInterceptor"/>
</list>
</property>
</bean>
<bean id="themeChangeInterceptor"
class="org.springframework.Web.servlet.theme.ThemeChangeInterceptor"> </bean>
ThemeChangeInterceptor默认以名称为“theme”的参数作为要切换的主题名称。比如,以 http://host:port/simplefx/anyrequest.do?theme=blue形式发送的Web请求,将最终被切换到blue主题显示 风格。如果我们不想使用“theme”作为标志参数,那么可以通过设置ThemeChangeInterceptor的paramName属性变更这一默认使用的标志参数。