java框架之SpringBoot(4)-資源映射&thymeleaf

資源映射

靜態資源映射

查看 SpringMVC 的自動配置類,裏面有一個配置靜態資源映射的方法:javascript

 1 @Override
 2 public void addResourceHandlers(ResourceHandlerRegistry registry) {
 3     if (!this.resourceProperties.isAddMappings()) {
 4         logger.debug("Default resource handling disabled");
 5         return;
 6     }
 7     Integer cachePeriod = this.resourceProperties.getCachePeriod();
 8     if (!registry.hasMappingForPattern("/webjars/**")) {
 9         // 將路徑爲 "/webjars/**" 匹配到的資源在 "classpath:/META-INF/resources/webjars/"
10         customizeResourceHandlerRegistration(registry
11                 .addResourceHandler("/webjars/**")
12                 .addResourceLocations("classpath:/META-INF/resources/webjars/")
13                 .setCachePeriod(cachePeriod));
14     }
15     // 從配置中獲取靜態路由規則
16     String staticPathPattern = this.mvcProperties.getStaticPathPattern();
17     if (!registry.hasMappingForPattern(staticPathPattern)) {
18         // 將路徑爲 staticPathPattern 匹配到的資源在 this.resourceProperties.getStaticLocations()
19         customizeResourceHandlerRegistration(
20                 registry.addResourceHandler(staticPathPattern)
21                         .addResourceLocations(
22                                 this.resourceProperties.getStaticLocations())
23                         .setCachePeriod(cachePeriod));
24     }
25 }
org.springframework.boot.autoconfigure.web.WebMvcAutoConfiguration.WebMvcAutoConfigurationAdapter#addResourceHandlers

從第 8-14 行能夠看到,有一個默認配置,將能匹配 "/webjars/**" 的請求路徑映射到 "classpath:/META-INF/resources/webjars/" 中。css

接着從 16-24 行又將 this.mvcProperties.getStaticPathPattern() 變量對應值的路徑映射 this.resourceProperties.getStaticLocations() 對應值的目錄下。html

