This is the final article in the SpringMVC source analysis series about the Theme.

Theme is the Theme of the website. Click on the Theme to change the Theme of the website. I believe everyone has used the similar function.

In consideration of some friends may not have used the Theme, so here Songge first to explain the usage, and then we will conduct source analysis.

1. One key skin change

To make a simple requirement, suppose I have three buttons on my page that I can click to change skin with one click, like this:

Let’s see how this requirement is implemented.

First of all, the three buttons correspond to three different styles. Let’s define these three different styles first, as follows:

Blue. CSS:

    background-color: #05e1ff;

Green. CSS:

    background-color: #aaff9c;

Red. CSS:

    background-color: #ff0721;

Themes are often defined as a set of styles, so we usually configure the same theme styles together in a properties file for later loading.

So let’s create a new theme directory under the Resources directory and create three files in the theme directory with the following contents:

Blue. The properties:


Green. The properties:


Red. The properties:


Different styles are introduced in different properties profiles, but the key of the style definition is index.body so that it can be easily introduced later in the page.

Next, configure the three beans in the SpringMVC container as follows:

<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=""> <property name="basenamePrefix" value="theme."/> </bean> <bean id="themeResolver" class="org.springframework.web.servlet.theme.SessionThemeResolver"> <property name="defaultThemeName" value="blue"/> </bean>
  1. First, configure the ThemechangeInterceptor, which resolvers the theme parameters with the key theme, such as the request address/index? theme=blue, the interceptor will automatically set the system theme to Blue. Of course, you can not configure an interceptor. If you do not configure an interceptor, you can provide a separate interface to modify the theme and manually modify the theme, as follows:
private 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 is just the new theme name that you configure to ThemeSolver.

  1. Next ResourceBundleThemeSource configuration, the Bean is mainly in order to load theme files, need to configure a basenamePrefix attribute, if our theme files in the folder, the value of this basenamePrefix isFolder name..
  2. Next, configure the topic parser. There are three kinds of topic resolvers, namely CookieThemeResolver, FixedThemeResolver, SessionThemeResolver. Here we use SessionThemeResolver. The topic information will be stored in the Session, and the topic will remain valid as long as the Session is unchanged. Songko will take a closer look at these three topic parsers in the next section.

After the configuration is complete, let’s provide a test page like this:

<%@ taglib prefix="spring" uri="" %> <%@ page contentType="text/html; charset=UTF-8" language="java" %> <html> <head> <title>Title</title> <link rel="stylesheet" href="<spring:theme Code = "index. The body" / > "> < / head > < body > < div > a key switch topics: < br / > < a href ="/index? Theme =blue"> TopaBlue </a> <a href="/index? Theme =red"> </a> <a href="/index? Theme =green"> </div> <br/> </body> </ HTML >

The most important thing is:

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

The CSS styles are not written directly, but refer to the index.body we defined in the properties file, which will load different CSS files depending on the current theme.

Finally, we provide another processor, as follows:

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

That’s easy. There’s nothing to say.

Finally, start the project for testing, and you can see the picture we gave at the beginning of this article. Click different buttons to switch the background.

Is not very Easy!

2. Principle analysis

The topic section is mainly about the topic parser. The topic parser is very similar to, but simpler than, the internationalization parser we talked about earlier. Let’s take a look at it.

Let’s start with the ThemeSolver interface:

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

There are only two methods in this interface:

  1. ResolveThemename: Resolves the name of the topic from the current request.
  2. SetThemename: Sets the current theme.

ThemeResolver has three main implementation classes, with the following inheritance relationships:

Next, we will analyze these implementation classes one by one.

2.1 CookieThemeResolver

Go straight to the source code:

@Override public 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; } @Override public 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); }}

Let’s start with the resolveThemename method:

  1. You first try to get the topic name directly from the request, and return it if you do.
  2. If the topic name is not obtained in the first step, the next step is to try to obtain the topic name from the Cookie, the Cookie is also extracted from the current request, using the WebUtils tool for parsing, if the topic name is resolved, it is assigned to the themeName variable.
  3. If the theme name is not previously obtained, the default theme name is used. The developer can configure the default theme name. If not, it is the theme.
  4. Save the parsed theme in the request for future use.

Let’s look at the setThemename method:

  1. Set the Themename if it exists, and add the Themename to the Cookie.
  2. If the Themename does not exist, a default subject name is set and the Cookie is removed from the response.

As you can see, the whole idea is pretty simple.

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 mainly provides the ability to configure a default theme.

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"); }}

FixedThemeSolver uses the default theme name and does not allow you to modify the theme.

2.4 SessionThemeResolver

public class SessionThemeResolver extends AbstractThemeResolver {
    public static final String THEME_SESSION_ATTRIBUTE_NAME = SessionThemeResolver.class.getName() + ".THEME";
    public String resolveThemeName(HttpServletRequest request) {
        String themeName = (String) WebUtils.getSessionAttribute(request, THEME_SESSION_ATTRIBUTE_NAME);
        return (themeName != null ? themeName : getDefaultThemeName());
    public void setThemeName(
            HttpServletRequest request, @Nullable HttpServletResponse response, @Nullable String themeName) {
        WebUtils.setSessionAttribute(request, THEME_SESSION_ATTRIBUTE_NAME,
                (StringUtils.hasText(themeName) ? themeName : null));
  • ResolveThemeName: theme removed from a session name and return, if the topic name in the session is null, it returns the default theme name.
  • SetThemename: Configure the topic into the request.

I don’t want to talk about it because it’s easy.

2.5 ThemeChangeInterceptor

Finally, let’s take a look at the ThemeChangeInterceptor interceptor. This interceptor automatically extracts the subject parameters from the request and sets them in the request. The core of this interceptor is in the preHandle method:

@Override public 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; }

Extract the theme parameter from the request and set it to the ThemeSolver.

3. Summary

All right, that’s the one-click skin change I shared with my friends today! Both the functionality and source code are very similar to internationalization, but much simpler than internationalization. Do you know if you have got it?