微服務架構Day04-SpringBoot之web開發
引入項目
- 把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.默認為false String 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,從快取中提取已生成的MessageFormat ResourceBundle doGetBundle(String baseName, Locale locale) throws MissingResourceException 獲取給定baseName和locale設置的資源包 MessageFormat getMessageFormat(ResourceBundle resourceBundle, String code, Locale locale) throws Missing ResourceException 為給定的包和程式碼返回一個MessageFormat,從快取中提取已生成的MessageFormats String 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=false
2.頁面修改完以後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 |
thymeleaf對公共頁面元素抽取
- 抽取公共片段
<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作為錯誤頁面的文件名來匹配這種類型的所有錯誤 – 精確優先,即優先尋找精確的錯誤狀態碼.html
3.頁面能獲取哪些資訊:- timstamp: 時間戳
- status: 狀態碼
- error: 錯誤提示
- exception: 異常對象
- message: 異常消息
- errors: JSR303數據校驗錯誤
- 模板引擎沒有的情況下:
1.模板引擎找不到錯誤頁面,就在靜態資源文件夾下找 - 模板引擎沒有,靜態資源文件夾也沒有的情況下:
1.默認來到SpringBoot的錯誤提示頁面
- 模板引擎有的情況下:
- 如何訂製錯誤的json數據:
1.自定義異常處理並返回訂製的json數據
- 如何訂製錯誤頁面
@ControllerAdvice
public 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進行自適應響應效果處理
@ControllerAdvice
public 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改變需要返回的內容.
@Component
public 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;
}
}