查看 this.mvcProperties 對應的配置類:java

  1 /*
  2  * Copyright 2012-2017 the original author or authors.
  3  *
  4  * Licensed under the Apache License, Version 2.0 (the "License");
  5  * you may not use this file except in compliance with the License.
  6  * You may obtain a copy of the License at
  7  *
  8  *      http://www.apache.org/licenses/LICENSE-2.0
  9  *
 10  * Unless required by applicable law or agreed to in writing, software
 11  * distributed under the License is distributed on an "AS IS" BASIS,
 12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 13  * See the License for the specific language governing permissions and
 14  * limitations under the License.
 15  */
 16 
 17 package org.springframework.boot.autoconfigure.web;
 18 
 19 import java.util.LinkedHashMap;
 20 import java.util.Locale;
 21 import java.util.Map;
 22 
 23 import org.springframework.boot.context.properties.ConfigurationProperties;
 24 import org.springframework.http.MediaType;
 25 import org.springframework.validation.DefaultMessageCodesResolver;
 26 
 27 /**
 28  * {@link ConfigurationProperties properties} for Spring MVC.
 29  *
 30  * @author Phillip Webb
 31  * @author Sébastien Deleuze
 32  * @author Stephane Nicoll
 33  * @author Eddú Meléndez
 34  * @since 1.1
 35  */
 36 @ConfigurationProperties(prefix = "spring.mvc")
 37 public class WebMvcProperties {
 38 
 39     /**
 40      * Formatting strategy for message codes (PREFIX_ERROR_CODE, POSTFIX_ERROR_CODE).
 41      */
 42     private DefaultMessageCodesResolver.Format messageCodesResolverFormat;
 43 
 44     /**
 45      * Locale to use. By default, this locale is overridden by the "Accept-Language"
 46      * header.
 47      */
 48     private Locale locale;
 49 
 50     /**
 51      * Define how the locale should be resolved.
 52      */
 53     private LocaleResolver localeResolver = LocaleResolver.ACCEPT_HEADER;
 54 
 55     /**
 56      * Date format to use (e.g. dd/MM/yyyy).
 57      */
 58     private String dateFormat;
 59 
 60     /**
 61      * Dispatch TRACE requests to the FrameworkServlet doService method.
 62      */
 63     private boolean dispatchTraceRequest = false;
 64 
 65     /**
 66      * Dispatch OPTIONS requests to the FrameworkServlet doService method.
 67      */
 68     private boolean dispatchOptionsRequest = true;
 69 
 70     /**
 71      * If the content of the "default" model should be ignored during redirect scenarios.
 72      */
 73     private boolean ignoreDefaultModelOnRedirect = true;
 74 
 75     /**
 76      * If a "NoHandlerFoundException" should be thrown if no Handler was found to process
 77      * a request.
 78      */
 79     private boolean throwExceptionIfNoHandlerFound = false;
 80 
 81     /**
 82      * Enable warn logging of exceptions resolved by a "HandlerExceptionResolver".
 83      */
 84     private boolean logResolvedException = false;
 85 
 86     /**
 87      * Maps file extensions to media types for content negotiation, e.g. yml->text/yaml.
 88      */
 89     private Map<String, MediaType> mediaTypes = new LinkedHashMap<String, MediaType>();
 90 
 91     /**
 92      * Path pattern used for static resources.
 93      */
 94     private String staticPathPattern = "/**";
 95 
 96     private final Async async = new Async();
 97 
 98     private final Servlet servlet = new Servlet();
 99 
100     private final View view = new View();
101 
102     public DefaultMessageCodesResolver.Format getMessageCodesResolverFormat() {
103         return this.messageCodesResolverFormat;
104     }
105 
106     public void setMessageCodesResolverFormat(
107             DefaultMessageCodesResolver.Format messageCodesResolverFormat) {
108         this.messageCodesResolverFormat = messageCodesResolverFormat;
109     }
110 
111     public Locale getLocale() {
112         return this.locale;
113     }
114 
115     public void setLocale(Locale locale) {
116         this.locale = locale;
117     }
118 
119     public LocaleResolver getLocaleResolver() {
120         return this.localeResolver;
121     }
122 
123     public void setLocaleResolver(LocaleResolver localeResolver) {
124         this.localeResolver = localeResolver;
125     }
126 
127     public String getDateFormat() {
128         return this.dateFormat;
129     }
130 
131     public void setDateFormat(String dateFormat) {
132         this.dateFormat = dateFormat;
133     }
134 
135     public boolean isIgnoreDefaultModelOnRedirect() {
136         return this.ignoreDefaultModelOnRedirect;
137     }
138 
139     public void setIgnoreDefaultModelOnRedirect(boolean ignoreDefaultModelOnRedirect) {
140         this.ignoreDefaultModelOnRedirect = ignoreDefaultModelOnRedirect;
141     }
142 
143     public boolean isThrowExceptionIfNoHandlerFound() {
144         return this.throwExceptionIfNoHandlerFound;
145     }
146 
147     public void setThrowExceptionIfNoHandlerFound(
148             boolean throwExceptionIfNoHandlerFound) {
149         this.throwExceptionIfNoHandlerFound = throwExceptionIfNoHandlerFound;
150     }
151 
152     public boolean isLogResolvedException() {
153         return this.logResolvedException;
154     }
155 
156     public void setLogResolvedException(boolean logResolvedException) {
157         this.logResolvedException = logResolvedException;
158     }
159 
160     public Map<String, MediaType> getMediaTypes() {
161         return this.mediaTypes;
162     }
163 
164     public void setMediaTypes(Map<String, MediaType> mediaTypes) {
165         this.mediaTypes = mediaTypes;
166     }
167 
168     public boolean isDispatchOptionsRequest() {
169         return this.dispatchOptionsRequest;
170     }
171 
172     public void setDispatchOptionsRequest(boolean dispatchOptionsRequest) {
173         this.dispatchOptionsRequest = dispatchOptionsRequest;
174     }
175 
176     public boolean isDispatchTraceRequest() {
177         return this.dispatchTraceRequest;
178     }
179 
180     public void setDispatchTraceRequest(boolean dispatchTraceRequest) {
181         this.dispatchTraceRequest = dispatchTraceRequest;
182     }
183 
184     public String getStaticPathPattern() {
185         return this.staticPathPattern;
186     }
187 
188     public void setStaticPathPattern(String staticPathPattern) {
189         this.staticPathPattern = staticPathPattern;
190     }
191 
192     public Async getAsync() {
193         return this.async;
194     }
195 
196     public Servlet getServlet() {
197         return this.servlet;
198     }
199 
200     public View getView() {
201         return this.view;
202     }
203 
204     public static class Async {
205 
206         /**
207          * Amount of time (in milliseconds) before asynchronous request handling times
208          * out. If this value is not set, the default timeout of the underlying
209          * implementation is used, e.g. 10 seconds on Tomcat with Servlet 3.
210          */
211         private Long requestTimeout;
212 
213         public Long getRequestTimeout() {
214             return this.requestTimeout;
215         }
216 
217         public void setRequestTimeout(Long requestTimeout) {
218             this.requestTimeout = requestTimeout;
219         }
220 
221     }
222 
223     public static class Servlet {
224 
225         /**
226          * Load on startup priority of the dispatcher servlet.
227          */
228         private int loadOnStartup = -1;
229 
230         public int getLoadOnStartup() {
231             return this.loadOnStartup;
232         }
233 
234         public void setLoadOnStartup(int loadOnStartup) {
235             this.loadOnStartup = loadOnStartup;
236         }
237 
238     }
239 
240     public static class View {
241 
242         /**
243          * Spring MVC view prefix.
244          */
245         private String prefix;
246 
247         /**
248          * Spring MVC view suffix.
249          */
250         private String suffix;
251 
252         public String getPrefix() {
253             return this.prefix;
254         }
255 
256         public void setPrefix(String prefix) {
257             this.prefix = prefix;
258         }
259 
260         public String getSuffix() {
261             return this.suffix;
262         }
263 
264         public void setSuffix(String suffix) {
265             this.suffix = suffix;
266         }
267 
268     }
269 
270     public enum LocaleResolver {
271 
272         /**
273          * Always use the configured locale.
274          */
275         FIXED,
276 
277         /**
278          * Use the "Accept-Language" header or the configured locale if the header is not
279          * set.
280          */
281         ACCEPT_HEADER
282 
283     }
284 
285 }
org.springframework.boot.autoconfigure.web.WebMvcProperties

