SpringBoot之web開發引入專案登入頁面國際化登入攔截器RESTfulthymeleaf對公共頁面元素抽取列表 CRUD錯誤處理機制引入專案把html頁面放在模板引擎資料夾templates下,這樣能使用模板引擎的功能。登入頁面國際化國際化:編寫國際化配置檔案1.編寫國際化配置檔案,抽取頁面需要顯示的國際化訊息2.SpringBoot自動配置好了管理國際化資原始檔的元件
@Bean @ConfigurationProperties( prefix = "spring.messages" ) public MessageSourceProperties messageSourceProperties() { return new MessageSourceProperties(); } @Bean public MessageSource messageSource(MessageSourceProperties properties) { /* * ResourceBoundleMessageSource extends AbstractResourceBasedMessageSource implements BeanClassLoaderAware * 該實現類允許使用者透過beanName指定一個資源名:包括類路徑的全限定資源名 * 或者透過beanName指定一組資源名 */ ResourceBundleMessageSource messageSource = new ResourceBundleMessageSource(); if (StringUtils.hasText(properties.getBasename())) { /* * setBasenames 設定國際化資原始檔去掉語言國家程式碼的基礎名, * 國際化資原始檔可以直接放在類路徑下叫 messages.properties, * 也可以在配置檔案中指定基礎名 spring.messages.basename */ String basename = context.getEnvironment().getProperty("spring.messages.basename", "messages"); messageSource.setBasenames(StringUtils.commaDelimitedListToStringArray(StringUtils.trimAllWhitespace(properties.getBasename()))); } if (properties.getEncoding() != null) { messageSource.setDefaultEncoding(properties.getEncoding().name()); } /* * 如果沒有找到特定語言環境的檔案,是否返回系統區域設定 * 預設為true * 如果是關閉的,將會使用唯一的預設檔案:比如baseName的“message”的 message.properties */ messageSource.setFallbackToSystemLocale(properties.isFallbackToSystemLocale()); Duration cacheDuration = properties.getCacheDuration(); if (cacheDuration != null) { messageSource.setCacheMillis(cacheDuration.toMillis()); } /* * 設定是否始終應用訊息格式元件,解析沒有引數的訊息 * 比如:MessageFormat希望單引號被轉義為""", * 如果訊息文字全部使用這樣的轉義編寫,即使沒有定義引數佔位符,也需要將此標誌設為true * 否則,只有具有實際意義的引數訊息文字才會用MessageFormat的轉義來編寫 */ messageSource.setAlwaysUseMessageFormat(properties.isAlwaysUseMessageFormat()); /* * 是否使用訊息程式碼作為預設訊息,而不是丟擲NoSuchMessageException異常, * 適用於開發和除錯,預設值為false */ messageSource.setUseCodeAsDefaultMessage(properties.isUseCodeAsDefaultMessage()); return messageSource; }
MessageSource解析:
MessageSource架構圖:MessageSource: 抽象化的訊息介面HierarchicalMessageSource: 分層的訊息源介面,可獲取父訊息源MessageSourceSupport: 訊息源解析的抽象類,透過指定"訊息格式化元件MessageFormat"格式化訊息DelegatingMessageSource: 訊息源解析委派類. 使用者未指定訊息源解析類時,SpringContext預設使用這個類. 功能比較簡單:將字串和引數陣列格式化為一個訊息字串AbstractMessageSource: 支援"配置檔案"的方式國際化資源的抽象類. 內部提供一個與區域設定無關的公共訊息配置檔案,訊息程式碼為關鍵字StaticMessageSource: 主要用於程式測試. 允許透過程式設計的方式提供國際化資訊ResourceBundleMessageSource: 該實現類允許使用者透過beanName指定一個資源名,包括類的全限定資源名. 或者透過beanName指定一組資源名. 不同的區域獲取載入不同資原始檔,以達到國際化的目的ReloadableResourceBundleMessageSource:ReloadableResourceBundleMessageSource和ResourceBundleMessageSource的區別:載入資源型別及方式:ReloadResourceBundleMessageSource依託Spring的ResourceLoader載入Resource資源,功能更加強大,支援 .class和 .properties檔案ResourceBundleMessageSource依託JDK自帶的ResourceBundle載入資源,支援絕對路徑和工程路徑,支援 .class和 .properties檔案快取時間:ReloadResourceBundleMessageSource每次載入都會記錄每個資源載入的時間點,在快取資源過期後會再次比較檔案的修改時間,如果不變則不需要載入,同時重新整理本次載入時間點ResourceBundleMessageSource主要利用ResourceBundle.Control實現簡單的自動載入編碼方式:ReloadResourceBundleMessageSource不僅可以指定統一的預設編碼方式,也同時支援為每個檔案單獨制定編碼方式
MessageSource介面:
方法描述String getMessage(String code, Object[] args, String defaultMessge, Locale locale)獲取訊息,如果沒有找到訊息,就返回預設值String getMessage(String code, Object[] args, Locale locale) throws NoSuchMessageException獲取訊息,如果無法找到訊息,則視為錯誤String getMessage(MessageSourceResolvable resolvable, Locale locale) throws NoSuchMessageException嘗試使用傳入的{@code MessageSourceResolvable}引數中包含的所有屬性來解析訊息. 必須在此方法上丟擲{@code NoSuchMessageException}, 因為在呼叫此方法時,無法確定可解析的{@code defaultMessage}屬性是否為空
MessageSourceResolvable解析訊息要素的包裝介面和類:
方法描述String[] getCode()返回用於解決此訊息的程式碼,按照這些程式碼應該嘗試的順序. 因此,最後的一個程式碼將是預設程式碼Object[] getArguments()返回要用於解析此訊息的引數陣列String getDefaultMessage()返回要用於解析此訊息的預設訊息
HierarchicalMessageSource訊息源分層介面:
方法描述void setParentMessageSource(MessageSource parent)設定將用於解決次物件無法解析的訊息的父級引數parent是將用於解析此物件無法解析的訊息的父MessageSource.可能是{@code null},在這種情況下不需要解決MessageSource getParentMessageSource()返回當前MessageSource的父級,否則返回{@Code null}
MessageSourceSupport用於支援訊息源解析的抽象類:
方法描述void setAlwaysUseMessageFormat(boolean alwaysUseMessageFormat)設定是否始終應用訊息格式元件,解析沒有引數的訊息比如: MessageFromat希望單引號轉義為"""如果訊息文字全部用這樣的轉義編寫,即使沒有定義引數佔位符,只需要將此標誌設為"true"否則,只有具有實際引數的訊息文字才會用MessageFormat轉義類編寫boolean isAlwaysUseMessageFormat()返回是否應用訊息格式元件,解析沒有引數的訊息String renderDefaultMessage(String defaultMessage, Object[] args, Locale locale)渲染給定的預設訊息字串String formatMessage(String msg, Object[] args, Locale locale)渲染給定的訊息字串MessageFormat createMessageFormat(String msg, Locale locale)為給定的訊息和區域設定建立一個MessageFormat
DelegatingMessageSource訊息源解析委派類:
方法描述String getMessage(String code, Object[] args, String defaultMessage, Locale locale)解析訊息父訊息解析源不為null時,則採用父訊息源解析訊息.否則使用自身訊息源解析訊息String getMessage(String code, Object[] args, Locale locale) throws NoSuchMessageException解析訊息如果父訊息解析源不為null時,則採用父訊息源解析訊息,否則丟擲異常String getMessage(MessageSourceResolvable resolvable, Locale locale) throws NoSuchMessageException解析訊息如果父訊息解析源不為null時,則採用父訊息源解析訊息,否則使用自身訊息源解析訊息
AbstractMessageSourc抽象類Spring中支援配置檔案的方式國際化資源的抽象類:
方法描述void setUseCodeAsDafaultMessage(boolean useCodeAsDefaultMessage)設定是否使用訊息程式碼作為預設訊息,而不是丟擲NoSuchMessageException.預設為falseString getMessageInternal(String code, Object[] args, Locale locale)將給定的程式碼和引數解析為給定的區域中設定的訊息,如果沒有找到則返回{@code null}String getMessageFromPArent(String code, Object[] args, Locale locale)如果父MessageSource中存在訊息則嘗試從父MessageSource檢索給定的訊息String getDefaultMessage(String code)返回預設訊息Object[] resolveArgements(Object[] args, Locale locale)透過給定的引數陣列搜尋,找到MessageSourceResolve物件並解析String resolveCodeWithoutArguments(String code, Locale locale)解析不帶引數的訊息
StaticMessageSource是AbstractMessageSource允許透過程式設計的方式提供國際化資訊:
方法描述void addMessage(String code, Locale locale, String msg)將給定的訊息與給定的程式碼相關聯void addMessage(Map<String, String> messages, Locale locale)批次將給定的訊息與給定的程式碼相關聯
ResourceBundleMessageSource是AbstractMessageSource的實現類,允許使用者透過beanName指定一個資源名- 包括類路徑的全限定名, 或者透過beanNames指定一組資源名:
方法描述void setBaseName(String basename)設定資原始檔void setBaseNames(String… basenames)批次設定資原始檔void setDefaultEncoding(String defaultEncoding)設定用於解析繫結的資原始檔的預設字符集void setFallBackToSystemLocale(boolean fallbackToSystemLocale)如果沒有找到特定語言環境的檔案,是否返回到系統區域設定預設為true. 如果為false,唯一的備用檔案將是預設檔案void setCacheSeconds(int cacheSeconds)設定快取載入繫結的資原始檔的秒數String resolveCodeWithoutArguments(String code, Locale locale)將給定的訊息程式碼解析為已註冊資源包中的key,按照原樣返回捆綁包中的值,不使用MessageFormat解析MessageFormat resolveCode(String code, Locale locale)將給定的訊息程式碼解析為註冊資源包中的key,每個訊息程式碼使用快取的MessageFormat例項ResourceBundle getResourceBundle(String baseName, Locale locale)為給定的baseName和程式碼返回一個ResourceBundle,從快取中提取已生成的MessageFormatResourceBundle doGetBundle(String baseName, Locale locale) throws MissingResourceException獲取給定baseName和locale設定的資源包MessageFormat getMessageFormat(ResourceBundle resourceBundle, String code, Locale locale) throws Missing ResourceException為給定的包和程式碼返回一個MessageFormat,從快取中提取已生成的MessageFormatsString getStringOrNull(ResourceBundle resourceBundle, String key)獲取資源包中指定key所對應的值
ReloadableResourceBundleMessageSource實現類允許使用者透過beanName指定一個資源名,包括類路徑和全限定名.或者透過beanNames指定一組資源名:
方法描述String resolveCodeWithoutArguments(String code, Locale locale)將訊息程式碼解析為檢索到的包檔案中的key,按原樣返回包中找到的值,不使用MessageFormat解析MessageFormat resolveCode(String code, Locale locale)將給定的訊息程式碼解析為檢索到的包檔案中的key,每個訊息程式碼使用快取的MessageFormat例項PropertiesHolder getMergedProperties(Locale locale)獲取locale所對應的持有properties物件List< String > calculateAllFilenames(String basename, Locale locale)計算給定的捆綁包基礎名稱和區域設定的所有檔名將計算給定區域設定的檔名,系統區域設定預設檔案List < String > calculateFilenamesForLocale(String basename, Locale locale)計算給定捆綁基礎包名稱和區域設定的檔名Properties loadProperties(Resource resource, String filename)解析給定的resource資源,返回對應的properties物件void clearCache()清除所有資源包對應的properties檔案void clearCacheIncludingAncestors()清除當前MessageSource及所有父資源的快取MessageFormat訊息元件格式化: 主要就是將訊息串,引數格式化成字串
3.在頁面獲取國際化的值
標籤體中:th:text="#{}"th:placeholder="#{}"非標籤體,行內表示式[[#{}]]
國際化原理:國際化中Locale(區域資訊物件);LocaleResolver(獲取區域資訊物件)
@Bean @ConditionalOnMissingBean @ConditionalOnProperty( prefix = "spring.mvc", name = {"locale"} ) // 預設的區域資訊解析器就是根據請求頭的區域資訊獲取Locale進行國際化解析 public LocaleResolver localeResolver() { if (this.mvcProperties.getLocaleResolver() == org.springframework.boot.autoconfigure.web.servlet.WebMvcProperties.LocaleResolver.FIXED) { return new FixedLocaleResolver(this.mvcProperties.getLocale()); } else { AcceptHeaderLocaleResolver localeResolver = new AcceptHeaderLocaleResolver(); localeResolver.setDefaultLocale(this.mvcProperties.getLocale()); return localeResolver; } } public Locale resolveLocale(HttpServletRequest request) { Locale defaultLocale = this.getDefaultLocale(); if (defaultLocale != null && request.getHeader("Accept-Language") == null) { return defaultLocale; } else { Locale requestLocale = request.getLocale(); List<Locale> supportedLocales = this.getSupportedLocales(); if (!supportedLocales.isEmpty() && !supportedLocales.contains(requestLocale)) { Locale supportedLocale = this.findSupportedLocale(request, supportedLocales); if (supportedLocale != null) { return supportedLocale; } else { return defaultLocale != null ? defaultLocale : requestLocale; } } else { return requestLocale; } } }
登入開發期間模板引擎修改以後,要想能夠實時生效1.禁用模板引擎快取-spring.thymeleaf.cache=false2.頁面修改完以後ctrl+F9,進行重新編譯登入錯誤訊息的顯示th:text="${msg}" th:if="${not #strings.isEmpty(msg)}"
攔截器透過攔截器進行登入檢查RESTful普通 CRUD:URI,/資源名稱/資源標識RESTful CRUD:以HTTP請求方式區分對資源的CRUD操作
普通 CRUD(URI來區分操作) |
RESTful CRUD | |||
查詢 |
getEmp |
emp–GET | ||
新增 |
addEmp?xxx |
emp–POST | ||
修改 |
updateEmp?id=xx&xxx |
emp/{id}–PUT |
deleteEmp?id=xx |
emp/{id}–DELETE |
請求URI | 請求方式 | |||
查詢所有員工 |
emps |
GET | ||
查詢某個員工(來到修改頁面) |
emp/{id} |
GET | ||
進入新增頁面 |
emp |
GET | ||
新增員工 |
emp |
POST | ||
進入修改頁面(查出員工資訊進行回顯) |
emp/{id} |
GET | ||
修改員工 |
emp/{id} |
PUT |
emp/{id} |
DELETE |
<div th:fragment="copy"></div>
引入公共片段
<div th:insert="~{footer :: copy}"></div>引入公共片段的兩種方式:~{templatename::selector} 模板名::選擇器~{templatename::fragmentname} 模板名::片段名其中模板名(公共片段來源的檔名)會使用thymeleaf的前後綴配置規則進行解析
引入公共片段的th屬性:1.th:insert -將公共片段整個插入到宣告引入的元素中2.th:replace-將宣告引入的元素替換為公共片段3.th:include-將被引入的片段的內容包含進這個標籤中<div th:insert="footer :: copy"></div><div th:replace="footer :: copy"></div><div th:include="footer :: copy"></div>
使用th屬性進行引入公共片段時,可以不用寫 ~ {},只有行內寫法[[~ {}]],[(~{})]要寫列表 CRUD
C:
redirect:表示重定向到一個地址 / 代表當前專案路徑forward:表示轉發到一個地址SpringMVC自動將請求引數和入參物件的屬性進行一一繫結.要求就是請求引數的名字name和JavaBean入參的物件裡的屬性名一致.問題:提交的資料格式不對:生日日期==日期格式化:SpringMVC將頁面提交的資料需要轉換為指定的型別.U:
請求URI和資料id透過 + 拼接字串頁面傳送PUT請求:1.在SpringMVC中配置HiddenHttpMethodFilter,可以修改頁面請求,SpringBoot已經自動配置好2.頁面建立一個POST表單3.建立一個input項,name="_method";值就是指定的請求方式錯誤處理機制SpringBoot預設的錯誤處理機制1.瀏覽器訪問時,返回一個預設的錯誤頁面:錯誤狀態碼,錯誤型別,錯誤提示資訊,錯誤時間.瀏覽器傳送請求的請求頭: text.html.2.如果是其它客戶端訪問,返回預設的一個json資料客戶端傳送請求的請求頭:/*3.原理:可以參照ErrorMvcAutoConfiguration給容器中添加了如下元件:1.DefaultErrorAttributes:在頁面共享錯誤資訊public Map<String, Object> getErrorAttributes(WebRequest webRequest, boolean includeStackTrace) { Map<String, Object> errorAttributes = new LinkedHashMap(); errorAttributes.put("timestamp", new Date()); this.addStatus(errorAttributes, webRequest); this.addErrorDetails(errorAttributes, webRequest, includeStackTrace); this.addPath(errorAttributes, webRequest); return errorAttributes; }
2.BasicErrorController:處理預設/error請求
@Controller@RequestMapping({"${server.error.path:${error.path:/error}}"})public class BasicErrorController extends AbstractErrorController { private final ErrorProperties errorProperties; public BasicErrorController(ErrorAttributes errorAttributes, ErrorProperties errorProperties) { this(errorAttributes, errorProperties, Collections.emptyList()); } public BasicErrorController(ErrorAttributes errorAttributes, ErrorProperties errorProperties, List<ErrorViewResolver> errorViewResolvers) { super(errorAttributes, errorViewResolvers); Assert.notNull(errorProperties, "ErrorProperties must not be null"); this.errorProperties = errorProperties; } public String getErrorPath() { return this.errorProperties.getPath(); } @RequestMapping( produces = {"text/html"} ) //產生html資料,處理瀏覽器傳送的請求 public ModelAndView errorHtml(HttpServletRequest request, HttpServletResponse response) { HttpStatus status = this.getStatus(request); Map<String, Object> model = Collections.unmodifiableMap(this.getErrorAttributes(request, this.isIncludeStackTrace(request, MediaType.TEXT_HTML))); response.setStatus(status.value()); // 去哪個頁面作為錯誤頁面,包含頁面地址和頁面內容 ModelAndView modelAndView = this.resolveErrorView(request, response, status, model); return modelAndView != null ? modelAndView : new ModelAndView("error", model); } @RequestMapping //產生json資料,處理其它客戶端請求 public ResponseEntity<Map<String, Object>> error(HttpServletRequest request) { Map<String, Object> body = this.getErrorAttributes(request, this.isIncludeStackTrace(request, MediaType.ALL)); HttpStatus status = this.getStatus(request); return new ResponseEntity(body, status); }
3.ErrorPageCustomizer:系統出現4開頭和5開頭的錯誤,該元件生效,定製錯誤響應規則.就會來到/error請求.
@Value("${error.path:/error}") private String path = "/error"; //系統出現錯誤以後來到error請求進行處理
4.DefaultErrorViewResolver:
public ModelAndView resolveErrorView(HttpServletRequest request, HttpStatus status, Map<String, Object> model) { ModelAndView modelAndView = this.resolve(String.valueOf(status.value()), model); if (modelAndView == null && SERIES_VIEWS.containsKey(status.series())) { modelAndView = this.resolve((String)SERIES_VIEWS.get(status.series()), model); } return modelAndView; } private ModelAndView resolve(String viewName, Map<String, Object> model) { // 預設SpringBoot可以找到頁面-error/404 String errorViewName = "error/" + viewName; // 如果模板引擎可以解析這個頁面地址就使用模板引擎解析 TemplateAvailabilityProvider provider = this.templateAvailabilityProviders.getProvider(errorViewName, this.applicationContext); // 模板引擎可用的話返回到errorViewName指定的檢視地址;如果模板引擎不可用,就在靜態資原始檔夾下找errorViewName對應的頁面.假如靜態資原始檔夾沒有對應的頁面則返回null return provider != null ? new ModelAndView(errorViewName, model) : this.resolveResource(errorViewName, model); }
錯誤處理步驟:
系統出現4開頭和5開頭的錯誤,該元件生效,定製錯誤響應規則.就會來到/error請求,就會被BasicErrorController處理.響應頁面:去哪個頁面是由DefaultErrorViewResolver解析得到的 protected ModelAndView resolveErrorView(HttpServletRequest request, HttpServletResponse response, HttpStatus status, Map<String, Object> model) { Iterator var5 = this.errorViewResolvers.iterator(); ModelAndView modelAndView; do { if (!var5.hasNext()) { return null; } ErrorViewResolver resolver = (ErrorViewResolver)var5.next(); modelAndView = resolver.resolveErrorView(request, status, model); } while(modelAndView == null); return modelAndView; }
如何定製錯誤響應如何定製錯誤頁面模板引擎有的情況下:1.error/錯誤狀態碼,只要將錯誤頁面命名為"錯誤狀態碼.html"放在模板引擎資料夾裡的error資料夾下,發生此狀態碼的錯誤就會來到對應的頁面2.可以使用4xx和5xx作為錯誤頁面的檔名來匹配這種型別的所有錯誤 - 精確優先,即優先尋找精確的錯誤狀態碼.html3.頁面能獲取哪些資訊:timstamp: 時間戳status: 狀態碼error: 錯誤提示exception: 異常物件message: 異常訊息errors: JSR303資料校驗錯誤模板引擎沒有的情況下:1.模板引擎找不到錯誤頁面,就在靜態資原始檔夾下找模板引擎沒有,靜態資原始檔夾也沒有的情況下:1.預設來到SpringBoot的錯誤提示頁面如何定製錯誤的json資料:1.自定義異常處理並返回定製的json資料@ControllerAdvicepublic class MyExceptionHandler { //沒有自適應效果-瀏覽器和客戶端都是返回的json資料 @ResponseBody @ExceptionHandler(RuntimeException.class) public Map<String,Object> handleException(Exception e){ Map<String,Object> map=new HashMap<>(); map.put("code","執行異常"); map.put("message",e.getMessage()); return map; }}
2.轉發到forward:/error進行自適應響應效果處理
@ControllerAdvicepublic class MyExceptionHandler { @ExceptionHandler(RuntimeException.class) public String handleException(Exception e, HttpServletRequest request){ Map<String,Object> map=new HashMap<>(); // 傳入自己的錯誤狀態碼,否則就不會進入定製錯誤頁面的解析流程--------Integer statusCode = (Integer)request.getAttribute("javax.servlet.error.status_code"); request.setAttribute("javax.servlet.error.status_code","500"); map.put("code","執行異常"); map.put("message",e.getMessage()); //轉發到/error,實現自適應效果 return "forward:/error"; }}
3.將定製資料攜帶出去:出現錯誤以後,會來到/error請求,這個請求會被BasicErrorController處理,響應的資料是由getErrorAttributes(由AbstractErrorController(ErrorController)規定的方法)得到的
可以編寫一個繼承AbstractErrorController的子類實現類,放在容器中頁面上能用的資料,json上返回的資料都是透過errorAttributes.getErrorAttributes得到的,也就是容器中DefaultErrorAttributes.getErrorAttributes()進行資料處理的響應是自適應的,可以透過定製ErrorAtrributes改變需要返回的內容.@Componentpublic class MyErrorAttributes extends DefaultErrorAttributes { //返回值map是頁面和json能獲取的所有欄位 @Override public Map<String, Object> getErrorAttributes(WebRequest webRequest, boolean includeStackTrace) { Map<String, Object> map=super.getErrorAttributes(webRequest, includeStackTrace); map.put("company","oxford"); //異常處理器攜帶的資料 Map<String,Object> ext=(Map<String, Object>) webRequest.getAttribute("ext",0); map.put("ext",ext); return map; }}