【学习笔记】后端表单验证JSR303

RECOMMEND

在web开发中我们常常会遇到国际化语言处理问题,那么如何来做到国际化呢? 你能get的知识点? > 1. 使用springgmvc与thymeleaf进行国际化处理。 > 2. 使用springgmvc与jsp进行国际化处理。 > 3. 使用springboot与thymeleaf进行国际化处理。 你必须要知道的概念 关于i18n: > i18n(其来源是英文单词 > internationalization的首末字符i和n,18为中间的字符数)是“国际化”的简称。在资讯领域,国际化(i18n)指让产品(出版物,软件,硬件等)无需做大的改变就能够适应不同的语言和地区的需要。对程序来说,在不修改内部代码的情况下,能根据不同语言及地区显示相应的界面。 > 在全球化的时代,国际化尤为重要,因为产品的潜在用户可能来自世界的各个角落。通常与i18n相关的还有L10n(“本地化”的简称)。 一:使用springgmvc与thymeleaf进行国际化处理。 ![文件目录](https://img-blog.csdnimg.cn/20200419192309668.pngpic_center) 1、在项目spring的Spring MVC配置文件springmvc.xml中,你需要配置 - 资源文件绑定器ResourceBundleMessageSource - SessionLocaleResolver(用于将Locale对象存储于Session中供后续使用) - LocaleChangeInterceptor(用于获取请求中的locale信息,将其转为Locale对象,获取LocaleResolver对象)。 ```xml <!-- 使用ResourceBundleMessageSource实现国际化资源--> <bean id="messageSource" class="org.springframework.context.support.ResourceBundleMessageSource"> <property name="basenames" value="messages"/> <property name="defaultEncoding" value="UTF-8"/> </bean> <bean id="localeResolver" class="org.springframework.web.servlet.i18n.SessionLocaleResolver"> <property name="defaultLocale" value="en_US"/> </bean> <mvc:interceptors> <bean class="org.springframework.web.servlet.i18n.LocaleChangeInterceptor"> <property name="paramName" value="lang"/> </bean> </mvc:interceptors> ``` 2、在控制器中添加方法localeChange处理国际化,并注入ResourceBundleMessageSource的Bean实例 ```java @Autowired private ResourceBundleMessageSource messageSource; @GetMapping("/localeChange") public String localeChange(Locale locale){ String userName = messageSource.getMessage("userName",null,locale); String passWord = messageSource.getMessage("passWord",null,locale); System.out.println(userName+passWord); return "login"; } ``` 3、创建国际化资源属性文件`messages_en_US.properties`和`messages_zh_CN.properties`。 **注意这两个文件的命名格式,否则解析会出错, 并且我这里的两个文件就是位于我的resources目录下,当你新建这两个文件后,他会自动给你归档,不要以为我的这两个上面还有一层,你也跟着建一个文件夹。** ``` 1、messages_en_US.properties userName=userName passWord=password 2、messages_zh_CN.properties userName=用户名 passWord=密码 ``` 4、新建html,用于最终的显示,这里使用的是thymeleaf模板引擎,没有做springmvc与thymeleaf的整合可以看我的另一篇文章 [springmvc与thymeleaf的整合](https://blog.csdn.net/qq_41929184/article/details/105244034) ```html <!-- @Author: lomtom @Date: 2020/4/19 @Time: 16:51 @Email: lomtom@qq.com --> <!DOCTYPE html> <html lang="en" xmlns:th="http://www.thymeleaf.org"> <head> <meta charset="UTF-8"> <title>login</title> </head> <body> <h2 th:text="{userName}+' '+{passWord}"></h2> <a href="localeChange?lang=en_US">英文</a> <a href="localeChange?lang=zh_CN">中文</a> </body> </html> ``` 最终的效果: ![](https://img-blog.csdnimg.cn/20200419192645199.gif) 二: 使用springgmvc与jsp进行国际化处理。 这里的前三部与上面相同,唯一有区别的就是最终视图的显示,使用jsp,就要用到JSTL标签,这里需要引入JSTL的message标签。 即在头部加入`<%@ taglib prefix="spring" uri="http://www.springframework.org/tags" %>` 然后通过`<spring:message>`元素的key属性输出资源属性文件中的key所对应的值,最终的jsp是这样的。 ```jsp <%@ page contentType="text/html;charset=UTF-8" language="java" %> <%@ taglib prefix="spring" uri="http://www.springframework.org/tags" %> <html> <head> <title>login</title> </head> <body> <p><spring:message code="userName"/></p> <p><spring:message code="password"/></p> <a href="localeChange?lang=en_US">英文</a> <a href="localeChange?lang=zh_US">中文</a> </body> </html> ``` 依次点击“中文”和“英文”链接,可以看到`<spring:message>`元素显示的文本能够根据所传递的语言来动态展现。 三:使用springboot与thymeleaf进行国际化处理。 没有做springmvc与thymeleaf的整合可以看我的另一篇文章 [springmvc与thymeleaf的整合](https://blog.csdn.net/qq_41929184/article/details/105244034) 1、创建配置文件,抽取页面要显示的消息 ![](https://img-blog.csdnimg.cn/2020041919372920.png) 2、springboot已经配置好了组件,加入即可 ```yml spring: 让springboot来管理配置文件 messages: basename: i18n.login ``` 3、让页面获取即可(默认根据浏览器语言来切换),利用**thymeleaf** ```html <button type="submit" class="btn btn-default" th:text="{login.btn}">Sign in</button> ``` 4、根据链接来进行语言的切换 原理:国际化locale(区域信息对象),如果自定义了,就是用自己的,而不是系统的 ```html <a class="btn btn-sm" th:href="@{/index.html(l=zh_CN)}">[[{login.zh}]]</a> <a class="btn btn-sm" th:href="@{/index.html(l=en_US)}">[[{login.en}]]</a> <a class="btn btn-sm" th:href="@{/index.html(l=kor_KR)}">[[{login.kor}]]</a> ``` 5、解析器,因为我们设置了自己的区域信息对象,所以我们需要书写自己的解析器。并且注入到容器中。 ``` 1、新建一个LocaleResolver public class MyLocaleResolver implements LocaleResolver { @Override //解析区域信息 public Locale resolveLocale(HttpServletRequest httpServletRequest) { String l = httpServletRequest.getParameter("l"); Locale locale = Locale.getDefault(); if(!StringUtils.isEmpty(l)){ String[] split = l.split("_"); locale = new Locale(split[0],split[1]); } return locale; } @Override public void setLocale(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Locale locale) { } } 2、在config文件中加入到容器中 @Configuration public class MyMvcConfig implements WebMvcConfigurer { @Bean public LocaleResolver localeResolver(){ return new MyLocaleResolver(); } } ``` 这个写的时间过于久远,所以贴出代码,感兴趣的,可以自己研究研究: ![](https://img-blog.csdnimg.cn/20200419200538388.pngpic_center) 1、html ```html <!-- User: 欧阳隆桐 Date: 2019/12/22 Time: 16:42 --> <!DOCTYPE html> <html lang="ch" xmlns:th="http://www.thymeleaf.org"> <head> <meta charset="UTF-8"> <title>[[{login.title}]]</title> <link rel="stylesheet" href="/webjars/bootstrap/4.4.1/css/bootstrap.css" th:href="@{/webjars/bootstrap/4.4.1/css/bootstrap.css}"> <script src="/webjars/jquery/3.3.1/jquery.js" th:src="@{/webjars/jquery/3.3.1/jquery.js}"></script> <script src="/webjars/bootstrap/4.4.1/js/bootstrap.js" th:src="@{/webjars/bootstrap/4.4.1/js/bootstrap.js}"></script> </head> <body> <div class="container"> <div class="row clearfix"> <div class="col-md-12 column"> <form class="form-horizontal" role="form" th:action="@{/user/login}" method="post"> <div class="form-group text-center"> <h1 class="h3 mb-3 font-weight-normal" th:text="{login.tip}">Please sign in</h1> <p style="color: red" th:text="${msg}" th:if="${not strings.isEmpty(msg)}"></p> </div> <div class="form-group"> <label for="username" class="col-sm-2 control-label" th:text="{login.username}">Username</label> <div class="col-sm-10"> <input type="text" class="form-control" name="username" id="username" /> </div> </div> <div class="form-group"> <label for="password" class="col-sm-2 control-label" th:text="{login.password}">Password</label> <div class="col-sm-10"> <input type="password" class="form-control" name="password" id="password" /> </div> </div> <div class="form-group"> <div class="col-sm-offset-2 col-sm-10"> <div class="checkbox"> <label><input type="checkbox" />[[{login.remember}]]</label> </div> </div> </div> <div class="form-group"> <div class="col-sm-offset-2 col-sm-10"> <button type="submit" class="btn btn-default" th:text="{login.btn}">Sign in</button> </div> </div> <div class="form-group text-center"> <p class="mt-5 mb-3 text-muted">@ 2019</p> <a class="btn btn-sm" th:href="@{/index.html(l=zh_CN)}">[[{login.zh}]]</a> <a class="btn btn-sm" th:href="@{/index.html(l=en_US)}">[[{login.en}]]</a> <a class="btn btn-sm" th:href="@{/index.html(l=kor_KR)}">[[{login.kor}]]</a> </div> </form> </div> </div> </div> </body> </html> ``` 2、properties ``` 1、默认 login.btn=登录 login.en=英文 login.kor=韩文 login.password=密码 login.remember=记住我 login.tip=请登录 login.title=登录 login.username=用户名 login.zh=中文 2、英文 login.btn=Sign in login.en=English login.kor=Korean login.password=Password login.remember=Remember me login.tip=Please sign in login.title=Login login.username=Username login.zh=Chinese 3、韩文 login.btn=로그인 login.en=영어 login.kor=한글 login.password=암호 login.remember=저를 기억하세요 login.tip=로그인하십시오. login.title=로그인 login.username=사용자 이름 login.zh=중국어 4、中文 login.btn=登录 login.en=英文 login.kor=韩文 login.password=密码 login.remember=记住我 login.tip=请登录 login.title=登录 login.username=用户名 login.zh=中文 ``` 3、java ```java 1、LocaleResolver /** * 可以在连接上携带区域信息 */ public class MyLocaleResolver implements LocaleResolver { @Override //解析区域信息 public Locale resolveLocale(HttpServletRequest httpServletRequest) { String l = httpServletRequest.getParameter("l"); Locale locale = Locale.getDefault(); if(!StringUtils.isEmpty(l)){ String[] split = l.split("_"); locale = new Locale(split[0],split[1]); } return locale; } @Override public void setLocale(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Locale locale) { } } 2、config @Configuration public class MyMvcConfig implements WebMvcConfigurer { @Bean public LocaleResolver localeResolver(){ return new MyLocaleResolver(); } } ``` 4、yml ```yml spring: 让springboot来管理配置文件 messages: basename: i18n.login ``` 问题 描述:显示中文时乱码 解决:将国际化资源属性文件的编码格式设置为UTF-8即可,当然也可以把整个项目编码格式都设为UTF-8 ![](https://img-blog.csdnimg.cn/20200419195341175.png)
2020-04-19
概述 Thymeleaf提供了一组Spring集成,使您可以将其用作Spring MVC应用程序中JSP的全功能替代品。 这些集成将使您能够: - @Controller像使用JSP一样,将Spring MVC 对象中的映射方法转发到Thymeleaf管理的模板。 - 在模板中使用Spring表达式语言(Spring EL)代替OGNL。 - 在与表单支持Bean和结果绑定完全集成的模板中创建表单,包括使用属性编辑器,转换服务和验证错误处理。 - 显示Spring管理的消息文件中的国际化消息(通过常规MessageSource对象)。 - 使用Spring自己的资源解析机制解析您的模板。 thymeleaf自己也做了spring的集成,所以我们并不需要做太多的配置,就可以达到我们想要的结果。thymeleaf提供了两种集成方法:①、注解配置,也就是java代码,②、xml文件配配置,本文主要介绍第二种xml配置。 你能get到的知识点: 1、springmvc整合thymeleaf 2、spring提供的三种model的使用 3、解决html前端thymeleaf不生效问题(见问题1) 4、解决html前端显示乱码问题(见问题2) @[Toc] springmvc整合thymeleaf 一:加入依赖 在springmvc里面,除了要加入`thymeleaf`的主依赖之外,还需要`thymeleaf-spring4`,否则会报`org.thymeleaf.spring4.view.ThymeleafViewResolver`,找不到thymeleaf解析器,所以`thymeleaf-spring4`也是必不可少的。 > Thymeleaf具有针对Spring Framework 3.x和4.x的集成,由两个独立的库分别称为thymeleaf-spring3和提供thymeleaf-spring4。这些库打包在单独的.jar文件(thymeleaf-spring3-{version}.jar和thymeleaf-spring4-{version}.jar)中,需要添加到类路径中,以便在应用程序中使用Thymeleaf的Spring集成 。 ```xml <!-- thymeleaf--> <dependency> <groupId>org.thymeleaf</groupId> <artifactId>thymeleaf-spring4</artifactId> <version>3.0.11.RELEASE</version> </dependency> <dependency> <groupId>org.thymeleaf</groupId> <artifactId>thymeleaf</artifactId> <version>3.0.11.RELEASE</version> </dependency> ``` 二:设置thymeleaf解析器 在springmvc配置文件中配置thymeleaf解析器,官方文档中Thymeleaf提供了上述两个接口的实现: ``` org.thymeleaf.spring4.view.ThymeleafView org.thymeleaf.spring4.view.ThymeleafViewResolver ``` 不过现在都已经被`org.thymeleaf.spring4.view.ThymeleafViewResolver`所代替,至于以上配置是否还能够生效,就要靠你来试试了。 ```xml <!-- thymeleaf 模板解析器 --> <bean id="templateResolver" class="org.thymeleaf.spring4.templateresolver.SpringResourceTemplateResolver"> <property name="prefix" value="/" /> <property name="suffix" value=".html" /> <property name="templateMode" value="HTML" /> <property name="cacheable" value="false" /> <property name="characterEncoding" value="UTF-8"/> </bean> <bean id="templateEngine" class="org.thymeleaf.spring4.SpringTemplateEngine"> <property name="templateResolver" ref="templateResolver" /> </bean> <!-- 视图解析器--> <bean id="viewResolver" class="org.thymeleaf.spring4.view.ThymeleafViewResolver"> <property name="templateEngine" ref="templateEngine" /> <property name="characterEncoding" value="UTF-8"/> </bean> ``` ViewResolvers是负责获取特定操作和语言环境的View对象的对象。通常,控制器要求ViewResolvers转发到具有特定名称的视图(由controller方法返回的String),然后应用程序中的所有视图解析器将按有序链执行,直到其中一个能够解析该视图为止。如果返回了View对象,并且将控件传递给该对象以呈现HTML。 **注:值得注意的是**,如果自己设置了spring的视图解析器,需要将其注释掉,否则thymeleaf解析器可能不会生效,我就是因为这个调试了好久,最后才发现这个问题。 ```xml <!-- 配置视图解析器 prefix:前缀, suffix:后缀 使用thymeleaf需要将其注释掉--> <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver"> <property name="prefix" value="/"/> <property name="suffix" value=".html"/> </bean> ``` 三:编写控制器 需要从控制层传数据到视图时,我们就会使用model,常用的三种model就是:**Model、ModelMap、ModelAndView**。使用这三种model时,spring框架自动创建实例并作为controller的入参,用户无需自己创建 1、使用Model ```java /** * 在Model里存入一个用户信息 * @return hello页面 */ @GetMapping("returnModelAndView") public String returnModelAndView(Model model){ model.addAttribute("userInfo",new UserInfo("lomtom","123",new Address("湖南","邵阳"))); return "hello"; } ``` Model是一个接口, Model源码: ```java public interface Model { Model addAttribute(String var1, Object var2); Model addAttribute(Object var1); Model addAllAttributes(Collection<?> var1); Model addAllAttributes(Map<String, ?> var1); Model mergeAttributes(Map<String, ?> var1); boolean containsAttribute(String var1); Map<String, Object> asMap(); } ``` 2、使用ModelMap ModelMap继承LinkedHashMap ModelMap源码: ```java public class ModelMap extends LinkedHashMap<String, Object> { public ModelMap() { } public ModelMap(String attributeName, Object attributeValue) { this.addAttribute(attributeName, attributeValue); } public ModelMap(Object attributeValue) { this.addAttribute(attributeValue); } public ModelMap addAttribute(String attributeName, Object attributeValue) { Assert.notNull(attributeName, "Model attribute name must not be null"); this.put(attributeName, attributeValue); return this; } public ModelMap addAttribute(Object attributeValue) { Assert.notNull(attributeValue, "Model object must not be null"); return attributeValue instanceof Collection && ((Collection)attributeValue).isEmpty() ? this : this.addAttribute(Conventions.getVariableName(attributeValue), attributeValue); } public ModelMap addAllAttributes(Collection<?> attributeValues) { if (attributeValues != null) { Iterator var2 = attributeValues.iterator(); while(var2.hasNext()) { Object attributeValue = var2.next(); this.addAttribute(attributeValue); } } return this; } public ModelMap addAllAttributes(Map<String, ?> attributes) { if (attributes != null) { this.putAll(attributes); } return this; } public ModelMap mergeAttributes(Map<String, ?> attributes) { if (attributes != null) { Iterator var2 = attributes.entrySet().iterator(); while(var2.hasNext()) { Entry<String, ?> entry = (Entry)var2.next(); String key = (String)entry.getKey(); if (!this.containsKey(key)) { this.put(key, entry.getValue()); } } } return this; } public boolean containsAttribute(String attributeName) { return this.containsKey(attributeName); } } ``` 3、使用ModelAndView ```java /** * 在ModelAndView里存入一个用户信息 * @return ModelAndView */ @GetMapping("returnModelAndView") public ModelAndView returnModelAndView(ModelAndView modelAndView){ modelAndView.setViewName("hello"); modelAndView.addObject("userInfo",new UserInfo("lomtom","123",new Address("湖南","邵阳"))); return modelAndView; } ``` ModelAndView顾名思义就是模型和试图的结合。 ModelAndView源码: ```java public class ModelAndView { private Object view; private ModelMap model; private HttpStatus status; private boolean cleared = false; ...... } ``` 四:编写html 首先,写一个链接,请求`returnModelAndView`请求。 ```html <a href="returnModelAndView">ModelAndView</a> ``` 然后,写hello.html页面用于验证 ```html <h2>你好啊,你成功了</h2> <p th:text="${userInfo.userName}+'来自'+${userInfo.address.province}+${userInfo.address.city}"></p> ``` 五:结果 ![](https://img-blog.csdnimg.cn/20200401143955232.png) 六:记录我遇到的问题 **问题1**:配置好一切后,thymeleaf无法解析,所有关于thymeleaf的显示都无法生效。 **解决**:由于我配置了spring的视图解析,所以导致thymeleaf的试图解析无法生效,所以去掉spring的视图解析。 > thmelaf介绍Springmvc的视图解析: > 快速浏览其属性足以了解其配置方式: >- viewClass建立View实例的类。对于JSP解析器,这是必需的,但是当我们与Thymeleaf合作时,根本不需要。 > - prefix与suffixThymeleaf的TemplateResolver对象中相同名称的属性的工作方式相似。 > - order 确定在链中查询ViewResolver的顺序。 > - viewNames 允许使用此ViewResolver解析视图名称(带通配符)。 **问题2**:前端显示乱码,具体表现为后台传入的不乱码,但是html中原本存在的乱码。 **解决**:在试图解析器和模板解析器中加入参数:`<property name="characterEncoding" value="UTF-8"/>` 作者有话 我写的可能并不怎么详细,详细配置请查看thymeleaf官方介绍:[传送门](https://www.thymeleaf.org/doc/tutorials/3.0/thymeleafspring.htmlviews-and-view-resolvers-in-spring-mvc),到最后,看都看完了,如果对你有帮助,请点个赞吧。
2020-04-05
概述 之前的文章[springmvc使用注解声明控制器与请求映射](https://blog.csdn.net/qq_41929184/article/details/104997012)有简单提到过控制器与请求映射,这一次就详细讲解一下`SpringMVC`的`REST`风格的四种请求方式及其使用方法。 你能get的知识点 1、什么是Rest风格? 2、基于`springmvc`实现REST风格的四种请求方式 3、post请求转换为`delete`与`put`请求 4、解决请求乱码问题 5、`RequestMapping`注解的属性 壹:rest风格 一:什么是Rest风格? REST即表述性状态传递(英文:Representational State Transfer,简称REST)是Roy Fielding博士在2000年他的博士论文中提出来的一种软件架构风格。它是一种针对网络应用的设计和开发方式,可以降低开发的复杂性,提高系统的可伸缩性。 简单来说:使用URL定位资源,用HTTP动词(例如GET,POST,DELETE,DETC等)描述操作。 二:REST风格的四种请求方式 请求 | 说明 | 用于|例子|例子说明 ----|----|---|--|-- @GetMapping|匹配GET方式的请求;| 一般用于读取数据| /user/1|获取一号用户信息 @PostMapping|匹配POST方式的请求;| 一般用于新增数据 |/user/1|新增一号用户 @PutMapping|匹配PUT方式的请求;| 一般用于更新数据|/user/1|修改一号用户 @DeleteMapping|匹配DELETE方式的请求;|一般用于删除数据|/user/1|删除一号用户 也就是说,我们不再使用`/user/getuser?user=1`、`/user/deleteuser?user=1`等来区分使用者的行为操作,而是使用不同的请求方式来描述行为。 贰:基于`springmvc`实现REST风格的四种请求方式 Spring框架的4.3版本后,引入了新的组合注解,来帮助简化常用的`HTTP`方法的映射,并更好的表达被注解方法的语义。 ``` @GetMapping = @requestMapping(method = RequestMethod.GET)。 @PostMapping = @requestMapping(method = RequestMethod.POST)。 @DeleteMapping = @requestMapping(method = RequestMethod.DELETE)。 @PutMapping = @requestMapping(method = RequestMethod.PuT)。 ``` 一:@GetMapping请求 以`@GetMapping`为例,该组合注解是`@RequestMapping(method = RequestMethod.GET)`的缩写,它会将HTTP GET请求映射到特定的处理方法上。 `RequestMapping`后所有属性都是可选的,但其默认属性是`value`。当`value`是其唯一属性时,可以省略属性名。 ```java @RequestMapping(value = "/rest",method = RequestMethod.GET) public String restGet(){ System.out.println("Get请求,hello....."); return "hello"; } ``` `@GetMapping`是一个组合注解,是`@RequestMapping(method = RequestMethod.GET)`的缩写。 所以我们可以将以上代码简单的写成: ```java @GetMapping("/rest") public String restGet(){ System.out.println("Get请求,hello....."); return "hello"; } ``` 二:@PostMapping请求 ```java @PostMapping("/rest") public String restPost(){ System.out.println("Post请求,hello....."); return "hello"; } ``` 三:@DeleteMapping请求 ```java @DeleteMapping("/rest") public String restDelete(){ System.out.println("Delete请求,hello....."); return "redirect:rest"; } ``` 四:@PutMapping请求 ```java @PutMapping("/rest") public String restPut(){ System.out.println("Put请求,hello....."); return "redirect:rest"; } ``` 叁:问题 一:post请求转换为delete与put请求 由于form表单只支持GET和POST请求,而不支持DELETE和PUT等请求方式,所以我们实现delete和put请求往往会报错找不到方法。 Spring提供了一个过滤器`HiddenHttpMethodFilter`,可以将DELETE和PUT请求转换为标准的HTTP方式,即能将POST请求转为DELETE或PUT请求。 **具体实现**:在web.xml文件中配置过滤器HiddenHttpMethodFilter ```xml <!--使用Rest风格的URI,将页面普通的post请求转为delete或者put请求--> <filter> <filter-name>hiddenHttpMethodFilter</filter-name> <filter-class>org.springframework.web.filter.HiddenHttpMethodFilter</filter-class> </filter> <filter-mapping> <filter-name>hiddenHttpMethodFilter</filter-name> <url-pattern>/*</url-pattern> </filter-mapping> ``` **注意**:delete和put请求最好使用Redirect(重定向),不然会报404错误。 二:解决请求乱码问题 ```xml <!-- 解决乱码问题--> <filter> <filter-name>CharacterEncodingFilter</filter-name> <filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class> <init-param> <param-name>encoding</param-name> <param-value>UTF-8</param-value> </init-param> <init-param> <param-name>forceEncoding</param-name> <param-value>true</param-value> </init-param> </filter> <filter-mapping> <filter-name>CharacterEncodingFilter</filter-name> <url-pattern>/*</url-pattern> </filter-mapping> ``` **注意**:编码的过滤器应该放在所有的过滤器前,否则不生效 肆:RequestMapping注解的属性 属性名 | 类型 |描述 --|--|-- name|String|可选属性,用于为映射地址指定别名。 value|String[]|可选属性,同时也是默认属性,用于映射一-个请求和一种方法,可以标注在一-个方法或一个类上。 method|RequestMethod[]|可选属性,用于指定该方法用于处理哪种类型的请求方式,其请求方式包括GET、POST、HEAD、OPTIONS、PUT、PATCH、DELETE和TRACE例如method=RequestMethod.GET表示只支持GET请求,如果需要支持多个请求方式则需要通过{}写成数组的形式,并且多个请求方式之间是有英文逗号分隔。 params| String[]| 可选属性,用于指定Request中必须包含某些参数的值,.才可以通过其标注的方法处理。 headers| String[]| 可选属性,用于指定Request中必须包含某些指定的header的值,才可以通过其标注的方法处理。。 consumes| String[]| 可选属性,用于指定处理请求的提交内容类型(Content-type),比如application/json,text/html等。 produces| String[]| 可选属性,用于指定返回的内容类型,返回的内容类型必,须是request请求头(Accept)中所包含的类型。 > 表中所有属性都是可选的,但其默认属性是value。当value是其唯一属性时, 可以省略属性名。 > 例如,下面两种标注的含义相同: @RequestMapping(value="/rest") @RequestMapping("/rest") 伍:测试 新建index.html文件,加入以下代码。新建hello.html,用于请求后的页面跳转。 ```html <h2>各种请求</h2> <a href="rest">hello Get请求</a> <form action="rest" method="post"> <button type="submit">hello Post请求</button> </form> <form action="rest" method="post"> <input type="hidden" name="_method" value="delete"> <button type="submit">hello Delete请求</button> </form> <form action="rest" method="post"> <input type="hidden" name="_method" value="put"> <button type="submit">hello put请求</button> </form> ``` 结果:![](https://img-blog.csdnimg.cn/20200329124302860.png)
2020-03-29
一、题目描述: 给定一个二叉搜索树的根节点 root 和一个值 key,删除二叉搜索树中的 key 对应的节点,并保证二叉搜索树的性质不变。返回二叉搜索树(有可能被更新)的根节点的引用。 一般来说,删除节点可分为两个步骤: 首先找到需要删除的节点; 如果找到了,删除它。 说明: 要求算法时间复杂度为 O(h),h 为树的高度。 示例: >root = [5,3,6,2,4,null,7] > key = 3 >![](https://img-blog.csdnimg.cn/20210115133530614.png) >给定需要删除的节点值是 3,所以我们首先找到 3 这个节点,然后删除它。 > >一个正确的答案是 [5,4,6,2,null,null,7], 如下图所示。 > ![](https://img-blog.csdnimg.cn/20210115133552237.png) >另一个正确答案是 [5,2,6,null,4,null,7]。 > ![](https://img-blog.csdnimg.cn/20210115133608480.png) 二、解答 分析 解题思路: 首先,这个题目可以根据删除的节点的左右节点来判断。 而找到该节点是非常简单的,因为这棵树是二叉搜索树,而二叉搜索树的特性,左节点的值一定小于该节点值,右节点的值一定大于该节点的值,所以直接搜索就可以找到该值。 所以重点在于怎么判断该节点的左右节点的情况。 大致可以分为四种: 1. 该节点没有左节点,也没有右节点 2. 该节点没有左节点,但有右节点 3. 该节点有左节点,但没有右节点 4. 该节点有左节点,也有右节点 第一种:对于第一种情况,直接将该节点删除即可。 第二种:对于第二种情况,直接删除节点,将左节点代替该节点。 第三种:对于第三种情况:直接删除节点,将右节点代替该节点。 第四种:对于第四种情况,又可以分为三种情况: 1. 该节点的左节点没有右节点,将左节点代替该节点。 2. 该节点的右节点没有左节点,将右节点代替该节点。 3. 对于都有的情况,为了保证二叉搜索树的结构,我们 ① :可以用该节点的左节点最右节点的值代替该节点;②:也可以用该节点的右节点的最左节点的值代替该节点。 >而对于最后的情况,也就是第四种情况的第三种情况, >需要注意 >①中,如果最右节点还有左节点,我们可以用最右节点的左节点的值代替最右节点所在的位置; >②中,如果最左节点还有右节点,我们可以用最左节点的右节点的值代替最左节点所在的位置。 **再一次总结归纳:** 其实,最后第四种情况的第三种就包括了前面所有的方面, 在找到该节点后: 1. 如果该节点的左节点不为空,我们用该节点的左节点最右节点的值代替该节点; 2. 否则,如果该节点的右节点不为空,我们可以用该节点的右节点的最左节点的值代替该节点。 3. 否则,将该节点置空。 找到该节点,非常容易,因为左节点的值一定小于该节点值,右节点的值一定大于该节点的值。 所以,从根节点开始遍历 1. 如果遍历到的节点的值大于该值,该值一定处于该节点的右子树,往右遍历即可。 2. 否则,如果遍历到的节点的值小于该值,该值一定处于该节点的左子树,往左遍历即可。 3. 否则,就是找到了该值,在进行上述操作即可。 >时间复杂度:O(h),其中 n 为树的高度。 代码 ```java /** * Definition for a binary tree node. * public class TreeNode { * int val; * TreeNode left; * TreeNode right; * TreeNode() {} * TreeNode(int val) { this.val = val; } * TreeNode(int val, TreeNode left, TreeNode right) { * this.val = val; * this.left = left; * this.right = right; * } * } */ class Solution { public TreeNode deleteNode(TreeNode root, int key) { if(root!=null){ if(root.val == key){ if(root.left != null){ root.val = leftMax(root); root.left = deleteNode(root.left, root.val); } else if(root.right != null){ root.val = rightMin(root); root.right = deleteNode(root.right, root.val); } else { root = null; } }else if(root.val>key){ root.left = deleteNode(root.left,key); }else{ root.right = deleteNode(root.right,key); } return root; } return null; } public int rightMin(TreeNode root) { root = root.right; while (root.left != null) root = root.left; return root.val; } public int leftMax(TreeNode root) { root = root.left; while (root.right != null) root = root.right; return root.val; } } ``` > 执行用时:0 ms, 在所有 Java 提交中击败了100.00%的用户 内存消耗:39.2 MB, 在所有 Java 提交中击败了8.92%的用户 三、官方解答 ```java class Solution { /* One step right and then always left */ public int successor(TreeNode root) { root = root.right; while (root.left != null) root = root.left; return root.val; } /* One step left and then always right */ public int predecessor(TreeNode root) { root = root.left; while (root.right != null) root = root.right; return root.val; } public TreeNode deleteNode(TreeNode root, int key) { if (root == null) return null; // delete from the right subtree if (key > root.val) root.right = deleteNode(root.right, key); // delete from the left subtree else if (key < root.val) root.left = deleteNode(root.left, key); // delete the current node else { // the node is a leaf if (root.left == null && root.right == null) root = null; // the node is not a leaf and has a right child else if (root.right != null) { root.val = successor(root); root.right = deleteNode(root.right, root.val); } // the node is not a leaf, has no right child, and has a left child else { root.val = predecessor(root); root.left = deleteNode(root.left, root.val); } } return root; } } ``` > 参考: > 1、[题目](https://leetcode-cn.com/problems/delete-node-in-a-bst/) > 2、[官方解答](https://leetcode-cn.com/problems/delete-node-in-a-bst/solution/shan-chu-er-cha-sou-suo-shu-zhong-de-jie-dian-by-l/) >本文首发于CSDN,作者:lomtom >原文链接:**[https://blog.csdn.net/qq_41929184/article/details/112662236](https://blog.csdn.net/qq_41929184/article/details/112662236)** >个人网站:**[https://lomtom.top](https://lomtom.top)**,公众号:**博思奥园**,同步更新。 > > **你的支持就是我最大的动力。** ![](https://img-blog.csdnimg.cn/20200405094243147.png)
2021-01-17
一、题目描述: 给定一个无重复元素的有序整数数组 nums 。 返回 恰好覆盖数组中所有数字 的 最小有序 区间范围列表。也就是说,nums 的每个元素都恰好被某个区间范围所覆盖,并且不存在属于某个范围但不属于 nums 的数字 x 。 列表中的每个区间范围 [a,b] 应该按如下格式输出: - "a->b" ,如果 a != b - "a" ,如果 a == b 示例 示例 1: >输入:nums = [0,1,2,4,5,7] 输出:["0->2","4->5","7"] 解释:区间范围是: [0,2] --> "0->2" [4,5] --> "4->5" [7,7] --> "7" 示例 2: >输入:nums = [0,2,3,4,6,8,9] 输出:["0","2->4","6","8->9"] 解释:区间范围是: [0,0] --> "0" [2,4] --> "2->4" [6,6] --> "6" [8,9] --> "8->9" 示例 3: >输入:nums = [] 输出:[] 示例 4: >输入:nums = [-1] 输出:["-1"] 示例 5: >输入:nums = [0] 输出:["0"] 提示: >0 <= nums.length <= 20 -231 <= nums[i] <= 231 - 1 nums 中的所有值都 互不相同 nums 按升序排列 二、解答 分析 解题思路: 1. 遍历整个数组,当相邻的数只相差1时,构成一个区间。 2. 当相邻的数相差大于1时,开始一个新的区间。 3. 当判断开始一个新的区间时,我们需要保存前面的区间。 4. 保存一个区间需要两个值分别记录区间开始的值,当前数的前一个数的值。 5. 唯一值得注意的是处理边界问题。 >时间复杂度:O(n),其中 n 为数组的长度。我们只需要遍历一次数组即可。 空间复杂度:O(1)。只需要常数空间存放若干变量。 代码 ```cpp class Solution { public List<String> summaryRanges(int[] nums) { int length = nums.length; List<String> result = new ArrayList<>(); if(length == 0){ return result; } int start = nums[0]; int now = nums[0]; int i = 1; for(;i < length;i++){ if(now + 1 == nums[i]){ now++; } else{ result.add(start == now?String.valueOf(start):start+"->"+now); start = now = nums[i]; } } result.add(start == now?String.valueOf(start):start+"->"+now); return result; } } ``` > 执行用时:7 ms, 在所有 Java 提交中击败了82.54%的用户 内存消耗:36.9 MB, 在所有 Java 提交中击败了53.76%的用户 改进 1. 因为当前数的前一个数的值,我可以通过当前的下标来获取,所以该值完全没有必要记录,所以可以省略该值 2. 省略该值之后,我只要通过下标来判断即可,所以在循环里,我只要判断前一个数加一与当前值不相等即可 ```java public List<String> summaryRanges(int[] nums) { int length = nums.length; List<String> result = new ArrayList<>(); if(length == 0){ return result; } int start = nums[0]; int i = 1; for(;i < length;i++){ if(nums[i - 1] + 1 != nums[i]){ result.add(start == nums[i - 1]?String.valueOf(start):start+"->"+nums[i - 1]); start = nums[i]; } } result.add(start == nums[i - 1]?String.valueOf(start):start+"->"+nums[i - 1]); return result; } ``` 这样做的好处就是节省了一定的空间。 三、官方解答 官方给出的题解也是一次遍历,但是是用双指针的方法,也就是使用维护下标low 和 high 分别记录区间的起点和终点,和我的大致思想也差不多。 ```cpp class Solution { public List<String> summaryRanges(int[] nums) { List<String> ret = new ArrayList<String>(); int i = 0; int n = nums.length; while (i < n) { int low = i; i++; while (i < n && nums[i] == nums[i - 1] + 1) { i++; } int high = i - 1; StringBuffer temp = new StringBuffer(Integer.toString(nums[low])); if (low < high) { temp.append("->"); temp.append(Integer.toString(nums[high])); } ret.add(temp.toString()); } return ret; } } ``` > 参考: > 1、[题目](https://leetcode-cn.com/problems/summary-ranges/) > 2、[官方解答](https://leetcode-cn.com/problems/summary-ranges/solution/hui-zong-qu-jian-by-leetcode-solution-6zrs/) >本文首发于CSDN,作者:lomtom 原文链接:**[https://blog.csdn.net/lomtom/article/details/112306554](https://blog.csdn.net/lomtom/article/details/112306554)** 个人网站:**[https://lomtom.top](https://lomtom.top)**,公众号:**博思奥园**,同步更新。 你的支持就是我最大的动力。 ![](https://img-blog.csdnimg.cn/20200405094243147.png)
2021-01-10
一、场景引入 前提背景 在某些场景下,例如淘宝京东这样海量的数据,高访问量的场景,无疑对数据库造成了相当大的负载,同时对于系统的稳定性和扩展性提出很高的要求。 而单个服务器所能够提供的服务以及负载都是有限的。 所以,为了系统的问题,以及较快的响应速度或处理能力,在数据库方面就有了集中解决方案,**分库分表,读写分离**,这些都能在一定程度上有效地减小单台数据库的压力。 而本文就是从读写分离角度来一探究竟。 实现原理 主要理解以下三个点就差不多了: **1、主机负责写操作** **2、从机负责读操作** **3、从机自动从主机中同步数据** 然而,我们对于一个新的东西,我们就要提出我们的哲学三问: ~~我是谁?我在那?我要干嘛?~~ **是什么?为什么?怎么做?** 1、什么是读写分离 读写分离,基本的原理是让主数据库处理事务性增、改、删操作(INSERT、UPDATE、DELETE),而从数据库处理SELECT查询操作。数据库复制被用来把事务性操作导致的变更同步到集群中的从数据库。 2、为什么要读写分离呢? 因为数据库的“写”(写10000条数据到oracle可能要3分钟)操作是比较耗时的。 但是数据库的“读”(从oracle读10000条数据可能只要5秒钟)。 所以读写分离,解决的是,数据库的写入,影响了查询的效率。 3、什么时候要读写分离? 数据库不一定要读写分离,如果程序使用数据库较多时,而更新少,查询多的情况下会考虑使用,利用数据库 主从同步 。可以减少数据库压力,提高性能。当然,数据库也有其它优化方案。memcache 或是 表折分,或是搜索引擎。都是解决方法。 4.主从复制、读写分离的基本设计 在实际的生产环境中,对数据库的读和写都在同一个数据库服务器中,是不能满足实际需求的。无论是在安全性、高可用性还是高并发等各个方面都是完全不能满足实际需求的。因此,通过主从复制的方式来同步数据,再通过读写分离来提升数据库的并发负载能力。 > 取自:[读写分离的实现原理及使用场景](https://blog.csdn.net/belalds/article/details/82655786) **这里使用docker进行数据库的安装,docker的优势以及就怎么安装docker就不多做赘述了,感兴趣的可以去翻一下我以前的文章。** 一、安装mysql 这里只安装了一个主机(master),一个从机(slave) ``` docker run --name mysql-master -p 33061:3306 -e MYSQL_ROOT_PASSWORD=123456 -d mysql:5.7 --character-set-server=utf8mb4 --collation-server=utf8mb4_unicode_ci docker run --name mysql-salve -p 33062:3306 -e MYSQL_ROOT_PASSWORD=123456 -d mysql:5.7 --character-set-server=utf8mb4 --collation-server=utf8mb4_unicode_ci ``` 二、配置同步 为什么? 如果不进行同步的配置,那么从机无法获取主机的数据,最终就会导致同步失败。 1、配置主机用户 ``` grant replication slave on *.* to 'lomtom'@'%' identified by '123456' ``` 2、修改配置文件 在从机中的配置文件加入以下参数。 1. 主机 ``` log-bin = /var/lib/mysql/binlog server-id =1 binlog-do-db =lomtomdb ``` 2. 从机 ``` server-id =2 ``` 1、修改配置文件(以master为例) ``` Copyright (c) 2014, 2016, Oracle and/or its affiliates. All rights reserved. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License, version 2.0, as published by the Free Software Foundation. This program is also distributed with certain software (including but not limited to OpenSSL) that is licensed under separate terms, as designated in a particular file or component or in included license documentation. The authors of MySQL hereby grant you an additional permission to link the program and your derivative works with the separately licensed software that they have included with MySQL. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License, version 2.0, for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA The MySQL Server configuration file. For explanations see http://dev.mysql.com/doc/mysql/en/server-system-variables.html [mysqld] log-bin = /var/lib/mysql/binlog server-id =1 binlog-do-db =lomtomdb pid-file = /var/run/mysqld/mysqld.pid socket = /var/run/mysqld/mysqld.sock datadir = /var/lib/mysql log-error = /var/log/mysql/error.log By default we only accept connections from localhost bind-address = 127.0.0.1 Disabling symbolic-links is recommended to prevent assorted security risks symbolic-links=0 ``` 2、拷贝(主从都需要拷贝,以master为例) 如果服务器有vi编辑器,直接使用vi编辑器即可。 ``` docker cp mysql.cnf mysql-master:/etc/mysql/mysql.conf.d/ ``` 3、重启mysql ``` docker restart mysql-master ``` 4、查看是否配置成功 ``` show master status; ``` 如果出现以下数据即为成功。 ![](https://img-blog.csdnimg.cn/20210107122208680.png) 5、在从机(slave)中配置主机(master) ``` 1、配置主机信息 change master to master_host='192.168.43.236',master_port=33061,master_user='lomtom',master_password='123456',master_log_file='binlog.000001',master_log_pos=154; 2、查看从机状态 show slave status; SLAVE_IO_RUNNING ,SLAVE_MYSQL_RUNNING两个值为YES即为正确启动,否则自己根据下方的错误提示修改配置 ``` 三、测试 在第一个数据库(master)创建我们配置的数据库,然后随意修改该数据库的数据,刷新slave,数据同步成功。 四、注意 1、因为在配置当中指定了数据库(lomtomdb),也就是`binlog-do-db`参数,所以从机只会同步主机中的lomtomdb数据库,其他数据库不同步。 2、修改配置文件时,`log-bin`参数所指定的目录一定是要mysql能够操作的文件,也就是说,如果你指定了其他目录,请给予mysql操作权限。 >参考: >[读写分离的实现原理及使用场景](https://blog.csdn.net/belalds/article/details/82655786) >[江南一点雨](https://mp.weixin.qq.com/s/R89aCCFvCvudLp6FUn2JjQ) >本文首发于CSDN,作者:lomtom 原文链接:**[https://blog.csdn.net/qq_41929184/article/details/112306554](https://blog.csdn.net/qq_41929184/article/details/112306554)** 个人网站:**[https://lomtom.top](https://lomtom.top)**,公众号:**博思奥园**,同步更新。 你的支持就是我最大的动力。
2021-01-07

CONTACT ME

You can contact me in the following ways

Copyright © 2019-2020 Made with love By LomTom | 湘ICP备19023870号 | 经历风雨 666 天 6 小时 6 分 6 秒