查看 this.resourceProperties 對應的配置類:ios

/*
 * Copyright 2012-2018 the original author or authors.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.springframework.boot.autoconfigure.web;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

import org.springframework.beans.factory.InitializingBean;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.context.properties.NestedConfigurationProperty;
import org.springframework.context.ResourceLoaderAware;
import org.springframework.core.io.ClassPathResource;
import org.springframework.core.io.Resource;
import org.springframework.core.io.ResourceLoader;

/**
 * Properties used to configure resource handling.
 *
 * @author Phillip Webb
 * @author Brian Clozel
 * @author Dave Syer
 * @author Venil Noronha
 * @since 1.1.0
 */
@ConfigurationProperties(prefix = "spring.resources", ignoreUnknownFields = false)
public class ResourceProperties implements ResourceLoaderAware, InitializingBean {

    private static final String[] SERVLET_RESOURCE_LOCATIONS = { "/" };

    private static final String[] CLASSPATH_RESOURCE_LOCATIONS = {
            "classpath:/META-INF/resources/", "classpath:/resources/",
            "classpath:/static/", "classpath:/public/" };

    private static final String[] RESOURCE_LOCATIONS;

    static {
        RESOURCE_LOCATIONS = new String[CLASSPATH_RESOURCE_LOCATIONS.length
                + SERVLET_RESOURCE_LOCATIONS.length];
        System.arraycopy(SERVLET_RESOURCE_LOCATIONS, 0, RESOURCE_LOCATIONS, 0,
                SERVLET_RESOURCE_LOCATIONS.length);
        System.arraycopy(CLASSPATH_RESOURCE_LOCATIONS, 0, RESOURCE_LOCATIONS,
                SERVLET_RESOURCE_LOCATIONS.length, CLASSPATH_RESOURCE_LOCATIONS.length);
    }

    /**
     * Locations of static resources. Defaults to classpath:[/META-INF/resources/,
     * /resources/, /static/, /public/] plus context:/ (the root of the servlet context).
     */
    private String[] staticLocations = RESOURCE_LOCATIONS;

    /**
     * Cache period for the resources served by the resource handler, in seconds.
     */
    private Integer cachePeriod;

    /**
     * Enable default resource handling.
     */
    private boolean addMappings = true;

    private final Chain chain = new Chain();

    private ResourceLoader resourceLoader;

    @Override
    public void setResourceLoader(ResourceLoader resourceLoader) {
        this.resourceLoader = resourceLoader;
    }

    @Override
    public void afterPropertiesSet() {
        this.staticLocations = appendSlashIfNecessary(this.staticLocations);
    }

    public String[] getStaticLocations() {
        return this.staticLocations;
    }

    public void setStaticLocations(String[] staticLocations) {
        this.staticLocations = appendSlashIfNecessary(staticLocations);
    }

    private String[] appendSlashIfNecessary(String[] staticLocations) {
        String[] normalized = new String[staticLocations.length];
        for (int i = 0; i < staticLocations.length; i++) {
            String location = staticLocations[i];
            if (location != null) {
                normalized[i] = (location.endsWith("/") ? location : location + "/");
            }
        }
        return normalized;
    }

    public Resource getWelcomePage() {
        for (String location : getStaticWelcomePageLocations()) {
            Resource resource = this.resourceLoader.getResource(location);
            try {
                if (resource.exists()) {
                    resource.getURL();
                    return resource;
                }
            }
            catch (Exception ex) {
                // Ignore
            }
        }
        return null;
    }

    private String[] getStaticWelcomePageLocations() {
        String[] result = new String[this.staticLocations.length];
        for (int i = 0; i < result.length; i++) {
            String location = this.staticLocations[i];
            if (!location.endsWith("/")) {
                location = location + "/";
            }
            result[i] = location + "index.html";
        }
        return result;
    }

    List<Resource> getFaviconLocations() {
        List<Resource> locations = new ArrayList<Resource>(
                this.staticLocations.length + 1);
        if (this.resourceLoader != null) {
            for (String location : this.staticLocations) {
                locations.add(this.resourceLoader.getResource(location));
            }
        }
        locations.add(new ClassPathResource("/"));
        return Collections.unmodifiableList(locations);
    }

    public Integer getCachePeriod() {
        return this.cachePeriod;
    }

    public void setCachePeriod(Integer cachePeriod) {
        this.cachePeriod = cachePeriod;
    }

    public boolean isAddMappings() {
        return this.addMappings;
    }

    public void setAddMappings(boolean addMappings) {
        this.addMappings = addMappings;
    }

    public Chain getChain() {
        return this.chain;
    }

