微服務架構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解析:html

  • MessageSource架構圖:
    在這裏插入圖片描述
    • MessageSource: 抽象化的消息接口
    • HierarchicalMessageSource: 分層的消息源接口,可獲取父消息源
    • MessageSourceSupport: 消息源解析的抽象類,經過指定"消息格式化組件MessageFormat"格式化消息
    • DelegatingMessageSource: 消息源解析委派類. 用戶未指定消息源解析類時,SpringContext默認使用這個類. 功能比較簡單:將字符串和參數數組格式化爲一個消息字符串
    • AbstractMessageSource: 支持"配置文件"的方式國際化資源的抽象類. 內部提供一個與區域設置無關的公共消息配置文件,消息代碼爲關鍵字
    • StaticMessageSource: 主要用於程序測試. 容許經過編程的方式提供國際化信息
    • ResourceBundleMessageSource: 該實現類容許用戶經過beanName指定一個資源名,包括類的全限定資源名. 或者經過beanName指定一組資源名. 不一樣的區域獲取加載不一樣資源文件,以達到國際化的目的
    • ReloadableResourceBundleMessageSource:
      • ReloadableResourceBundleMessageSource和ResourceBundleMessageSource的區別:
        • 加載資源類型及方式:
          • ReloadResourceBundleMessageSource依託SpringResourceLoader加載Resource資源,功能更增強大,支持 .class.properties文件
          • ResourceBundleMessageSource依託JDK自帶的ResourceBundle加載資源,支持絕對路徑和工程路徑,支持 .class.properties文件
        • 緩存時間:
          • ReloadResourceBundleMessageSource每次加載都會記錄每一個資源加載的時間點,在緩存資源過時後會再次比較文件的修改時間,若是不變則不須要加載,同時刷新本次加載時間點
          • ResourceBundleMessageSource主要利用ResourceBundle.Control實現簡單的自動加載
        • 編碼方式:
          • ReloadResourceBundleMessageSource不只能夠指定統一的默認編碼方式,也同時支持爲每一個文件單獨制定編碼方式

MessageSource接口:java

方法 描述
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解析消息要素的包裝接口和類:web

方法 描述
String[] getCode() 返回用於解決此消息的代碼,按照這些代碼應該嘗試的順序. 所以,最後的一個代碼將是默認代碼
Object[] getArguments() 返回要用於解析此消息的參數數組
String getDefaultMessage() 返回要用於解析此消息的默認消息

HierarchicalMessageSource消息源分層接口:spring

方法 描述
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消息源解析委派類:json

方法 描述
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指定一組資源名:restful

方法 描述
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;
    }
}
相關文章
相關標籤/搜索