写点什么

微服务架构实战之使用 SpringBoot 开发 Web 项目

发布于: 2021 年 04 月 15 日
微服务架构实战之使用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 接口:

MessageSourceResolvable 解析消息要素的包装接口和类:

HierarchicalMessageSource 消息源分层接口:

MessageSourceSupport 用于支持消息源解析的抽象类:

DelegatingMessageSource 消息源解析委派类:

AbstractMessageSourc 抽象类 Spring 中支持配置文件的方式国际化资源的抽象类:

StaticMessageSource 是 AbstractMessageSource 允许通过编程的方式提供国际化信息:

ResourceBundleMessageSource 是 AbstractMessageSource 的实现类,允许用户通过 beanName 指定一个资源名- 包括类路径的全限定名, 或者通过 beanNames 指定一组资源名:

ReloadableResourceBundleMessageSource 实现类允许用户通过 beanName 指定一个资源名,包括类路径和全限定名.或者通过 beanNames 指定一组资源名:

  • 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 操作

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 作为错误页面的文件名来匹配这种类型的所有错误 - 精确优先,即优先寻找精确的错误状态码.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;    }}
复制代码


发布于: 2021 年 04 月 15 日阅读数: 13
用户头像

一位攻城狮的自我修养 2021.04.06 加入

分享技术干货,面试题和攻城狮故事。 你的关注支持是我持续进步的最大动力! https://github.com/ChovaVea

评论

发布
暂无评论
微服务架构实战之使用SpringBoot开发Web项目