    /**
     * Configuration for the Spring Resource Handling chain.
     */
    public static class Chain {

        /**
         * Enable the Spring Resource Handling chain. Disabled by default unless at least
         * one strategy has been enabled.
         */
        private Boolean enabled;

        /**
         * Enable caching in the Resource chain.
         */
        private boolean cache = true;

        /**
         * Enable HTML5 application cache manifest rewriting.
         */
        private boolean htmlApplicationCache = false;

        /**
         * Enable resolution of already gzipped resources. Checks for a resource name
         * variant with the "*.gz" extension.
         */
        private boolean gzipped = false;

        @NestedConfigurationProperty
        private final Strategy strategy = new Strategy();

        /**
         * Return whether the resource chain is enabled. Return {@code null} if no
         * specific settings are present.
         * @return whether the resource chain is enabled or {@code null} if no specified
         * settings are present.
         */
        public Boolean getEnabled() {
            return getEnabled(getStrategy().getFixed().isEnabled(),
                    getStrategy().getContent().isEnabled(), this.enabled);
        }

        public void setEnabled(boolean enabled) {
            this.enabled = enabled;
        }

        public boolean isCache() {
            return this.cache;
        }

        public void setCache(boolean cache) {
            this.cache = cache;
        }

        public Strategy getStrategy() {
            return this.strategy;
        }

        public boolean isHtmlApplicationCache() {
            return this.htmlApplicationCache;
        }

        public void setHtmlApplicationCache(boolean htmlApplicationCache) {
            this.htmlApplicationCache = htmlApplicationCache;
        }

        public boolean isGzipped() {
            return this.gzipped;
        }

        public void setGzipped(boolean gzipped) {
            this.gzipped = gzipped;
        }

        static Boolean getEnabled(boolean fixedEnabled, boolean contentEnabled,
                Boolean chainEnabled) {
            return (fixedEnabled || contentEnabled) ? Boolean.TRUE : chainEnabled;
        }

    }

    /**
     * Strategies for extracting and embedding a resource version in its URL path.
     */
    public static class Strategy {

        @NestedConfigurationProperty
        private final Fixed fixed = new Fixed();

        @NestedConfigurationProperty
        private final Content content = new Content();

        public Fixed getFixed() {
            return this.fixed;
        }

        public Content getContent() {
            return this.content;
        }

    }

    /**
     * Version Strategy based on content hashing.
     */
    public static class Content {

        /**
         * Enable the content Version Strategy.
         */
        private boolean enabled;

        /**
         * Comma-separated list of patterns to apply to the Version Strategy.
         */
        private String[] paths = new String[] { "/**" };

        public boolean isEnabled() {
            return this.enabled;
        }

        public void setEnabled(boolean enabled) {
            this.enabled = enabled;
        }

        public String[] getPaths() {
            return this.paths;
        }

        public void setPaths(String[] paths) {
            this.paths = paths;
        }

    }

    /**
     * Version Strategy based on a fixed version string.
     */
    public static class Fixed {

        /**
         * Enable the fixed Version Strategy.
         */
        private boolean enabled;

        /**
         * Comma-separated list of patterns to apply to the Version Strategy.
         */
        private String[] paths = new String[] { "/**" };

        /**
         * Version string to use for the Version Strategy.
         */
        private String version;

        public boolean isEnabled() {
            return this.enabled;
        }

        public void setEnabled(boolean enabled) {
            this.enabled = enabled;
        }

        public String[] getPaths() {
            return this.paths;
        }

        public void setPaths(String[] paths) {
            this.paths = paths;
        }

        public String getVersion() {
            return this.version;
        }

        public void setVersion(String version) {
            this.version = version;
        }

    }

}
org.springframework.boot.autoconfigure.web.ResourceProperties

即 16-24 行就是將能匹配 "/**"  的請求路徑映射到項目路徑下 "classpath:/META-INF/resources/", "classpath:/resources/", "classpath:/static/", "classpath:/public/" 中。web

結論:spring

  • 請求路徑若是匹配 "/webjars/**" 規則,那麼 SpringBoot 就會去 classpath:/META-INF/resources/webjars/ 目錄下尋找對應資源。
  • 請求路徑若是匹配 "/**" 規則(即任意請求路徑),那麼 SpringBoot 就會去 "classpath:/META-INF/resources/", "classpath:/resources/", "classpath:/static/", "classpath:/public/" 目錄下尋找對應資源。

歡迎頁

依舊是 SpringMVC 配置類中,有一個註冊歡迎頁映射 bean 的方法:express

1 @Bean
2 public WelcomePageHandlerMapping welcomePageHandlerMapping(
3         ResourceProperties resourceProperties) {
4     return new WelcomePageHandlerMapping(resourceProperties.getWelcomePage(),
5             this.mvcProperties.getStaticPathPattern());
6 }
org.springframework.boot.autoconfigure.web.WebMvcAutoConfiguration.WebMvcAutoConfigurationAdapter#welcomePageHandlerMapping

