SpringMVC 源码剖析系列最初一篇,和大家聊一聊 Theme。

Theme,就是主题,点一下就给网站更换一个主题,置信大家都用过相似性能,这个其实和后面所说的国际化性能很像,代码其实也很像,明天咱们就来捋一捋。

思考到有的小伙伴可能还没用过 Theme,所以这里松哥先来说下用法,而后咱们再进行源码剖析。

1.一键换肤

来做一个简略的需要,假如我的页面上有三个按钮,点击之后就能一键换肤,像上面这样:

咱们来看下这个需要怎么实现。

首先三个按钮别离对应了三个不同的款式,咱们先把这三个不同的款式定义进去,别离如下:

blue.css:

body{    background-color: #05e1ff;}

green.css:

body{    background-color: #aaff9c;}

red.css:

body{    background-color: #ff0721;}

主题的定义,往往是一组款式,因而咱们个别都是在一个 properties 文件中将同一主题的款式配置在一起,这样不便前期加载。

所以接下来咱们在 resources 目录下新建 theme 目录,而后在 theme 目录中创立三个文件,内容如下:

blue.properties:

index.body=/css/blue.css

green.properties:

index.body=/css/green.css

red.properties:

index.body=/css/red.css

在不同的 properties 配置文件中引入不同的款式,然而款式定义的 key 都是 index.body,这样不便前期在页面中引入。

接下来在 SpringMVC 容器中配置三个 Bean,如下:

<mvc:interceptors>    <mvc:interceptor>        <mvc:mapping path="/**"/>        <bean class="org.springframework.web.servlet.theme.ThemeChangeInterceptor">            <property name="paramName" value="theme"/>        </bean>    </mvc:interceptor></mvc:interceptors><bean id="themeSource" class="org.springframework.ui.context.support.ResourceBundleThemeSource">    <property name="basenamePrefix" value="theme."/></bean><bean id="themeResolver" class="org.springframework.web.servlet.theme.SessionThemeResolver">    <property name="defaultThemeName" value="blue"/></bean>
  1. 首先配置拦截器 ThemeChangeInterceptor,这个拦截器用来解析主题参数,参数的 key 为 theme,例如申请地址是 /index?theme=blue,该拦截器就会主动设置零碎主题为 blue。当然也能够不配置拦截器,如果不配置的话,就能够独自提供一个批改主题的接口,而后手动批改主题,相似上面这样:
@Autowiredprivate ThemeResolver themeResolver;@RequestMapping(path = "/01/{theme}",method = RequestMethod.GET)public String theme1(@PathVariable("theme") String themeStr, HttpServletRequest request, HttpServletResponse response){    themeResolver.setThemeName(request,response, themeStr);    return "redirect:/01";}

themeStr 就是新的主题名称,将其配置给 themeResolver 即可。

  1. 接下来配置 ResourceBundleThemeSource,这个 Bean 次要是为了加载主题文件,须要配置一个 basenamePrefix 属性,如果咱们的主题文件放在文件夹中,这个 basenamePrefix 的值就是 文件夹名称.
  2. 接下来配置主题解析器,主题解析器有三种,别离是 CookieThemeResolver、FixedThemeResolver、SessionThemeResolver,这里咱们应用的是 SessionThemeResolver,主题信息将被保留在 Session 中,只有 Session 不变,主题就始终无效。这三个主题解析器松哥会在下一大节中和大家仔细分析。

配置实现后,咱们再来提供一个测试页面,如下:

<%@ taglib prefix="spring" uri="http://www.springframework.org/tags" %><%@ page contentType="text/html;charset=UTF-8" language="java" %><html><head>    <title>Title</title>    <link rel="stylesheet" href="<spring:theme code="index.body" />" ></head><body><div>    一键切换主题:<br/>    <a href="/index?theme=blue">托帕蓝</a>    <a href="/index?theme=red">多巴胺红</a>    <a href="/index?theme=green">石竹青</a></div><br/></body></html>

最要害的是:

<link rel="stylesheet" href="<spring:theme code="index.body" />" >

css 款式不间接写,而是援用咱们在 properties 文件中定义的 index.body,这样将依据以后主题加载不同的 css 文件。

最初再提供一个处理器,如下:

@GetMapping(path = "/index")public  String getPage(){    return "index";}

这个就很简略了,没啥好说的。

最初启动我的项目进行测试,大家就能够看到咱们文章一开始给出的图片了,点击不同的按钮就能够实现背景的切换。

是不是十分 Easy!

2.原理剖析

主题这块波及到的货色次要就是主题解析器,主题解析器和咱们后面所说的国际化的解析器十分相似,然而比它更简略,咱们一起来剖析下。

先来看下 ThemeResolver 接口:

public interface ThemeResolver {    String resolveThemeName(HttpServletRequest request);    void setThemeName(HttpServletRequest request, @Nullable HttpServletResponse response, @Nullable String themeName);}

这个接口中就两个办法:

  1. resolveThemeName:从以后申请中解析出主题的名字。
  2. setThemeName:设置以后主题。

ThemeResolver 次要有三个实现类,继承关系如下:

接下来咱们对这几个实现类来一一剖析。

2.1 CookieThemeResolver

间接上源码吧:

@Overridepublic String resolveThemeName(HttpServletRequest request) {    String themeName = (String) request.getAttribute(THEME_REQUEST_ATTRIBUTE_NAME);    if (themeName != null) {        return themeName;    }    String cookieName = getCookieName();    if (cookieName != null) {        Cookie cookie = WebUtils.getCookie(request, cookieName);        if (cookie != null) {            String value = cookie.getValue();            if (StringUtils.hasText(value)) {                themeName = value;            }        }    }    if (themeName == null) {        themeName = getDefaultThemeName();    }    request.setAttribute(THEME_REQUEST_ATTRIBUTE_NAME, themeName);    return themeName;}@Overridepublic void setThemeName(HttpServletRequest request, @Nullable HttpServletResponse response, @Nullable String themeName) {    if (StringUtils.hasText(themeName)) {        request.setAttribute(THEME_REQUEST_ATTRIBUTE_NAME, themeName);        addCookie(response, themeName);    } else {        request.setAttribute(THEME_REQUEST_ATTRIBUTE_NAME, getDefaultThemeName());        removeCookie(response);    }}

先来看 resolveThemeName 办法:

  1. 首先会尝试间接从申请中获取主题名称,如果获取到了,就间接返回。
  2. 如果第一步没有获取到主题名称,接下来就尝试从 Cookie 中获取主题名称,Cookie 也是从以后申请中提取,利用 WebUtils 工具进行解析,如果解析到了主题名称,就赋值给 themeName 变量。
  3. 如果后面没有获取到主题名称,就应用默认的主题名称,开发者能够自行配置默认的主题名称,如果不配置,就是 theme。
  4. 将解析进去的 theme 保留到 request 中,以备后续应用。

再来看 setThemeName 办法:

  1. 如果存在 themeName 就进行设置,同时将 themeName 增加到 Cookie 中。
  2. 如果不存在 themeName,就设置一个默认的主题名,同时从 response 中移除 Cookie。

能够看到,整个实现思路还是非常简单的。

2.2 AbstractThemeResolver

public abstract class AbstractThemeResolver implements ThemeResolver {    public static final String ORIGINAL_DEFAULT_THEME_NAME = "theme";    private String defaultThemeName = ORIGINAL_DEFAULT_THEME_NAME;    public void setDefaultThemeName(String defaultThemeName) {        this.defaultThemeName = defaultThemeName;    }    public String getDefaultThemeName() {        return this.defaultThemeName;    }}

AbstractThemeResolver 次要提供了配置默认主题的能力。

2.3 FixedThemeResolver

public class FixedThemeResolver extends AbstractThemeResolver {    @Override    public String resolveThemeName(HttpServletRequest request) {        return getDefaultThemeName();    }    @Override    public void setThemeName(            HttpServletRequest request, @Nullable HttpServletResponse response, @Nullable String themeName) {        throw new UnsupportedOperationException("Cannot change theme - use a different theme resolution strategy");    }}

FixedThemeResolver 就是应用默认的主题名称,并且不容许批改主题。

2.4 SessionThemeResolver

public class SessionThemeResolver extends AbstractThemeResolver {    public static final String THEME_SESSION_ATTRIBUTE_NAME = SessionThemeResolver.class.getName() + ".THEME";    @Override    public String resolveThemeName(HttpServletRequest request) {        String themeName = (String) WebUtils.getSessionAttribute(request, THEME_SESSION_ATTRIBUTE_NAME);        return (themeName != null ? themeName : getDefaultThemeName());    }    @Override    public void setThemeName(            HttpServletRequest request, @Nullable HttpServletResponse response, @Nullable String themeName) {        WebUtils.setSessionAttribute(request, THEME_SESSION_ATTRIBUTE_NAME,                (StringUtils.hasText(themeName) ? themeName : null));    }}
  • resolveThemeName:从 session 中取出主题名称并返回,如果 session 中的主题名称为 null,就返回默认的主题名称。
  • setThemeName:将主题配置到申请中。

不想多说,因为很简略。

2.5 ThemeChangeInterceptor

最初咱们再来看一看 ThemeChangeInterceptor 拦截器,这个拦截器会主动从申请中提取出主题参数,并设置到申请中,外围局部在 preHandle 办法中:

@Overridepublic boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)        throws ServletException {    String newTheme = request.getParameter(this.paramName);    if (newTheme != null) {        ThemeResolver themeResolver = RequestContextUtils.getThemeResolver(request);        if (themeResolver == null) {            throw new IllegalStateException("No ThemeResolver found: not in a DispatcherServlet request?");        }        themeResolver.setThemeName(request, response, newTheme);    }    return true;}

从申请中提取出 theme 参数,并设置到 themeResolver 中。

3.小结

好啦,这就是明天和小伙伴们分享的一键换肤!无论是功能性还是源码,都和国际化十分相似,然而比国际化简略很多,不晓得小伙伴们有没有 GET 到呢?