查看 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 }
從第 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 }
查看 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; } } }
即 16-24 行就是將能匹配 "/**" 的請求路徑映射到項目路徑下 "classpath:/META-INF/resources/", "classpath:/resources/", "classpath:/static/", "classpath:/public/" 中。web
結論:spring
依舊是 SpringMVC 配置類中,有一個註冊歡迎頁映射 bean 的方法:express
1 @Bean 2 public WelcomePageHandlerMapping welcomePageHandlerMapping( 3 ResourceProperties resourceProperties) { 4 return new WelcomePageHandlerMapping(resourceProperties.getWelcomePage(), 5 this.mvcProperties.getStaticPathPattern()); 6 }
查看 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 }
接着查看 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 }
即 resourceProperties.getWelcomePage() 方法默認就是從靜態資源目錄下即 "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 }
第 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 }
能夠看到,該方法返回的是靜態文件夾目錄資源。
結論:
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 中使用 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"; } }
二、新建模板頁面:
<!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>
三、測試:
啓動項目,訪問 localhost:8080/test:
關閉 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);
<!--取值--> <span th:text="${name}"></span> <hr> <!--循環--> <ul> <li th:each="name : ${nameList}"><span th:text="${name}"/></li> </ul>
選擇變量表達式很像,不一樣它須要預先選擇一個對象做爲上下文變量容器。
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; } }
model.addAttribute("user", new User("張三", 18));
<div th:object="${user}"> <p>姓名:<span th:text="*{name}"></span></p> <p>年齡:<span th:text="*{age}"></span></p> </div>
URL 表達式能夠幫助咱們更輕鬆的動態拼裝請求 URL。
<!--() 中能夠指定要傳遞的參數--> <span th:text="@{/order/details(type=1,keyword=ff)}"></span>
文本文字 : '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 | 刪除某個屬性 |
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:布爾值,當前循環是不是最後一個 -->
<!--() 中能夠指定要傳遞的參數--> <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>
<div class="body"> 這是主體 </div>
<div class="footer"> 這是尾部 </div>
二、引用模板:
<!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>
一、定義模板塊:
<!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>
二、使用模板塊:
<!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>