查看 resourceProperties.getWelcomePage() 方法:apache

 1 public Resource getWelcomePage() {
 2     for (String location : getStaticWelcomePageLocations()) {
 3         Resource resource = this.resourceLoader.getResource(location);
 4         try {
 5             if (resource.exists()) {
 6                 resource.getURL();
 7                 return resource;
 8             }
 9         }
10         catch (Exception ex) {
11             // Ignore
12         }
13     }
14     return null;
15 }
org.springframework.boot.autoconfigure.web.ResourceProperties#getWelcomePage

接着查看 getStaticWelcomePageLocations() 方法:數組

 1 private static final String[] SERVLET_RESOURCE_LOCATIONS = { "/" };
 2 
 3 private static final String[] CLASSPATH_RESOURCE_LOCATIONS = {
 4             "classpath:/META-INF/resources/", "classpath:/resources/",
 5             "classpath:/static/", "classpath:/public/" };
 6 
 7 private static final String[] RESOURCE_LOCATIONS;
 8 
 9 static {
10     RESOURCE_LOCATIONS = new String[CLASSPATH_RESOURCE_LOCATIONS.length
11             + SERVLET_RESOURCE_LOCATIONS.length];
12     System.arraycopy(SERVLET_RESOURCE_LOCATIONS, 0, RESOURCE_LOCATIONS, 0,
13             SERVLET_RESOURCE_LOCATIONS.length);
14     System.arraycopy(CLASSPATH_RESOURCE_LOCATIONS, 0, RESOURCE_LOCATIONS,
15             SERVLET_RESOURCE_LOCATIONS.length, CLASSPATH_RESOURCE_LOCATIONS.length);
16 }
17 
18 
19 private String[] staticLocations = RESOURCE_LOCATIONS;
20 
21 private String[] getStaticWelcomePageLocations() {
22     String[] result = new String[this.staticLocations.length];
23     for (int i = 0; i < result.length; i++) {
24         String location = this.staticLocations[i];
25         if (!location.endsWith("/")) {
26             location = location + "/";
27         }
28         result[i] = location + "index.html";
29     }
30     return result;
31 }
org.springframework.boot.autoconfigure.web.ResourceProperties#getStaticWelcomePageLocations

即 resourceProperties.getWelcomePage() 方法默認就是從靜態資源目錄下即 "classpath:/META-INF/resources/", "classpath:/resources/", "classpath:/static/", "classpath:/public/" 目錄中尋找名爲 "index.html" 的資源。

結論:

  • SpringBoot 中默認的歡迎頁爲 "classpath:/META-INF/resources/", "classpath:/resources/", "classpath:/static/", "classpath:/public/" 目錄下名爲的 "index.html" 的頁面。

頁面圖標

在 SpringMVC 配置類中還有一個頁面圖標配置類:

 1 @Configuration
 2 @ConditionalOnProperty(value = "spring.mvc.favicon.enabled", matchIfMissing = true)  // 默認開啓圖標顯示
 3 public static class FaviconConfiguration {
 4 
 5     private final ResourceProperties resourceProperties;
 6 
 7     public FaviconConfiguration(ResourceProperties resourceProperties) {
 8         this.resourceProperties = resourceProperties;
 9     }
10 
11     @Bean
12     public SimpleUrlHandlerMapping faviconHandlerMapping() {
13         SimpleUrlHandlerMapping mapping = new SimpleUrlHandlerMapping();
14         mapping.setOrder(Ordered.HIGHEST_PRECEDENCE + 1);
15         mapping.setUrlMap(Collections.singletonMap("**/favicon.ico",
16                 faviconRequestHandler()));
17         return mapping;
18     }
19 
20     @Bean
21     public ResourceHttpRequestHandler faviconRequestHandler() {
22         ResourceHttpRequestHandler requestHandler = new ResourceHttpRequestHandler();
23         requestHandler
24                 .setLocations(this.resourceProperties.getFaviconLocations());
25         return requestHandler;
26     }
27 
28 }
org.springframework.boot.autoconfigure.web.WebMvcAutoConfiguration.WebMvcAutoConfigurationAdapter.FaviconConfiguration

第 12 行的 FaviconConfiguration 方法即是用來處理圖標映射,在第 15 行爲匹配 "**/favicon.ico" 的請求路徑指定了圖標請求處理器 faviconRequestHandler() ,在第 24 行設置了圖標請求處理器尋找圖標的目錄爲 this.resourceProperties.getFaviconLocations() ,查看該方法:

 1 List<Resource> getFaviconLocations() {
 2     List<Resource> locations = new ArrayList<Resource>(
 3             this.staticLocations.length + 1);
 4     if (this.resourceLoader != null) {
 5         for (String location : this.staticLocations) {
 6             locations.add(this.resourceLoader.getResource(location));
 7         }
 8     }
 9     locations.add(new ClassPathResource("/"));
10     return Collections.unmodifiableList(locations);
11 }
org.springframework.boot.autoconfigure.web.ResourceProperties#getFaviconLocations

能夠看到,該方法返回的是靜態文件夾目錄資源。

結論:

  • 在 SpringBoot 工程中的靜態資源目錄放置一個名爲 "favicon.ico" 的網頁圖標,該圖標就會被 SpringBoot 使用。

模板引擎thymeleaf

thymeleaf中文離線文檔下載(提取碼:ip1g) | thymeleaf官網

引入

thymeleaf 是 SpringBoot 推薦使用的一款模板引擎框架,要引入很簡單,SpringBoot 爲它提供了場景啓動器:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>

切換版本

如 SpringBoot 1.5.19 版本使用的 thymeleaf 版本默認爲 2.1.6,若是想切換到 3.0 以上,直接覆蓋它的版本定義屬性便可,要注意的是須要同時更新它的佈局功能支持程序的版本:

<thymeleaf.version>3.0.2.RELEASE</thymeleaf.version>
<!--佈局功能支持程序,thymeleaf 使用 3.0 版本以上時支持程序要使用 2.0 以上-->
<thymeleaf-layout-dialect.version>2.1.1</thymeleaf-layout-dialect.version>

SpringBoot中使用

要在 SpringBoot 中使用 thymeleaf,能夠先看下 thymeleaf 的自動配置類:

@Configuration
@EnableConfigurationProperties(ThymeleafProperties.class)
@ConditionalOnClass(SpringTemplateEngine.class)
@AutoConfigureAfter(WebMvcAutoConfiguration.class)
public class ThymeleafAutoConfiguration {

查看它的屬性映射類:

@ConfigurationProperties(prefix = "spring.thymeleaf")
public class ThymeleafProperties {

    private static final Charset DEFAULT_ENCODING = Charset.forName("UTF-8");

    private static final MimeType DEFAULT_CONTENT_TYPE = MimeType.valueOf("text/html");

    public static final String DEFAULT_PREFIX = "classpath:/templates/";

    public static final String DEFAULT_SUFFIX = ".html";

一目瞭然,thymeleaf 默認使用的模板路徑爲 classpath:/templates/ ,且可省略後綴 .html ,下面咱們就開始在 SpringBoot 項目中使用 thymeleaf:

一、建立測試控制器:

package com.springboot.webdev1;

import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;

@Controller
public class TestController {

    @RequestMapping("test")
    public String test(Model model){
        // 傳值
        model.addAttribute("name", "張三");
        // SpringBoot 會找到 classpath:templates/test.html 使用 thymeleaf 渲染
        return "test";
    }
}
com.springboot.webdev1.TestController

二、新建模板頁面:

<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>test</title>
</head>
<body>
<h1 th:text="${name}"></h1>
</body>
</html>
templates/test.html

三、測試:

啓動項目,訪問 localhost:8080/test:

test

IDEA語法報錯解決

關閉 thymeleaf 的表達式語法檢查:

熱部署

這裏選用的是 Idea 工具進行操做,thymeleaf 的實時變動依賴於此 IDE。

一、導入依賴:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-devtools</artifactId>
</dependency>

二、開啓 Idea 的自動編譯,也能夠經過 Ctrl+F9 手動編譯:

三、事件設置,讓 thymeleaf 變動即時生效, Ctrl+Shift+A 打開時間對話框,選擇勾選以下:

標準表達式

變量表達式

thymeleaf 的變量表達式相似於 EL 表達式,經過 ${} 取值。

List<String> nameList = new ArrayList<>();
nameList.add("張三");
nameList.add("李四");
nameList.add("王五");

model.addAttribute("name", "張三");
model.addAttribute("nameList", nameList);
controller
<!--取值-->
<span th:text="${name}"></span>
<hr>
<!--循環-->
<ul>
    <li th:each="name : ${nameList}"><span th:text="${name}"/></li>
</ul>

html

選擇變量表達式

選擇變量表達式很像,不一樣它須要預先選擇一個對象做爲上下文變量容器。

public class User {
    public User(){}
    public User(String name, Integer age) {
        this.name = name;
        this.age = age;
    }

    private String name;

    private Integer age;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public Integer getAge() {
        return age;
    }

    public void setAge(Integer age) {
        this.age = age;
    }
}
com.springboot.webdev1.bean.User
model.addAttribute("user", new User("張三", 18));
controller
<div th:object="${user}">
    <p>姓名:<span th:text="*{name}"></span></p>
    <p>年齡:<span th:text="*{age}"></span></p>
</div>
html

URL表達式

URL 表達式能夠幫助咱們更輕鬆的動態拼裝請求 URL。

<!--() 中能夠指定要傳遞的參數-->
<span th:text="@{/order/details(type=1,keyword=ff)}"></span>
html

表達式支持語法

字面量

文本文字 : 'one text', 'Another one!',…
數字文本 : 0, 34, 3.0, 12.3,…
布爾文本 : true, false
空 : null
文字標記 : one, sometext, main,…

文本操做

字符串鏈接 : +
文本替換 : |The name is ${name}|

算術運算

二元運算符 : +, -, *, /, %
減號(單目運算符) : -

布爾操做

二元運算符 : and, or
布爾否認(一元運算符) : !, not

比較

比較 : >, <, >=, <= (gt, lt, ge, le)
等值運算符 :==, != (eq, ne)

條件運算

If-then : (if) ? (then)  # 例:<span th:text="${name} == '張三' ? 'Administrator'"/>
If-then-else : (if) ? (then) : (else)  # 例:<span th:text="${name} == '張三' ? 'Administrator' : (${name} ?: 'Unknown')"/>
Default : (value) ?: (defaultvalue)  # 例:<span th:text="${name} ?: 'Unknown'"/>

經常使用標籤

關鍵字 功能介紹 案例
th:id 替換id  <input th:id="'xxx' + ${collect.id}"/> 
th:text 文本替換  <p th:text="${collect.description}">description</p> 
th:utext 支持html的文本替換  <p th:utext="${htmlcontent}">conten</p> 
th:object 替換對象  <div th:object="${session.user}"> 
th:value 屬性賦值  <input th:value="${user.name}" /> 
th:with 變量賦值運算  <div th:with="isEven=${prodStat.count}%2==0"></div> 
th:style 設置樣式  <span th:style="'display:' + @{(${sitrue} ? 'none' : 'inline-block')} + ''"/>  
th:onclick 點擊事件  <button th:onclick="'getCollect()'"></button> 
th:each 屬性賦值  <tr th:each="user,userStat:${users}"></tr>  
th:if 判斷條件  <a th:if="${userId == collect.userId}" > 
th:unless 和th:if判斷相反
<a th:href="@{/login}" th:unless=${session.user != null}>Login</a>
th:href 連接地址
<a th:href="@{/login}" th:unless=${session.user != null}>Login</a>
th:switch 多路選擇 配合th:case 使用  <div th:switch="${user.role}"> 
th:case th:switch的一個分支  <p th:case="'admin'">User is an administrator</p> 
th:fragment 佈局標籤,定義一個代碼片斷,方便其它地方引用  <div th:fragment="alert"> 
th:include 佈局標籤,替換內容到引入的文件  <head th:include="layout :: htmlhead" th:with="title='xx'"></head> /> 
th:replace 佈局標籤,替換整個標籤到引入的文件  <div th:replace="fragments/header :: title"></div> 
th:selected selected選擇框 選中  <option th:selected="(${xxx.id} == ${configObj.dd})"></option> 
th:src 圖片類地址引入  <img class="img-responsive" alt="App Logo" th:src="@{/img/logo.png}" /> 
th:inline 定義js腳本可使用變量  <script type="text/javascript" th:inline="javascript"> 
th:action 表單提交的地址  <form action="subscribe.html" th:action="@{/subscribe}"> 
th:remove 刪除某個屬性

 <tr th:remove="all">  

1.all:刪除包含標籤和全部的孩子。2.body:不包含標記刪除,但刪除其全部的孩子。3.tag:包含標記的刪除,但不刪除它的孩子。4.all-but-first:刪除全部包含標籤的孩子,除了第一個。5.none:什麼也不作。這個值是有用的動態評估。

th:attr 設置標籤屬性,多個屬性能夠用逗號分隔

 <img th:attr="src=@{/image/aa.jpg},title=#{logo}"/> 此標籤不太優雅,通常用的比較少。

一個標籤內能夠包含多個th:x屬性,其生效優先級順序以下:

include、each、if/unless/switch/case、with、attr、attrprepend、attrappend、value、href、src、etc、text、utext、fragment、remove

經常使用操做

字符串拼接

<!--使用 + 號-->
<span th:text="'Welcome to our application, ' + ${name} + '!'"/> <br>
<!--使用 | 進行字符串格式化-->
<span th:text="|Welcome to our application, ${name}!|"/>

條件判斷

<span th:if="${name}=='張三'">是張三</span>
<span th:unless="${name}=='張三'">不是張三</span>
<span th:text="${name} ?: 'Unknown'"/>
<span th:text="${name} == '張三' ? 'Administrator'"/>
<span th:text="${name} == '張三' ? 'Administrator' : (${name} ?: 'Unknown')"/>
<div th:switch="${name}"> <span th:case="張三">name 爲張三</span> <span th:case="李四">name 爲李四</span> </div>

循環

<ul>
    <li th:each="name,iterStat : ${nameList}" th:text="${iterStat.count} + ':'+ ${name}"></li>
</ul>
<!--
iterStat稱做狀態變量,屬性有:
    index:當前迭代對象的index(從0開始計算)
    count: 當前迭代對象的index(從1開始計算)
    size:被迭代對象的大小
    current:當前迭代變量
    even/odd:布爾值,當前循環是不是偶數/奇數(從0開始計算)
    first:布爾值,當前循環是不是第一個
    last:布爾值,當前循環是不是最後一個
-->

組裝URL

<!--() 中能夠指定要傳遞的參數-->
<form th:action="@{/order/details(type=1,keyword=ff)}" ></form>
<!--上述對應的 URL 爲 /order/details?type=1&keyword=ff-->

經常使用內置對象

thymeleaf 爲咱們提供了不少內置對象,經過 ${#內置對象名稱} 便可訪問到,下面列出一些比較經常使用的:

內置對象 做用 示例
dates 日期操做
<span th:text="${#dates.format(currentDate,'yyyy-MM-dd HH:mm:ss')}"/>
<!--格式化日期-->
numbers 數字格式化
<span th:text="${#numbers.formatDecimal(13.213, 0, 2)}"></span>
<!--此示例表示保留兩位小數位,整數位自動  結果 13.21-->
<span th:text="${#numbers.formatDecimal(13.213, 3, 2)}"></span>
<!--此示例表示保留兩位小數位,3位整數位(不夠的前加0) 結果 013.21-->
lists 列表操做
<p th:text="${#lists.size(nameList)}"/>
<!--獲取列表長度-->
calendars 日曆操做
<p th:text="${#calendars.format(#calendars.createNow(),'yyyy-MM-dd HH:mm:ss')}"></p>
<!--格式化日期,與 #dates 類似-->
strings 字符串操做 
<p th:text="${#strings.startsWith('abcde','aab')}"/>
<!--判斷字符串是否以指定字符串開頭-->
objects 對象操做
<p th:text="${#objects.nullSafe(name,'Unknown')}"></p>
<!--判斷指定對象是否爲空,若是是空則返回指定默認值,不然原樣返回-->
bools 布爾值操做
<p th:text="${#bools.isFalse(1>2)}">aa</p>
<!--判斷一個表達式結果是否爲假-->
arrays 數組操做
<p th:text="${#arrays.isEmpty(testArr)}"></p>
<!--判斷一個數組是否爲空-->
sets 集合操做
<p th:text="${#sets.size(set)}"></p>
<!--獲取一個集合中元素個數-->
maps 地圖操做
<p th:text="${#maps.containsKey(map,'key1')}"></p>
<!--判斷一個 Map 中是否存在指定 key-->
aggregates 統計運算
<p th:text="${#aggregates.avg(numArr)}"></p>
<!--計算一個數組中的平均值-->
messages 屬性文件取值 
<p th:text="${#messages.msg('hahah')}"/>
<!--取一個屬性文件中的屬性值,至關於 <p th:text="#{hahah}"/>-->
convertions 類型轉換
<p th:text="${#conversions.convert('213','java.lang.Integer')+23}"></p>
<!--將一個字符串轉成 Integer 類型-->
execInfo 模板信息
<p th:text="${#execInfo.getTemplateName()}"></p>
<!--獲取運行時當前模板名稱-->
request 請求對象
<p th:text="${#request.method}"></p>
<!--經過請求對象獲取當前的請求方法 #httpServletRequest 與之相同-->
response 響應對象
<p th:text="${#response.getWriter().write('aaa')}"/>
<!--經過相應對象輸出字符串 -->
session 會話對象
<p th:text="${#session.getId()}"></p>
<!--經過會話對象獲取當前會話id #httpSession 與之相同-->

佈局

介紹

在 web 開發中,咱們常常會將公共頭,公共尾,菜單等部分提取成模板供其它頁面使用。在 thymeleaf 中,經過 th:fragment、th:include、th:replace、參數化模板配置、css 選擇器加載代碼塊等實現。

依賴

Spring Boot 2.0 將佈局單獨提取了出來,須要單獨引入依賴:thymeleaf-layout-dialect。

<dependency>
    <groupId>nz.net.ultraq.thymeleaf</groupId>
    <artifactId>thymeleaf-layout-dialect</artifactId>
</dependency>

選擇器使用

一、定義模板:

<div class="header">
    這是頭部
</div>
templates/common/header.html
<div class="body">
    這是主體
</div>
templates/common/body.html
<div class="footer">
    這是尾部
</div>
templates/common/footer.html

二、引用模板:

<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>模板測試</title>
</head>
<body>
    <!--insert 會將全部選擇的標籤及內容插入到當前標籤內-->
    <div class="layout_header" th:insert="common/header :: .header"></div>
    <!--replace 會讓選擇的標籤替換當前的標籤-->
    <div class="layout_body" th:replace="common/body :: .body"></div>
    <!--include 會將選擇的標籤內容插入到當前標籤內-->
    <div class="layout_footer" th:include="common/footer :: .footer"></div>
</body>
</html>

templates/layout.html

fragment使用

一、定義模板塊:

<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>fragment Test</title>
</head>
<body>
<!--fragment 定義用於被加載的塊-->
<span th:fragment="copy">msg from fragment</span>
<!--定義能接收參數的塊-->
<span th:fragment="sayHello(msg, name)">[[|${msg} ${name}|]]</span>
</body>
</html>
templates/common/fragment.html

二、使用模板塊:  

<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>模板測試</title>
</head>
<body>
<div th:include="common/fragment::copy"></div>
<div th:include="common/fragment::sayHello('hello','bob')"></div>
</body>
</html>

templates/layout.html
其中  th:include 、  th:insert 、  th:replace 中的參數格式爲  templatename::[domselector] ,其中  templatename 是模板名(如  footer ),  domselector 是可選的 dom 選擇器。若是隻寫  templatename ,不寫  domselector ,則會加載整個模板。
相關文章
相關標籤/搜索