Jekyll2019-12-17T12:40:15+00:00http://springboot.javaboy.org/feed.xmlSpringMVC 教程Spring Boot2 原创教程合集,还在不断更新中 配套视频教程同步发布!
江南一点雨wangsong0210@gmail.com下载电子档2019-11-20T22:28:52+00:002019-11-20T22:28:52+00:00http://springboot.javaboy.org/2019/1120/download<p>扫码关注微信公众号【江南一点雨】,后台回复 SpringMVC,免费下载本教程电子书。</p>
<p><img src="http://www.javaboy.org/images/sb/javaboy.jpg" alt="" /></p>江南一点雨wangsong0210@gmail.com扫码关注微信公众号【江南一点雨】,后台回复 SpringMVC,免费下载本教程电子书。15. 拦截器2019-11-19T22:28:52+00:002019-11-19T22:28:52+00:00http://springboot.javaboy.org/2019/1119/interceptor<h2 id="15-拦截器">15. 拦截器</h2>
<p>SpringMVC 中的拦截器,相当于 Jsp/Servlet 中的过滤器,只不过拦截器的功能更为强大。</p>
<p>拦截器的定义非常容易:</p>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nd">@Component</span>
<span class="kd">public</span> <span class="kd">class</span> <span class="nc">MyInterceptor1</span> <span class="kd">implements</span> <span class="nc">HandlerInterceptor</span> <span class="o">{</span>
<span class="cm">/**
* 这个是请求预处理的方法,只有当这个方法返回值为 true 的时候,后面的方法才会执行
* @param request
* @param response
* @param handler
* @return
* @throws Exception
*/</span>
<span class="kd">public</span> <span class="kt">boolean</span> <span class="nf">preHandle</span><span class="o">(</span><span class="nc">HttpServletRequest</span> <span class="n">request</span><span class="o">,</span> <span class="nc">HttpServletResponse</span> <span class="n">response</span><span class="o">,</span> <span class="nc">Object</span> <span class="n">handler</span><span class="o">)</span> <span class="kd">throws</span> <span class="nc">Exception</span> <span class="o">{</span>
<span class="nc">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">println</span><span class="o">(</span><span class="s">"MyInterceptor1:preHandle"</span><span class="o">);</span>
<span class="k">return</span> <span class="kc">true</span><span class="o">;</span>
<span class="o">}</span>
<span class="kd">public</span> <span class="kt">void</span> <span class="nf">postHandle</span><span class="o">(</span><span class="nc">HttpServletRequest</span> <span class="n">request</span><span class="o">,</span> <span class="nc">HttpServletResponse</span> <span class="n">response</span><span class="o">,</span> <span class="nc">Object</span> <span class="n">handler</span><span class="o">,</span> <span class="nc">ModelAndView</span> <span class="n">modelAndView</span><span class="o">)</span> <span class="kd">throws</span> <span class="nc">Exception</span> <span class="o">{</span>
<span class="nc">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">println</span><span class="o">(</span><span class="s">"MyInterceptor1:postHandle"</span><span class="o">);</span>
<span class="o">}</span>
<span class="kd">public</span> <span class="kt">void</span> <span class="nf">afterCompletion</span><span class="o">(</span><span class="nc">HttpServletRequest</span> <span class="n">request</span><span class="o">,</span> <span class="nc">HttpServletResponse</span> <span class="n">response</span><span class="o">,</span> <span class="nc">Object</span> <span class="n">handler</span><span class="o">,</span> <span class="nc">Exception</span> <span class="n">ex</span><span class="o">)</span> <span class="kd">throws</span> <span class="nc">Exception</span> <span class="o">{</span>
<span class="nc">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">println</span><span class="o">(</span><span class="s">"MyInterceptor1:afterCompletion"</span><span class="o">);</span>
<span class="o">}</span>
<span class="o">}</span>
<span class="nd">@Component</span>
<span class="kd">public</span> <span class="kd">class</span> <span class="nc">MyInterceptor2</span> <span class="kd">implements</span> <span class="nc">HandlerInterceptor</span> <span class="o">{</span>
<span class="cm">/**
* 这个是请求预处理的方法,只有当这个方法返回值为 true 的时候,后面的方法才会执行
* @param request
* @param response
* @param handler
* @return
* @throws Exception
*/</span>
<span class="kd">public</span> <span class="kt">boolean</span> <span class="nf">preHandle</span><span class="o">(</span><span class="nc">HttpServletRequest</span> <span class="n">request</span><span class="o">,</span> <span class="nc">HttpServletResponse</span> <span class="n">response</span><span class="o">,</span> <span class="nc">Object</span> <span class="n">handler</span><span class="o">)</span> <span class="kd">throws</span> <span class="nc">Exception</span> <span class="o">{</span>
<span class="nc">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">println</span><span class="o">(</span><span class="s">"MyInterceptor2:preHandle"</span><span class="o">);</span>
<span class="k">return</span> <span class="kc">true</span><span class="o">;</span>
<span class="o">}</span>
<span class="kd">public</span> <span class="kt">void</span> <span class="nf">postHandle</span><span class="o">(</span><span class="nc">HttpServletRequest</span> <span class="n">request</span><span class="o">,</span> <span class="nc">HttpServletResponse</span> <span class="n">response</span><span class="o">,</span> <span class="nc">Object</span> <span class="n">handler</span><span class="o">,</span> <span class="nc">ModelAndView</span> <span class="n">modelAndView</span><span class="o">)</span> <span class="kd">throws</span> <span class="nc">Exception</span> <span class="o">{</span>
<span class="nc">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">println</span><span class="o">(</span><span class="s">"MyInterceptor2:postHandle"</span><span class="o">);</span>
<span class="o">}</span>
<span class="kd">public</span> <span class="kt">void</span> <span class="nf">afterCompletion</span><span class="o">(</span><span class="nc">HttpServletRequest</span> <span class="n">request</span><span class="o">,</span> <span class="nc">HttpServletResponse</span> <span class="n">response</span><span class="o">,</span> <span class="nc">Object</span> <span class="n">handler</span><span class="o">,</span> <span class="nc">Exception</span> <span class="n">ex</span><span class="o">)</span> <span class="kd">throws</span> <span class="nc">Exception</span> <span class="o">{</span>
<span class="nc">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">println</span><span class="o">(</span><span class="s">"MyInterceptor2:afterCompletion"</span><span class="o">);</span>
<span class="o">}</span>
<span class="o">}</span>
</code></pre></div></div>
<p>拦截器定义好之后,需要在 SpringMVC 的配置文件中进行配置:</p>
<div class="language-xml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nt"><mvc:interceptors></span>
<span class="nt"><mvc:interceptor></span>
<span class="nt"><mvc:mapping</span> <span class="na">path=</span><span class="s">"/**"</span><span class="nt">/></span>
<span class="nt"><ref</span> <span class="na">bean=</span><span class="s">"myInterceptor1"</span><span class="nt">/></span>
<span class="nt"></mvc:interceptor></span>
<span class="nt"><mvc:interceptor></span>
<span class="nt"><mvc:mapping</span> <span class="na">path=</span><span class="s">"/**"</span><span class="nt">/></span>
<span class="nt"><ref</span> <span class="na">bean=</span><span class="s">"myInterceptor2"</span><span class="nt">/></span>
<span class="nt"></mvc:interceptor></span>
<span class="nt"></mvc:interceptors></span>
</code></pre></div></div>
<p>如果存在多个拦截器,拦截规则如下:</p>
<ul>
<li>preHandle 按拦截器定义顺序调用</li>
<li>postHandler 按拦截器定义逆序调用</li>
<li>afterCompletion 按拦截器定义逆序调用</li>
<li>postHandler 在拦截器链内所有拦截器返成功调用</li>
<li>afterCompletion 只有 preHandle 返回 true 才调用</li>
</ul>江南一点雨wangsong0210@gmail.com15. 拦截器 SpringMVC 中的拦截器,相当于 Jsp/Servlet 中的过滤器,只不过拦截器的功能更为强大。 拦截器的定义非常容易: @Component public class MyInterceptor1 implements HandlerInterceptor { /** * 这个是请求预处理的方法,只有当这个方法返回值为 true 的时候,后面的方法才会执行 * @param request * @param response * @param handler * @return * @throws Exception */ public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { System.out.println("MyInterceptor1:preHandle"); return true; } public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception { System.out.println("MyInterceptor1:postHandle"); } public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception { System.out.println("MyInterceptor1:afterCompletion"); } } @Component public class MyInterceptor2 implements HandlerInterceptor { /** * 这个是请求预处理的方法,只有当这个方法返回值为 true 的时候,后面的方法才会执行 * @param request * @param response * @param handler * @return * @throws Exception */ public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { System.out.println("MyInterceptor2:preHandle"); return true; } public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception { System.out.println("MyInterceptor2:postHandle"); } public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception { System.out.println("MyInterceptor2:afterCompletion"); } } 拦截器定义好之后,需要在 SpringMVC 的配置文件中进行配置: <mvc:interceptors> <mvc:interceptor> <mvc:mapping path="/**"/> <ref bean="myInterceptor1"/> </mvc:interceptor> <mvc:interceptor> <mvc:mapping path="/**"/> <ref bean="myInterceptor2"/> </mvc:interceptor> </mvc:interceptors> 如果存在多个拦截器,拦截规则如下: preHandle 按拦截器定义顺序调用 postHandler 按拦截器定义逆序调用 afterCompletion 按拦截器定义逆序调用 postHandler 在拦截器链内所有拦截器返成功调用 afterCompletion 只有 preHandle 返回 true 才调用14. 静态资源访问2019-11-18T22:28:52+00:002019-11-18T22:28:52+00:00http://springboot.javaboy.org/2019/1118/static-resources<h2 id="14-静态资源访问">14. 静态资源访问</h2>
<p>在 SpringMVC 中,静态资源,默认都是被拦截的,例如 html、js、css、jpg、png、txt、pdf 等等,都是无法直接访问的。因为所有请求都被拦截了,所以,针对静态资源,我们要做额外处理,处理方式很简单,直接在 SpringMVC 的配置文件中,添加如下内容:</p>
<div class="language-xml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nt"><mvc:resources</span> <span class="na">mapping=</span><span class="s">"/static/html/**"</span> <span class="na">location=</span><span class="s">"/static/html/"</span><span class="nt">/></span>
</code></pre></div></div>
<p>mapping 表示映射规则,也是拦截规则,就是说,如果请求地址是 /static/html 这样的格式的话,那么对应的资源就去 /static/html/ 这个目录下查找。</p>
<p>在映射路径的定义中,最后是两个 *,这是一种 Ant 风格的路径匹配符号,一共有三个通配符:</p>
<table>
<thead>
<tr>
<th style="text-align: left">通配符</th>
<th style="text-align: left">含义</th>
</tr>
</thead>
<tbody>
<tr>
<td style="text-align: left">**</td>
<td style="text-align: left">匹配多层路径</td>
</tr>
<tr>
<td style="text-align: left">*</td>
<td style="text-align: left">匹配一层路径</td>
</tr>
<tr>
<td style="text-align: left">?</td>
<td style="text-align: left">匹配任意单个字符</td>
</tr>
</tbody>
</table>
<p>一个比较原始的配置方式可能如下:</p>
<div class="language-xml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nt"><mvc:resources</span> <span class="na">mapping=</span><span class="s">"/static/html/**"</span> <span class="na">location=</span><span class="s">"/static/html/"</span><span class="nt">/></span>
<span class="nt"><mvc:resources</span> <span class="na">mapping=</span><span class="s">"/static/js/**"</span> <span class="na">location=</span><span class="s">"/static/js/"</span><span class="nt">/></span>
<span class="nt"><mvc:resources</span> <span class="na">mapping=</span><span class="s">"/static/css/**"</span> <span class="na">location=</span><span class="s">"/static/css/"</span><span class="nt">/></span>
</code></pre></div></div>
<p>但是,由于 ** 可以表示多级路径,所以,以上配置,我们可以进行简化:</p>
<div class="language-xml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nt"><mvc:resources</span> <span class="na">mapping=</span><span class="s">"/**"</span> <span class="na">location=</span><span class="s">"/"</span><span class="nt">/></span>
</code></pre></div></div>江南一点雨wangsong0210@gmail.com14. 静态资源访问 在 SpringMVC 中,静态资源,默认都是被拦截的,例如 html、js、css、jpg、png、txt、pdf 等等,都是无法直接访问的。因为所有请求都被拦截了,所以,针对静态资源,我们要做额外处理,处理方式很简单,直接在 SpringMVC 的配置文件中,添加如下内容: <mvc:resources mapping="/static/html/**" location="/static/html/"/> mapping 表示映射规则,也是拦截规则,就是说,如果请求地址是 /static/html 这样的格式的话,那么对应的资源就去 /static/html/ 这个目录下查找。 在映射路径的定义中,最后是两个 *,这是一种 Ant 风格的路径匹配符号,一共有三个通配符: 通配符 含义 ** 匹配多层路径 * 匹配一层路径 ? 匹配任意单个字符 一个比较原始的配置方式可能如下: <mvc:resources mapping="/static/html/**" location="/static/html/"/> <mvc:resources mapping="/static/js/**" location="/static/js/"/> <mvc:resources mapping="/static/css/**" location="/static/css/"/> 但是,由于 ** 可以表示多级路径,所以,以上配置,我们可以进行简化: <mvc:resources mapping="/**" location="/"/>13. RESTful2019-11-17T22:28:52+00:002019-11-17T22:28:52+00:00http://springboot.javaboy.org/2019/1117/restful<h2 id="13-restful">13. RESTful</h2>
<p>本小节选自外部博客,原文链接:https://www.ruanyifeng.com/blog/2011/09/restful.html</p>
<p>越来越多的人开始意识到,网站即软件,而且是一种新型的软件。这种”互联网软件”采用客户端/服务器模式,建立在分布式体系上,通过互联网通信,具有高延时(high latency)、高并发等特点。网站开发,完全可以采用软件开发的模式。但是传统上,软件和网络是两个不同的领域,很少有交集;软件开发主要针对单机环境,网络则主要研究系统之间的通信。互联网的兴起,使得这两个领域开始融合,现在我们必须考虑,如何开发在互联网环境中使用的软件。</p>
<p>RESTful 架构,就是目前最流行的一种互联网软件架构。它结构清晰、符合标准、易于理解、扩展方便,所以正得到越来越多网站的采用。</p>
<p>但是,到底什么是 RESTful 架构,并不是一个容易说清楚的问题。下面,我就谈谈我理解的 RESTful 架构。、</p>
<p>RESTful 它不是一个具体的架构,不是一个软件,不是一个框架,而是一种规范。在移动互联网兴起之前,我们都很少提及 RESTful,主要是因为用的少,移动互联网兴起后,RESTful 得到了非常广泛的应用,因为在移动互联网兴起之后,我们再开发后端应用,就不仅仅只是开发一个网站了,还对应了多个前端(Android、iOS、HTML5 等等),这个时候,我们在设计后端接口是,就需要考虑接口的形式,格式,参数的传递等等诸多问题了。</p>
<h3 id="131-起源">13.1 起源</h3>
<p>REST 这个词,是 Roy Thomas Fielding 在他 2000 年的博士论文中提出的。</p>
<p>Fielding 是一个非常重要的人,他是 HTTP 协议(1.0版和1.1版)的主要设计者、Apache 服务器软件的作者之一、Apache 基金会的第一任主席。所以,他的这篇论文一经发表,就引起了关注,并且立即对互联网开发产生了深远的影响。</p>
<p>他这样介绍论文的写作目的:</p>
<p>“本文研究计算机科学两大前沿—-软件和网络—-的交叉点。长期以来,软件研究主要关注软件设计的分类、设计方法的演化,很少客观地评估不同的设计选择对系统行为的影响。而相反地,网络研究主要关注系统之间通信行为的细节、如何改进特定通信机制的表现,常常忽视了一个事实,那就是改变应用程序的互动风格比改变互动协议,对整体表现有更大的影响。我这篇文章的写作目的,就是想在符合架构原理的前提下,理解和评估以网络为基础的应用软件的架构设计,得到一个功能强、性能好、适宜通信的架构。”</p>
<h3 id="132-名称">13.2 名称</h3>
<p>Fielding 将他对互联网软件的架构原则,定名为REST,即 Representational State Transfer 的缩写。我对这个词组的翻译是”表现层状态转化”。</p>
<p>如果一个架构符合 REST 原则,就称它为 RESTful 架构。</p>
<p>要理解 RESTful 架构,最好的方法就是去理解 Representational State Transfer 这个词组到底是什么意思,它的每一个词代表了什么涵义。如果你把这个名称搞懂了,也就不难体会 REST 是一种什么样的设计。</p>
<h3 id="133-资源resources">13.3 资源(Resources)</h3>
<p>REST 的名称”表现层状态转化”中,省略了主语。”表现层”其实指的是”资源”(Resources)的”表现层”。</p>
<p>所谓”资源”,就是网络上的一个实体,或者说是网络上的一个具体信息。它可以是一段文本、一张图片、一首歌曲、一种服务,总之就是一个具体的实在。你可以用一个 URI (统一资源定位符)指向它,每种资源对应一个特定的 URI。要获取这个资源,访问它的 URI 就可以,因此 URI 就成了每一个资源的地址或独一无二的识别符。</p>
<p>所谓”上网”,就是与互联网上一系列的”资源”互动,调用它的 URI。</p>
<blockquote>
<p>在 RESTful 风格的应用中,每一个 URI 都代表了一个资源。</p>
</blockquote>
<h3 id="134-表现层representation">13.4 表现层(Representation)</h3>
<p>“资源”是一种信息实体,它可以有多种外在表现形式。我们把”资源”具体呈现出来的形式,叫做它的”表现层”(Representation)。</p>
<p>比如,文本可以用 txt 格式表现,也可以用 HTML 格式、XML 格式、JSON 格式表现,甚至可以采用二进制格式;图片可以用 JPG 格式表现,也可以用 PNG 格式表现。</p>
<p>URI 只代表资源的实体,不代表它的形式。严格地说,有些网址最后的 “.html” 后缀名是不必要的,因为这个后缀名表示格式,属于 “表现层” 范畴,而 URI 应该只代表”资源”的位置。它的具体表现形式,应该在 HTTP 请求的头信息中用 Accept 和 Content-Type 字段指定,这两个字段才是对”表现层”的描述。</p>
<h3 id="135-状态转化state-transfer">13.5 状态转化(State Transfer)</h3>
<p>访问一个网站,就代表了客户端和服务器的一个互动过程。在这个过程中,势必涉及到数据和状态的变化。</p>
<p>互联网通信协议 HTTP 协议,是一个无状态协议。这意味着,所有的状态都保存在服务器端。因此,如果客户端想要操作服务器,必须通过某种手段,让服务器端发生”状态转化”(State Transfer)。而这种转化是建立在表现层之上的,所以就是”表现层状态转化”。</p>
<p>客户端用到的手段,只能是 HTTP 协议。具体来说,就是 HTTP 协议里面,四个表示操作方式的动词:GET、POST、PUT、DELETE。它们分别对应四种基本操作:</p>
<ul>
<li>GET 用来获取资源</li>
<li>POST 用来新建资源(也可以用于更新资源)</li>
<li>PUT 用来更新资源</li>
<li>DELETE 用来删除资源</li>
</ul>
<h3 id="136-综述">13.6 综述</h3>
<p>综合上面的解释,我们总结一下什么是 RESTful 架构:</p>
<ul>
<li>每一个 URI 代表一种资源;</li>
<li>客户端和服务器之间,传递这种资源的某种表现层;</li>
<li>客户端通过四个 HTTP 动词,对服务器端资源进行操作,实现”表现层状态转化”。</li>
</ul>
<h3 id="137-误区">13.7 误区</h3>
<p>RESTful 架构有一些典型的设计误区。</p>
<p>最常见的一种设计错误,就是 URI 包含动词。因为”资源”表示一种实体,所以应该是名词,URI 不应该有动词,动词应该放在 HTTP 协议中。</p>
<p>举例来说,某个 URI 是 /posts/show/1,其中 show 是动词,这个 URI 就设计错了,正确的写法应该是 /posts/1,然后用 GET 方法表示 show。</p>
<p>如果某些动作是HTTP动词表示不了的,你就应该把动作做成一种资源。比如网上汇款,从账户 1 向账户 2 汇款 500 元,错误的 URI 是:</p>
<ul>
<li>POST /accounts/1/transfer/500/to/2</li>
</ul>
<p>正确的写法是把动词 transfer 改成名词 transaction,资源不能是动词,但是可以是一种服务:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>POST /transaction HTTP/1.1
Host: 127.0.0.1
from=1&to=2&amount=500.00
</code></pre></div></div>
<p>另一个设计误区,就是在URI中加入版本号:</p>
<ul>
<li>http://www.example.com/app/1.0/foo</li>
<li>http://www.example.com/app/1.1/foo</li>
<li>http://www.example.com/app/2.0/foo</li>
</ul>
<p>因为不同的版本,可以理解成同一种资源的不同表现形式,所以应该采用同一个 URI。版本号可以在 HTTP 请求头信息的 Accept 字段中进行区分(参见 Versioning REST Services):</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Accept: vnd.example-com.foo+json; version=1.0
Accept: vnd.example-com.foo+json; version=1.1
Accept: vnd.example-com.foo+json; version=2.0
</code></pre></div></div>
<h3 id="138-springmvc-的支持">13.8 SpringMVC 的支持</h3>
<p>SpringMVC 对 RESTful 提供了非常全面的支持,主要有如下几个注解:</p>
<ul>
<li>@RestController</li>
</ul>
<p>这个注解是一个组合注解:</p>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nd">@Target</span><span class="o">(</span><span class="nc">ElementType</span><span class="o">.</span><span class="na">TYPE</span><span class="o">)</span>
<span class="nd">@Retention</span><span class="o">(</span><span class="nc">RetentionPolicy</span><span class="o">.</span><span class="na">RUNTIME</span><span class="o">)</span>
<span class="nd">@Documented</span>
<span class="nd">@Controller</span>
<span class="nd">@ResponseBody</span>
<span class="kd">public</span> <span class="nd">@interface</span> <span class="nc">RestController</span> <span class="o">{</span>
<span class="cm">/**
* The value may indicate a suggestion for a logical component name,
* to be turned into a Spring bean in case of an autodetected component.
* @return the suggested component name, if any (or empty String otherwise)
* @since 4.0.1
*/</span>
<span class="nd">@AliasFor</span><span class="o">(</span><span class="n">annotation</span> <span class="o">=</span> <span class="nc">Controller</span><span class="o">.</span><span class="na">class</span><span class="o">)</span>
<span class="nc">String</span> <span class="nf">value</span><span class="o">()</span> <span class="k">default</span> <span class="s">""</span><span class="o">;</span>
<span class="o">}</span>
</code></pre></div></div>
<p>一般,直接用 @RestController 来标记 Controller,可以不使用 @Controller。</p>
<p>请求方法中,提供了常见的请求方法:</p>
<ul>
<li>@PostMapping</li>
<li>@GetMapping</li>
<li>@PutMapping</li>
<li>@DeleteMapping</li>
</ul>
<p>另外还有一个提取请求地址中的参数的注解 @PathVariable:</p>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nd">@GetMapping</span><span class="o">(</span><span class="s">"/book/{id}"</span><span class="o">)</span><span class="c1">//http://localhost:8080/book/2</span>
<span class="kd">public</span> <span class="nc">Book</span> <span class="nf">getBookById</span><span class="o">(</span><span class="nd">@PathVariable</span> <span class="nc">Integer</span> <span class="n">id</span><span class="o">)</span> <span class="o">{</span>
<span class="nc">Book</span> <span class="n">book</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">Book</span><span class="o">();</span>
<span class="n">book</span><span class="o">.</span><span class="na">setId</span><span class="o">(</span><span class="n">id</span><span class="o">);</span>
<span class="k">return</span> <span class="n">book</span><span class="o">;</span>
<span class="o">}</span>
</code></pre></div></div>
<p>参数 2 将被传递到 id 这个变量上。</p>江南一点雨wangsong0210@gmail.com13. RESTful 本小节选自外部博客,原文链接:https://www.ruanyifeng.com/blog/2011/09/restful.html 越来越多的人开始意识到,网站即软件,而且是一种新型的软件。这种”互联网软件”采用客户端/服务器模式,建立在分布式体系上,通过互联网通信,具有高延时(high latency)、高并发等特点。网站开发,完全可以采用软件开发的模式。但是传统上,软件和网络是两个不同的领域,很少有交集;软件开发主要针对单机环境,网络则主要研究系统之间的通信。互联网的兴起,使得这两个领域开始融合,现在我们必须考虑,如何开发在互联网环境中使用的软件。 RESTful 架构,就是目前最流行的一种互联网软件架构。它结构清晰、符合标准、易于理解、扩展方便,所以正得到越来越多网站的采用。 但是,到底什么是 RESTful 架构,并不是一个容易说清楚的问题。下面,我就谈谈我理解的 RESTful 架构。、 RESTful 它不是一个具体的架构,不是一个软件,不是一个框架,而是一种规范。在移动互联网兴起之前,我们都很少提及 RESTful,主要是因为用的少,移动互联网兴起后,RESTful 得到了非常广泛的应用,因为在移动互联网兴起之后,我们再开发后端应用,就不仅仅只是开发一个网站了,还对应了多个前端(Android、iOS、HTML5 等等),这个时候,我们在设计后端接口是,就需要考虑接口的形式,格式,参数的传递等等诸多问题了。 13.1 起源 REST 这个词,是 Roy Thomas Fielding 在他 2000 年的博士论文中提出的。 Fielding 是一个非常重要的人,他是 HTTP 协议(1.0版和1.1版)的主要设计者、Apache 服务器软件的作者之一、Apache 基金会的第一任主席。所以,他的这篇论文一经发表,就引起了关注,并且立即对互联网开发产生了深远的影响。 他这样介绍论文的写作目的: “本文研究计算机科学两大前沿—-软件和网络—-的交叉点。长期以来,软件研究主要关注软件设计的分类、设计方法的演化,很少客观地评估不同的设计选择对系统行为的影响。而相反地,网络研究主要关注系统之间通信行为的细节、如何改进特定通信机制的表现,常常忽视了一个事实,那就是改变应用程序的互动风格比改变互动协议,对整体表现有更大的影响。我这篇文章的写作目的,就是想在符合架构原理的前提下,理解和评估以网络为基础的应用软件的架构设计,得到一个功能强、性能好、适宜通信的架构。” 13.2 名称 Fielding 将他对互联网软件的架构原则,定名为REST,即 Representational State Transfer 的缩写。我对这个词组的翻译是”表现层状态转化”。 如果一个架构符合 REST 原则,就称它为 RESTful 架构。 要理解 RESTful 架构,最好的方法就是去理解 Representational State Transfer 这个词组到底是什么意思,它的每一个词代表了什么涵义。如果你把这个名称搞懂了,也就不难体会 REST 是一种什么样的设计。 13.3 资源(Resources) REST 的名称”表现层状态转化”中,省略了主语。”表现层”其实指的是”资源”(Resources)的”表现层”。 所谓”资源”,就是网络上的一个实体,或者说是网络上的一个具体信息。它可以是一段文本、一张图片、一首歌曲、一种服务,总之就是一个具体的实在。你可以用一个 URI (统一资源定位符)指向它,每种资源对应一个特定的 URI。要获取这个资源,访问它的 URI 就可以,因此 URI 就成了每一个资源的地址或独一无二的识别符。 所谓”上网”,就是与互联网上一系列的”资源”互动,调用它的 URI。 在 RESTful 风格的应用中,每一个 URI 都代表了一个资源。 13.4 表现层(Representation) “资源”是一种信息实体,它可以有多种外在表现形式。我们把”资源”具体呈现出来的形式,叫做它的”表现层”(Representation)。 比如,文本可以用 txt 格式表现,也可以用 HTML 格式、XML 格式、JSON 格式表现,甚至可以采用二进制格式;图片可以用 JPG 格式表现,也可以用 PNG 格式表现。 URI 只代表资源的实体,不代表它的形式。严格地说,有些网址最后的 “.html” 后缀名是不必要的,因为这个后缀名表示格式,属于 “表现层” 范畴,而 URI 应该只代表”资源”的位置。它的具体表现形式,应该在 HTTP 请求的头信息中用 Accept 和 Content-Type 字段指定,这两个字段才是对”表现层”的描述。 13.5 状态转化(State Transfer) 访问一个网站,就代表了客户端和服务器的一个互动过程。在这个过程中,势必涉及到数据和状态的变化。 互联网通信协议 HTTP 协议,是一个无状态协议。这意味着,所有的状态都保存在服务器端。因此,如果客户端想要操作服务器,必须通过某种手段,让服务器端发生”状态转化”(State Transfer)。而这种转化是建立在表现层之上的,所以就是”表现层状态转化”。 客户端用到的手段,只能是 HTTP 协议。具体来说,就是 HTTP 协议里面,四个表示操作方式的动词:GET、POST、PUT、DELETE。它们分别对应四种基本操作: GET 用来获取资源 POST 用来新建资源(也可以用于更新资源) PUT 用来更新资源 DELETE 用来删除资源 13.6 综述 综合上面的解释,我们总结一下什么是 RESTful 架构: 每一个 URI 代表一种资源; 客户端和服务器之间,传递这种资源的某种表现层; 客户端通过四个 HTTP 动词,对服务器端资源进行操作,实现”表现层状态转化”。 13.7 误区 RESTful 架构有一些典型的设计误区。 最常见的一种设计错误,就是 URI 包含动词。因为”资源”表示一种实体,所以应该是名词,URI 不应该有动词,动词应该放在 HTTP 协议中。 举例来说,某个 URI 是 /posts/show/1,其中 show 是动词,这个 URI 就设计错了,正确的写法应该是 /posts/1,然后用 GET 方法表示 show。 如果某些动作是HTTP动词表示不了的,你就应该把动作做成一种资源。比如网上汇款,从账户 1 向账户 2 汇款 500 元,错误的 URI 是: POST /accounts/1/transfer/500/to/2 正确的写法是把动词 transfer 改成名词 transaction,资源不能是动词,但是可以是一种服务: POST /transaction HTTP/1.1 Host: 127.0.0.1 from=1&to=2&amount=500.00 另一个设计误区,就是在URI中加入版本号: http://www.example.com/app/1.0/foo http://www.example.com/app/1.1/foo http://www.example.com/app/2.0/foo 因为不同的版本,可以理解成同一种资源的不同表现形式,所以应该采用同一个 URI。版本号可以在 HTTP 请求头信息的 Accept 字段中进行区分(参见 Versioning REST Services): Accept: vnd.example-com.foo+json; version=1.0 Accept: vnd.example-com.foo+json; version=1.1 Accept: vnd.example-com.foo+json; version=2.0 13.8 SpringMVC 的支持 SpringMVC 对 RESTful 提供了非常全面的支持,主要有如下几个注解: @RestController 这个注解是一个组合注解: @Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) @Documented @Controller @ResponseBody public @interface RestController { /** * The value may indicate a suggestion for a logical component name, * to be turned into a Spring bean in case of an autodetected component. * @return the suggested component name, if any (or empty String otherwise) * @since 4.0.1 */ @AliasFor(annotation = Controller.class) String value() default ""; } 一般,直接用 @RestController 来标记 Controller,可以不使用 @Controller。 请求方法中,提供了常见的请求方法: @PostMapping @GetMapping @PutMapping @DeleteMapping 另外还有一个提取请求地址中的参数的注解 @PathVariable: @GetMapping("/book/{id}")//http://localhost:8080/book/2 public Book getBookById(@PathVariable Integer id) { Book book = new Book(); book.setId(id); return book; } 参数 2 将被传递到 id 这个变量上。12.2 接收 JSON2019-11-16T22:28:52+00:002019-11-16T22:28:52+00:00http://springboot.javaboy.org/2019/1116/receive-json<h2 id="122-接收-json">12.2 接收 JSON</h2>
<p>浏览器传来的参数,可以是 key/value 形式的,也可以是一个 JSON 字符串。在 Jsp/Servlet 中,我们接收 key/value 形式的参数,一般是通过 getParameter 方法。如果客户端商户惨的是 JSON 数据,我们可以通过如下格式进行解析:</p>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nd">@RequestMapping</span><span class="o">(</span><span class="s">"/addbook2"</span><span class="o">)</span>
<span class="nd">@ResponseBody</span>
<span class="kd">public</span> <span class="kt">void</span> <span class="nf">addBook2</span><span class="o">(</span><span class="nc">HttpServletRequest</span> <span class="n">req</span><span class="o">)</span> <span class="kd">throws</span> <span class="nc">IOException</span> <span class="o">{</span>
<span class="nc">ObjectMapper</span> <span class="n">om</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">ObjectMapper</span><span class="o">();</span>
<span class="nc">Book</span> <span class="n">book</span> <span class="o">=</span> <span class="n">om</span><span class="o">.</span><span class="na">readValue</span><span class="o">(</span><span class="n">req</span><span class="o">.</span><span class="na">getInputStream</span><span class="o">(),</span> <span class="nc">Book</span><span class="o">.</span><span class="na">class</span><span class="o">);</span>
<span class="nc">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">println</span><span class="o">(</span><span class="n">book</span><span class="o">);</span>
<span class="o">}</span>
</code></pre></div></div>
<p>但是这种解析方式有点麻烦,在 SpringMVC 中,我们可以通过一个注解来快速的将一个 JSON 字符串转为一个对象:</p>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nd">@RequestMapping</span><span class="o">(</span><span class="s">"/addbook3"</span><span class="o">)</span>
<span class="nd">@ResponseBody</span>
<span class="kd">public</span> <span class="kt">void</span> <span class="nf">addBook3</span><span class="o">(</span><span class="nd">@RequestBody</span> <span class="nc">Book</span> <span class="n">book</span><span class="o">)</span> <span class="o">{</span>
<span class="nc">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">println</span><span class="o">(</span><span class="n">book</span><span class="o">);</span>
<span class="o">}</span>
</code></pre></div></div>
<p>这样就可以直接收到前端传来的 JSON 字符串了。这也是 HttpMessageConverter 提供的第二个功能。</p>江南一点雨wangsong0210@gmail.com12.2 接收 JSON 浏览器传来的参数,可以是 key/value 形式的,也可以是一个 JSON 字符串。在 Jsp/Servlet 中,我们接收 key/value 形式的参数,一般是通过 getParameter 方法。如果客户端商户惨的是 JSON 数据,我们可以通过如下格式进行解析: @RequestMapping("/addbook2") @ResponseBody public void addBook2(HttpServletRequest req) throws IOException { ObjectMapper om = new ObjectMapper(); Book book = om.readValue(req.getInputStream(), Book.class); System.out.println(book); } 但是这种解析方式有点麻烦,在 SpringMVC 中,我们可以通过一个注解来快速的将一个 JSON 字符串转为一个对象: @RequestMapping("/addbook3") @ResponseBody public void addBook3(@RequestBody Book book) { System.out.println(book); } 这样就可以直接收到前端传来的 JSON 字符串了。这也是 HttpMessageConverter 提供的第二个功能。12.1 返回 JSON2019-11-15T22:28:52+00:002019-11-15T22:28:52+00:00http://springboot.javaboy.org/2019/1115/return-json<h2 id="121-返回-json">12.1 返回 JSON</h2>
<p>目前主流的 JSON 处理工具主要有三种:</p>
<ul>
<li>jackson</li>
<li>gson</li>
<li>fastjson</li>
</ul>
<p>在 SpringMVC 中,对 jackson 和 gson 都提供了相应的支持,就是如果使用这两个作为 JSON 转换器,只需要添加对应的依赖就可以了,返回的对象和返回的集合、Map 等都会自动转为 JSON,但是,如果使用 fastjson,除了添加相应的依赖之外,还需要自己手动配置 HttpMessageConverter 转换器。其实前两个也是使用 HttpMessageConverter 转换器,但是是 SpringMVC 自动提供的,SpringMVC 没有给 fastjson 提供相应的转换器。</p>
<h3 id="1211-jackson">12.1.1 jackson</h3>
<p>jackson 是一个使用比较多,时间也比较长的 JSON 处理工具,在 SpringMVC 中使用 jackson ,只需要添加 jackson 的依赖即可:</p>
<div class="language-xml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nt"><dependency></span>
<span class="nt"><groupId></span>com.fasterxml.jackson.core<span class="nt"></groupId></span>
<span class="nt"><artifactId></span>jackson-databind<span class="nt"></artifactId></span>
<span class="nt"><version></span>2.10.1<span class="nt"></version></span>
<span class="nt"></dependency></span>
</code></pre></div></div>
<p>依赖添加成功后,凡是在接口中直接返回的对象,集合等等,都会自动转为 JSON。如下:</p>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">public</span> <span class="kd">class</span> <span class="nc">Book</span> <span class="o">{</span>
<span class="kd">private</span> <span class="nc">Integer</span> <span class="n">id</span><span class="o">;</span>
<span class="kd">private</span> <span class="nc">String</span> <span class="n">name</span><span class="o">;</span>
<span class="kd">private</span> <span class="nc">String</span> <span class="n">author</span><span class="o">;</span>
<span class="kd">public</span> <span class="nc">String</span> <span class="nf">getName</span><span class="o">()</span> <span class="o">{</span>
<span class="k">return</span> <span class="n">name</span><span class="o">;</span>
<span class="o">}</span>
<span class="kd">public</span> <span class="kt">void</span> <span class="nf">setName</span><span class="o">(</span><span class="nc">String</span> <span class="n">name</span><span class="o">)</span> <span class="o">{</span>
<span class="k">this</span><span class="o">.</span><span class="na">name</span> <span class="o">=</span> <span class="n">name</span><span class="o">;</span>
<span class="o">}</span>
<span class="kd">public</span> <span class="nc">String</span> <span class="nf">getAuthor</span><span class="o">()</span> <span class="o">{</span>
<span class="k">return</span> <span class="n">author</span><span class="o">;</span>
<span class="o">}</span>
<span class="kd">public</span> <span class="kt">void</span> <span class="nf">setAuthor</span><span class="o">(</span><span class="nc">String</span> <span class="n">author</span><span class="o">)</span> <span class="o">{</span>
<span class="k">this</span><span class="o">.</span><span class="na">author</span> <span class="o">=</span> <span class="n">author</span><span class="o">;</span>
<span class="o">}</span>
<span class="kd">public</span> <span class="nc">Integer</span> <span class="nf">getId</span><span class="o">()</span> <span class="o">{</span>
<span class="k">return</span> <span class="n">id</span><span class="o">;</span>
<span class="o">}</span>
<span class="kd">public</span> <span class="kt">void</span> <span class="nf">setId</span><span class="o">(</span><span class="nc">Integer</span> <span class="n">id</span><span class="o">)</span> <span class="o">{</span>
<span class="k">this</span><span class="o">.</span><span class="na">id</span> <span class="o">=</span> <span class="n">id</span><span class="o">;</span>
<span class="o">}</span>
<span class="o">}</span>
<span class="nd">@RequestMapping</span><span class="o">(</span><span class="s">"/book"</span><span class="o">)</span>
<span class="nd">@ResponseBody</span>
<span class="kd">public</span> <span class="nc">Book</span> <span class="nf">getBookById</span><span class="o">()</span> <span class="o">{</span>
<span class="nc">Book</span> <span class="n">book</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">Book</span><span class="o">();</span>
<span class="n">book</span><span class="o">.</span><span class="na">setId</span><span class="o">(</span><span class="mi">1</span><span class="o">);</span>
<span class="n">book</span><span class="o">.</span><span class="na">setName</span><span class="o">(</span><span class="s">"三国演义"</span><span class="o">);</span>
<span class="n">book</span><span class="o">.</span><span class="na">setAuthor</span><span class="o">(</span><span class="s">"罗贯中"</span><span class="o">);</span>
<span class="k">return</span> <span class="n">book</span><span class="o">;</span>
<span class="o">}</span>
</code></pre></div></div>
<p>这里返回一个对象,但是在前端接收到的则是一个 JSON 字符串,这个对象会通过 HttpMessageConverter 自动转为 JSON 字符串。</p>
<p>如果想返回一个 JSON 数组,写法如下:</p>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nd">@RequestMapping</span><span class="o">(</span><span class="s">"/books"</span><span class="o">)</span>
<span class="nd">@ResponseBody</span>
<span class="kd">public</span> <span class="nc">List</span><span class="o"><</span><span class="nc">Book</span><span class="o">></span> <span class="nf">getAllBooks</span><span class="o">()</span> <span class="o">{</span>
<span class="nc">List</span><span class="o"><</span><span class="nc">Book</span><span class="o">></span> <span class="n">list</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">ArrayList</span><span class="o"><</span><span class="nc">Book</span><span class="o">>();</span>
<span class="k">for</span> <span class="o">(</span><span class="kt">int</span> <span class="n">i</span> <span class="o">=</span> <span class="mi">0</span><span class="o">;</span> <span class="n">i</span> <span class="o"><</span> <span class="mi">10</span><span class="o">;</span> <span class="n">i</span><span class="o">++)</span> <span class="o">{</span>
<span class="nc">Book</span> <span class="n">book</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">Book</span><span class="o">();</span>
<span class="n">book</span><span class="o">.</span><span class="na">setId</span><span class="o">(</span><span class="n">i</span><span class="o">);</span>
<span class="n">book</span><span class="o">.</span><span class="na">setName</span><span class="o">(</span><span class="s">"三国演义:"</span> <span class="o">+</span> <span class="n">i</span><span class="o">);</span>
<span class="n">book</span><span class="o">.</span><span class="na">setAuthor</span><span class="o">(</span><span class="s">"罗贯中:"</span> <span class="o">+</span> <span class="n">i</span><span class="o">);</span>
<span class="n">list</span><span class="o">.</span><span class="na">add</span><span class="o">(</span><span class="n">book</span><span class="o">);</span>
<span class="o">}</span>
<span class="k">return</span> <span class="n">list</span><span class="o">;</span>
<span class="o">}</span>
</code></pre></div></div>
<p>添加了 jackson ,就能够自动返回 JSON,这个依赖于一个名为 HttpMessageConverter 的类,这本身是一个接口,从名字上就可以看出,它的作用是 Http 消息转换器,既然是消息转换器,它提供了两方面的功能:</p>
<ol>
<li>将返回的对象转为 JSON</li>
<li>将前端提交上来的 JSON 转为对象</li>
</ol>
<p>但是,HttpMessageConverter 只是一个接口,由各个 JSON 工具提供相应的实现,在 jackson 中,实现的名字叫做 MappingJackson2HttpMessageConverter,而这个东西的初始化,则由 SpringMVC 来完成。除非自己有一些自定义配置的需求,否则一般来说不需要自己提供 MappingJackson2HttpMessageConverter。</p>
<p>举一个简单的应用场景,例如每一本书,都有一个出版日期,修改 Book 类如下:</p>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">public</span> <span class="kd">class</span> <span class="nc">Book</span> <span class="o">{</span>
<span class="kd">private</span> <span class="nc">Integer</span> <span class="n">id</span><span class="o">;</span>
<span class="kd">private</span> <span class="nc">String</span> <span class="n">name</span><span class="o">;</span>
<span class="kd">private</span> <span class="nc">String</span> <span class="n">author</span><span class="o">;</span>
<span class="kd">private</span> <span class="nc">Date</span> <span class="n">publish</span><span class="o">;</span>
<span class="kd">public</span> <span class="nc">Date</span> <span class="nf">getPublish</span><span class="o">()</span> <span class="o">{</span>
<span class="k">return</span> <span class="n">publish</span><span class="o">;</span>
<span class="o">}</span>
<span class="kd">public</span> <span class="kt">void</span> <span class="nf">setPublish</span><span class="o">(</span><span class="nc">Date</span> <span class="n">publish</span><span class="o">)</span> <span class="o">{</span>
<span class="k">this</span><span class="o">.</span><span class="na">publish</span> <span class="o">=</span> <span class="n">publish</span><span class="o">;</span>
<span class="o">}</span>
<span class="kd">public</span> <span class="nc">String</span> <span class="nf">getName</span><span class="o">()</span> <span class="o">{</span>
<span class="k">return</span> <span class="n">name</span><span class="o">;</span>
<span class="o">}</span>
<span class="kd">public</span> <span class="kt">void</span> <span class="nf">setName</span><span class="o">(</span><span class="nc">String</span> <span class="n">name</span><span class="o">)</span> <span class="o">{</span>
<span class="k">this</span><span class="o">.</span><span class="na">name</span> <span class="o">=</span> <span class="n">name</span><span class="o">;</span>
<span class="o">}</span>
<span class="kd">public</span> <span class="nc">String</span> <span class="nf">getAuthor</span><span class="o">()</span> <span class="o">{</span>
<span class="k">return</span> <span class="n">author</span><span class="o">;</span>
<span class="o">}</span>
<span class="kd">public</span> <span class="kt">void</span> <span class="nf">setAuthor</span><span class="o">(</span><span class="nc">String</span> <span class="n">author</span><span class="o">)</span> <span class="o">{</span>
<span class="k">this</span><span class="o">.</span><span class="na">author</span> <span class="o">=</span> <span class="n">author</span><span class="o">;</span>
<span class="o">}</span>
<span class="kd">public</span> <span class="nc">Integer</span> <span class="nf">getId</span><span class="o">()</span> <span class="o">{</span>
<span class="k">return</span> <span class="n">id</span><span class="o">;</span>
<span class="o">}</span>
<span class="kd">public</span> <span class="kt">void</span> <span class="nf">setId</span><span class="o">(</span><span class="nc">Integer</span> <span class="n">id</span><span class="o">)</span> <span class="o">{</span>
<span class="k">this</span><span class="o">.</span><span class="na">id</span> <span class="o">=</span> <span class="n">id</span><span class="o">;</span>
<span class="o">}</span>
<span class="o">}</span>
</code></pre></div></div>
<p>然后在构造 Book 时添加日期属性:</p>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nd">@RequestMapping</span><span class="o">(</span><span class="s">"/book"</span><span class="o">)</span>
<span class="nd">@ResponseBody</span>
<span class="kd">public</span> <span class="nc">Book</span> <span class="nf">getBookById</span><span class="o">()</span> <span class="o">{</span>
<span class="nc">Book</span> <span class="n">book</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">Book</span><span class="o">();</span>
<span class="n">book</span><span class="o">.</span><span class="na">setId</span><span class="o">(</span><span class="mi">1</span><span class="o">);</span>
<span class="n">book</span><span class="o">.</span><span class="na">setName</span><span class="o">(</span><span class="s">"三国演义"</span><span class="o">);</span>
<span class="n">book</span><span class="o">.</span><span class="na">setAuthor</span><span class="o">(</span><span class="s">"罗贯中"</span><span class="o">);</span>
<span class="n">book</span><span class="o">.</span><span class="na">setPublish</span><span class="o">(</span><span class="k">new</span> <span class="nc">Date</span><span class="o">());</span>
<span class="k">return</span> <span class="n">book</span><span class="o">;</span>
<span class="o">}</span>
</code></pre></div></div>
<p>访问 /book 接口,返回的 json 格式如下:</p>
<p><img src="http://springmvc.javaboy.org/assets/images/img/12-1-1.png" alt="" title="12-1-1.png" /></p>
<p>如果我们想自己定制返回日期的格式,简单的办法,可以通过添加注解来实现:</p>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">public</span> <span class="kd">class</span> <span class="nc">Book</span> <span class="o">{</span>
<span class="kd">private</span> <span class="nc">Integer</span> <span class="n">id</span><span class="o">;</span>
<span class="kd">private</span> <span class="nc">String</span> <span class="n">name</span><span class="o">;</span>
<span class="kd">private</span> <span class="nc">String</span> <span class="n">author</span><span class="o">;</span>
<span class="nd">@JsonFormat</span><span class="o">(</span><span class="n">pattern</span> <span class="o">=</span> <span class="s">"yyyy-MM-dd"</span><span class="o">,</span><span class="n">timezone</span> <span class="o">=</span> <span class="s">"Asia/Shanghai"</span><span class="o">)</span>
<span class="kd">private</span> <span class="nc">Date</span> <span class="n">publish</span><span class="o">;</span>
</code></pre></div></div>
<p>注意这里一定要设置时区。</p>
<p>这样,就可以定制返回的日期格式了。</p>
<p>但是,这种方式有一个弊端,这个注解可以加在属性上,也可以加在类上,也就说,最大可以作用到一个类中的所有日期属性上。如果项目中有很多实体类都需要做日期格式化,使用这种方式就比较麻烦了,这个时候,我们可以自己提供一个 jackson 的 HttpMesageConverter 实例,在这个实例中,自己去配置相关属性,这里的配置将是一个全局配置。</p>
<p>在 SpringMVC 配置文件中,添加如下配置:</p>
<div class="language-xml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nt"><mvc:annotation-driven></span>
<span class="nt"><mvc:message-converters></span>
<span class="nt"><ref</span> <span class="na">bean=</span><span class="s">"httpMessageConverter"</span><span class="nt">/></span>
<span class="nt"></mvc:message-converters></span>
<span class="nt"></mvc:annotation-driven></span>
<span class="nt"><bean</span> <span class="na">class=</span><span class="s">"org.springframework.http.converter.json.MappingJackson2HttpMessageConverter"</span> <span class="na">id=</span><span class="s">"httpMessageConverter"</span><span class="nt">></span>
<span class="nt"><property</span> <span class="na">name=</span><span class="s">"objectMapper"</span><span class="nt">></span>
<span class="nt"><bean</span> <span class="na">class=</span><span class="s">"com.fasterxml.jackson.databind.ObjectMapper"</span><span class="nt">></span>
<span class="nt"><property</span> <span class="na">name=</span><span class="s">"dateFormat"</span><span class="nt">></span>
<span class="nt"><bean</span> <span class="na">class=</span><span class="s">"java.text.SimpleDateFormat"</span><span class="nt">></span>
<span class="nt"><constructor-arg</span> <span class="na">name=</span><span class="s">"pattern"</span> <span class="na">value=</span><span class="s">"yyyy-MM-dd HH:mm:ss"</span><span class="nt">/></span>
<span class="nt"></bean></span>
<span class="nt"></property></span>
<span class="nt"><property</span> <span class="na">name=</span><span class="s">"timeZone"</span> <span class="na">value=</span><span class="s">"Asia/Shanghai"</span><span class="nt">/></span>
<span class="nt"></bean></span>
<span class="nt"></property></span>
<span class="nt"></bean></span>
</code></pre></div></div>
<p>添加完成后,去掉 Book 实体类中日期格式化的注解,再进行测试,结果如下:</p>
<h3 id="1212-gson">12.1.2 gson</h3>
<p>gson 是 Google 推出的一个 JSON 解析器,主要在 Android 开发中使用较多,不过,Web 开发中也是支持这个的,而且 SpringMVC 还针对 Gson 提供了相关的自动化配置,以致我们在项目中只要添加 gson 依赖,就可以直接使用 gson 来做 JSON 解析了。</p>
<div class="language-xml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nt"><dependency></span>
<span class="nt"><groupId></span>com.google.code.gson<span class="nt"></groupId></span>
<span class="nt"><artifactId></span>gson<span class="nt"></artifactId></span>
<span class="nt"><version></span>2.8.6<span class="nt"></version></span>
<span class="nt"></dependency></span>
</code></pre></div></div>
<p>如果项目中,同时存在 jackson 和 gson 的话,那么默认使用的是 jackson,为社么呢?在 org.springframework.http.converter.support.AllEncompassingFormHttpMessageConverter 类的构造方法中,加载顺序就是先加载 jackson 的 HttpMessageConverter,后加载 gson 的 HttpMessageConverter。</p>
<p>加完依赖之后,就可以直接返回 JSON 字符串了。使用 Gson 时,如果想做自定义配置,则需要自定义 HttpMessageConverter。</p>
<div class="language-xml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nt"><mvc:annotation-driven></span>
<span class="nt"><mvc:message-converters></span>
<span class="nt"><ref</span> <span class="na">bean=</span><span class="s">"httpMessageConverter"</span><span class="nt">/></span>
<span class="nt"></mvc:message-converters></span>
<span class="nt"></mvc:annotation-driven></span>
<span class="nt"><bean</span> <span class="na">class=</span><span class="s">"org.springframework.http.converter.json.GsonHttpMessageConverter"</span> <span class="na">id=</span><span class="s">"httpMessageConverter"</span><span class="nt">></span>
<span class="nt"><property</span> <span class="na">name=</span><span class="s">"gson"</span><span class="nt">></span>
<span class="nt"><bean</span> <span class="na">class=</span><span class="s">"com.google.gson.Gson"</span> <span class="na">factory-bean=</span><span class="s">"gsonBuilder"</span> <span class="na">factory-method=</span><span class="s">"create"</span><span class="nt">/></span>
<span class="nt"></property></span>
<span class="nt"></bean></span>
<span class="nt"><bean</span> <span class="na">class=</span><span class="s">"com.google.gson.GsonBuilder"</span> <span class="na">id=</span><span class="s">"gsonBuilder"</span><span class="nt">></span>
<span class="nt"><property</span> <span class="na">name=</span><span class="s">"dateFormat"</span> <span class="na">value=</span><span class="s">"yyyy-MM-dd"</span><span class="nt">/></span>
<span class="nt"></bean></span>
</code></pre></div></div>
<h3 id="1213-fastjson">12.1.3 fastjson</h3>
<p>fastjson 号称最快的 JSON 解析器,但是也是这三个中 BUG 最多的一个。在 SpringMVC 并没针对 fastjson 提供相应的 HttpMessageConverter,所以,fastjson 在使用时,一定要自己手动配置 HttpMessageConverter(前面两个如果没有特殊需要,直接添加依赖就可以了)。</p>
<p>使用 fastjson,我们首先添加 fastjson 依赖:</p>
<div class="language-xml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nt"><dependency></span>
<span class="nt"><groupId></span>com.alibaba<span class="nt"></groupId></span>
<span class="nt"><artifactId></span>fastjson<span class="nt"></artifactId></span>
<span class="nt"><version></span>1.2.60<span class="nt"></version></span>
<span class="nt"></dependency></span>
</code></pre></div></div>
<p>然后在 SpringMVC 的配置文件中配置 HttpMessageConverter:</p>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="o"><</span><span class="nl">mvc:</span><span class="n">annotation</span><span class="o">-</span><span class="n">driven</span><span class="o">></span>
<span class="o"><</span><span class="nl">mvc:</span><span class="n">message</span><span class="o">-</span><span class="n">converters</span><span class="o">></span>
<span class="o"><</span><span class="n">ref</span> <span class="n">bean</span><span class="o">=</span><span class="s">"httpMessageConverter"</span><span class="o">/></span>
<span class="o"></</span><span class="nl">mvc:</span><span class="n">message</span><span class="o">-</span><span class="n">converters</span><span class="o">></span>
<span class="o"></</span><span class="nl">mvc:</span><span class="n">annotation</span><span class="o">-</span><span class="n">driven</span><span class="o">></span>
<span class="o"><</span><span class="n">bean</span> <span class="kd">class</span><span class="err">="</span><span class="nc">com</span><span class="o">.</span><span class="na">alibaba</span><span class="o">.</span><span class="na">fastjson</span><span class="o">.</span><span class="na">support</span><span class="o">.</span><span class="na">spring</span><span class="o">.</span><span class="na">FastJsonHttpMessageConverter</span><span class="s">" id="</span><span class="n">httpMessageConverter</span><span class="s">">
<property name="</span><span class="n">fastJsonConfig</span><span class="s">">
<bean class="</span><span class="n">com</span><span class="o">.</span><span class="na">alibaba</span><span class="o">.</span><span class="na">fastjson</span><span class="o">.</span><span class="na">support</span><span class="o">.</span><span class="na">config</span><span class="o">.</span><span class="na">FastJsonConfig</span><span class="s">">
<property name="</span><span class="n">dateFormat</span><span class="s">" value="</span><span class="n">yyyy</span><span class="o">-</span><span class="no">MM</span><span class="o">-</span><span class="n">dd</span><span class="err">"</span><span class="o">/></span>
<span class="o"></</span><span class="n">bean</span><span class="o">></span>
<span class="o"></</span><span class="n">property</span><span class="o">></span>
<span class="o"></</span><span class="n">bean</span><span class="o">></span>
</code></pre></div></div>
<p>fastjson 默认中文乱码,添加如下配置解决:</p>
<div class="language-xml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nt"><mvc:annotation-driven></span>
<span class="nt"><mvc:message-converters></span>
<span class="nt"><ref</span> <span class="na">bean=</span><span class="s">"httpMessageConverter"</span><span class="nt">/></span>
<span class="nt"></mvc:message-converters></span>
<span class="nt"></mvc:annotation-driven></span>
<span class="nt"><bean</span> <span class="na">class=</span><span class="s">"com.alibaba.fastjson.support.spring.FastJsonHttpMessageConverter"</span> <span class="na">id=</span><span class="s">"httpMessageConverter"</span><span class="nt">></span>
<span class="nt"><property</span> <span class="na">name=</span><span class="s">"fastJsonConfig"</span><span class="nt">></span>
<span class="nt"><bean</span> <span class="na">class=</span><span class="s">"com.alibaba.fastjson.support.config.FastJsonConfig"</span><span class="nt">></span>
<span class="nt"><property</span> <span class="na">name=</span><span class="s">"dateFormat"</span> <span class="na">value=</span><span class="s">"yyyy-MM-dd"</span><span class="nt">/></span>
<span class="nt"></bean></span>
<span class="nt"></property></span>
<span class="nt"><property</span> <span class="na">name=</span><span class="s">"supportedMediaTypes"</span><span class="nt">></span>
<span class="nt"><list></span>
<span class="nt"><value></span>application/json;charset=utf-8<span class="nt"></value></span>
<span class="nt"></list></span>
<span class="nt"></property></span>
<span class="nt"></bean></span>
</code></pre></div></div>江南一点雨wangsong0210@gmail.com12.1 返回 JSON 目前主流的 JSON 处理工具主要有三种: jackson gson fastjson 在 SpringMVC 中,对 jackson 和 gson 都提供了相应的支持,就是如果使用这两个作为 JSON 转换器,只需要添加对应的依赖就可以了,返回的对象和返回的集合、Map 等都会自动转为 JSON,但是,如果使用 fastjson,除了添加相应的依赖之外,还需要自己手动配置 HttpMessageConverter 转换器。其实前两个也是使用 HttpMessageConverter 转换器,但是是 SpringMVC 自动提供的,SpringMVC 没有给 fastjson 提供相应的转换器。 12.1.1 jackson jackson 是一个使用比较多,时间也比较长的 JSON 处理工具,在 SpringMVC 中使用 jackson ,只需要添加 jackson 的依赖即可: <dependency> <groupId>com.fasterxml.jackson.core</groupId> <artifactId>jackson-databind</artifactId> <version>2.10.1</version> </dependency> 依赖添加成功后,凡是在接口中直接返回的对象,集合等等,都会自动转为 JSON。如下: public class Book { private Integer id; private String name; private String author; public String getName() { return name; } public void setName(String name) { this.name = name; } public String getAuthor() { return author; } public void setAuthor(String author) { this.author = author; } public Integer getId() { return id; } public void setId(Integer id) { this.id = id; } } @RequestMapping("/book") @ResponseBody public Book getBookById() { Book book = new Book(); book.setId(1); book.setName("三国演义"); book.setAuthor("罗贯中"); return book; } 这里返回一个对象,但是在前端接收到的则是一个 JSON 字符串,这个对象会通过 HttpMessageConverter 自动转为 JSON 字符串。 如果想返回一个 JSON 数组,写法如下: @RequestMapping("/books") @ResponseBody public List<Book> getAllBooks() { List<Book> list = new ArrayList<Book>(); for (int i = 0; i < 10; i++) { Book book = new Book(); book.setId(i); book.setName("三国演义:" + i); book.setAuthor("罗贯中:" + i); list.add(book); } return list; } 添加了 jackson ,就能够自动返回 JSON,这个依赖于一个名为 HttpMessageConverter 的类,这本身是一个接口,从名字上就可以看出,它的作用是 Http 消息转换器,既然是消息转换器,它提供了两方面的功能: 将返回的对象转为 JSON 将前端提交上来的 JSON 转为对象 但是,HttpMessageConverter 只是一个接口,由各个 JSON 工具提供相应的实现,在 jackson 中,实现的名字叫做 MappingJackson2HttpMessageConverter,而这个东西的初始化,则由 SpringMVC 来完成。除非自己有一些自定义配置的需求,否则一般来说不需要自己提供 MappingJackson2HttpMessageConverter。 举一个简单的应用场景,例如每一本书,都有一个出版日期,修改 Book 类如下: public class Book { private Integer id; private String name; private String author; private Date publish; public Date getPublish() { return publish; } public void setPublish(Date publish) { this.publish = publish; } public String getName() { return name; } public void setName(String name) { this.name = name; } public String getAuthor() { return author; } public void setAuthor(String author) { this.author = author; } public Integer getId() { return id; } public void setId(Integer id) { this.id = id; } } 然后在构造 Book 时添加日期属性: @RequestMapping("/book") @ResponseBody public Book getBookById() { Book book = new Book(); book.setId(1); book.setName("三国演义"); book.setAuthor("罗贯中"); book.setPublish(new Date()); return book; } 访问 /book 接口,返回的 json 格式如下: 如果我们想自己定制返回日期的格式,简单的办法,可以通过添加注解来实现: public class Book { private Integer id; private String name; private String author; @JsonFormat(pattern = "yyyy-MM-dd",timezone = "Asia/Shanghai") private Date publish; 注意这里一定要设置时区。 这样,就可以定制返回的日期格式了。 但是,这种方式有一个弊端,这个注解可以加在属性上,也可以加在类上,也就说,最大可以作用到一个类中的所有日期属性上。如果项目中有很多实体类都需要做日期格式化,使用这种方式就比较麻烦了,这个时候,我们可以自己提供一个 jackson 的 HttpMesageConverter 实例,在这个实例中,自己去配置相关属性,这里的配置将是一个全局配置。 在 SpringMVC 配置文件中,添加如下配置: <mvc:annotation-driven> <mvc:message-converters> <ref bean="httpMessageConverter"/> </mvc:message-converters> </mvc:annotation-driven> <bean class="org.springframework.http.converter.json.MappingJackson2HttpMessageConverter" id="httpMessageConverter"> <property name="objectMapper"> <bean class="com.fasterxml.jackson.databind.ObjectMapper"> <property name="dateFormat"> <bean class="java.text.SimpleDateFormat"> <constructor-arg name="pattern" value="yyyy-MM-dd HH:mm:ss"/> </bean> </property> <property name="timeZone" value="Asia/Shanghai"/> </bean> </property> </bean> 添加完成后,去掉 Book 实体类中日期格式化的注解,再进行测试,结果如下: 12.1.2 gson gson 是 Google 推出的一个 JSON 解析器,主要在 Android 开发中使用较多,不过,Web 开发中也是支持这个的,而且 SpringMVC 还针对 Gson 提供了相关的自动化配置,以致我们在项目中只要添加 gson 依赖,就可以直接使用 gson 来做 JSON 解析了。 <dependency> <groupId>com.google.code.gson</groupId> <artifactId>gson</artifactId> <version>2.8.6</version> </dependency> 如果项目中,同时存在 jackson 和 gson 的话,那么默认使用的是 jackson,为社么呢?在 org.springframework.http.converter.support.AllEncompassingFormHttpMessageConverter 类的构造方法中,加载顺序就是先加载 jackson 的 HttpMessageConverter,后加载 gson 的 HttpMessageConverter。 加完依赖之后,就可以直接返回 JSON 字符串了。使用 Gson 时,如果想做自定义配置,则需要自定义 HttpMessageConverter。 <mvc:annotation-driven> <mvc:message-converters> <ref bean="httpMessageConverter"/> </mvc:message-converters> </mvc:annotation-driven> <bean class="org.springframework.http.converter.json.GsonHttpMessageConverter" id="httpMessageConverter"> <property name="gson"> <bean class="com.google.gson.Gson" factory-bean="gsonBuilder" factory-method="create"/> </property> </bean> <bean class="com.google.gson.GsonBuilder" id="gsonBuilder"> <property name="dateFormat" value="yyyy-MM-dd"/> </bean> 12.1.3 fastjson fastjson 号称最快的 JSON 解析器,但是也是这三个中 BUG 最多的一个。在 SpringMVC 并没针对 fastjson 提供相应的 HttpMessageConverter,所以,fastjson 在使用时,一定要自己手动配置 HttpMessageConverter(前面两个如果没有特殊需要,直接添加依赖就可以了)。 使用 fastjson,我们首先添加 fastjson 依赖: <dependency> <groupId>com.alibaba</groupId> <artifactId>fastjson</artifactId> <version>1.2.60</version> </dependency> 然后在 SpringMVC 的配置文件中配置 HttpMessageConverter: <mvc:annotation-driven> <mvc:message-converters> <ref bean="httpMessageConverter"/> </mvc:message-converters> </mvc:annotation-driven> <bean class="com.alibaba.fastjson.support.spring.FastJsonHttpMessageConverter" id="httpMessageConverter"> <property name="fastJsonConfig"> <bean class="com.alibaba.fastjson.support.config.FastJsonConfig"> <property name="dateFormat" value="yyyy-MM-dd"/> </bean> </property> </bean> fastjson 默认中文乱码,添加如下配置解决: <mvc:annotation-driven> <mvc:message-converters> <ref bean="httpMessageConverter"/> </mvc:message-converters> </mvc:annotation-driven> <bean class="com.alibaba.fastjson.support.spring.FastJsonHttpMessageConverter" id="httpMessageConverter"> <property name="fastJsonConfig"> <bean class="com.alibaba.fastjson.support.config.FastJsonConfig"> <property name="dateFormat" value="yyyy-MM-dd"/> </bean> </property> <property name="supportedMediaTypes"> <list> <value>application/json;charset=utf-8</value> </list> </property> </bean>11.2 @ModelAttribute2019-11-14T22:28:52+00:002019-11-14T22:28:52+00:00http://springboot.javaboy.org/2019/1114/modelattribute<h2 id="112-modelattribute">11.2 @ModelAttribute</h2>
<p>@ModelAttribute 这个注解,主要有两方面的功能:</p>
<ol>
<li>在数据回显时,给变量定义别名</li>
<li>定义全局数据</li>
</ol>
<h3 id="1121-定义别名">11.2.1 定义别名</h3>
<p>在数据回显时,给变量定义别名,非常容易,直接加这个注解即可:</p>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nd">@RequestMapping</span><span class="o">(</span><span class="s">"/addstudent"</span><span class="o">)</span>
<span class="kd">public</span> <span class="nc">String</span> <span class="nf">addStudent</span><span class="o">(</span><span class="nd">@ModelAttribute</span><span class="o">(</span><span class="s">"s"</span><span class="o">)</span> <span class="nd">@Validated</span><span class="o">(</span><span class="nc">ValidationGroup2</span><span class="o">.</span><span class="na">class</span><span class="o">)</span> <span class="nc">Student</span> <span class="n">student</span><span class="o">,</span> <span class="nc">BindingResult</span> <span class="n">result</span><span class="o">)</span> <span class="o">{</span>
<span class="k">if</span> <span class="o">(</span><span class="n">result</span> <span class="o">!=</span> <span class="kc">null</span><span class="o">)</span> <span class="o">{</span>
<span class="c1">//校验未通过,获取所有的异常信息并展示出来</span>
<span class="nc">List</span><span class="o"><</span><span class="nc">ObjectError</span><span class="o">></span> <span class="n">allErrors</span> <span class="o">=</span> <span class="n">result</span><span class="o">.</span><span class="na">getAllErrors</span><span class="o">();</span>
<span class="k">for</span> <span class="o">(</span><span class="nc">ObjectError</span> <span class="n">allError</span> <span class="o">:</span> <span class="n">allErrors</span><span class="o">)</span> <span class="o">{</span>
<span class="nc">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">println</span><span class="o">(</span><span class="n">allError</span><span class="o">.</span><span class="na">getObjectName</span><span class="o">()+</span><span class="s">":"</span><span class="o">+</span><span class="n">allError</span><span class="o">.</span><span class="na">getDefaultMessage</span><span class="o">());</span>
<span class="o">}</span>
<span class="k">return</span> <span class="s">"student"</span><span class="o">;</span>
<span class="o">}</span>
<span class="k">return</span> <span class="s">"hello"</span><span class="o">;</span>
<span class="o">}</span>
</code></pre></div></div>
<p>这样定义完成后,在前端再次访问回显的变量时,变量名称就不是 student 了,而是 s:</p>
<div class="language-jsp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nt"><form</span> <span class="na">action=</span><span class="s">"/addstudent"</span> <span class="na">method=</span><span class="s">"post"</span><span class="nt">></span>
<span class="nt"><table></span>
<span class="nt"><tr></span>
<span class="nt"><td></span>学生编号:<span class="nt"></td></span>
<span class="nt"><td><input</span> <span class="na">type=</span><span class="s">"text"</span> <span class="na">name=</span><span class="s">"id"</span> <span class="na">value=</span><span class="s">"${s.id}"</span><span class="nt">></td></span>
<span class="nt"></tr></span>
<span class="nt"><tr></span>
<span class="nt"><td></span>学生姓名:<span class="nt"></td></span>
<span class="nt"><td><input</span> <span class="na">type=</span><span class="s">"text"</span> <span class="na">name=</span><span class="s">"name"</span> <span class="na">value=</span><span class="s">"${s.name}"</span><span class="nt">></td></span>
<span class="nt"></tr></span>
<span class="nt"><tr></span>
<span class="nt"><td></span>学生邮箱:<span class="nt"></td></span>
<span class="nt"><td><input</span> <span class="na">type=</span><span class="s">"text"</span> <span class="na">name=</span><span class="s">"email"</span> <span class="na">value=</span><span class="s">"${s.email}"</span><span class="nt">></td></span>
<span class="nt"></tr></span>
<span class="nt"><tr></span>
<span class="nt"><td></span>学生年龄:<span class="nt"></td></span>
<span class="nt"><td><input</span> <span class="na">type=</span><span class="s">"text"</span> <span class="na">name=</span><span class="s">"age"</span> <span class="na">value=</span><span class="s">"${s.age}"</span><span class="nt">></td></span>
<span class="nt"></tr></span>
<span class="nt"><tr></span>
<span class="nt"><td</span> <span class="na">colspan=</span><span class="s">"2"</span><span class="nt">></span>
<span class="nt"><input</span> <span class="na">type=</span><span class="s">"submit"</span> <span class="na">value=</span><span class="s">"提交"</span><span class="nt">></span>
<span class="nt"></td></span>
<span class="nt"></tr></span>
<span class="nt"></table></span>
<span class="nt"></form></span>
</code></pre></div></div>
<h3 id="1122-定义全局数据">11.2.2 定义全局数据</h3>
<p>假设有一个 Controller 中有很多方法,每个方法都会返回数据给前端,但是每个方法返回给前端的数据又不太一样,虽然不太一样,但是没有方法的返回值又有一些公共的部分。可以将这些公共的部分提取出来单独封装成一个方法,用 @ModelAttribute 注解来标记。</p>
<p>例如在一个 Controller 中 ,添加如下代码:</p>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nd">@ModelAttribute</span><span class="o">(</span><span class="s">"info"</span><span class="o">)</span>
<span class="kd">public</span> <span class="nc">Map</span><span class="o"><</span><span class="nc">String</span><span class="o">,</span><span class="nc">Object</span><span class="o">></span> <span class="nf">info</span><span class="o">()</span> <span class="o">{</span>
<span class="nc">Map</span><span class="o"><</span><span class="nc">String</span><span class="o">,</span> <span class="nc">Object</span><span class="o">></span> <span class="n">map</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">HashMap</span><span class="o"><>();</span>
<span class="n">map</span><span class="o">.</span><span class="na">put</span><span class="o">(</span><span class="s">"username"</span><span class="o">,</span> <span class="s">"javaboy"</span><span class="o">);</span>
<span class="n">map</span><span class="o">.</span><span class="na">put</span><span class="o">(</span><span class="s">"address"</span><span class="o">,</span> <span class="s">"www.javaboy.org"</span><span class="o">);</span>
<span class="k">return</span> <span class="n">map</span><span class="o">;</span>
<span class="o">}</span>
</code></pre></div></div>
<p>当用户访问当前 Controller 中的任意一个方法,在返回数据时,都会将添加了 @ModelAttribute 注解的方法的返回值,一起返回给前端。@ModelAttribute 注解中的 info 表示返回数据的 key。</p>江南一点雨wangsong0210@gmail.com11.2 @ModelAttribute @ModelAttribute 这个注解,主要有两方面的功能: 在数据回显时,给变量定义别名 定义全局数据 11.2.1 定义别名 在数据回显时,给变量定义别名,非常容易,直接加这个注解即可: @RequestMapping("/addstudent") public String addStudent(@ModelAttribute("s") @Validated(ValidationGroup2.class) Student student, BindingResult result) { if (result != null) { //校验未通过,获取所有的异常信息并展示出来 List<ObjectError> allErrors = result.getAllErrors(); for (ObjectError allError : allErrors) { System.out.println(allError.getObjectName()+":"+allError.getDefaultMessage()); } return "student"; } return "hello"; } 这样定义完成后,在前端再次访问回显的变量时,变量名称就不是 student 了,而是 s: <form action="/addstudent" method="post"> <table> <tr> <td>学生编号:</td> <td><input type="text" name="id" value="${s.id}"></td> </tr> <tr> <td>学生姓名:</td> <td><input type="text" name="name" value="${s.name}"></td> </tr> <tr> <td>学生邮箱:</td> <td><input type="text" name="email" value="${s.email}"></td> </tr> <tr> <td>学生年龄:</td> <td><input type="text" name="age" value="${s.age}"></td> </tr> <tr> <td colspan="2"> <input type="submit" value="提交"> </td> </tr> </table> </form> 11.2.2 定义全局数据 假设有一个 Controller 中有很多方法,每个方法都会返回数据给前端,但是每个方法返回给前端的数据又不太一样,虽然不太一样,但是没有方法的返回值又有一些公共的部分。可以将这些公共的部分提取出来单独封装成一个方法,用 @ModelAttribute 注解来标记。 例如在一个 Controller 中 ,添加如下代码: @ModelAttribute("info") public Map<String,Object> info() { Map<String, Object> map = new HashMap<>(); map.put("username", "javaboy"); map.put("address", "www.javaboy.org"); return map; } 当用户访问当前 Controller 中的任意一个方法,在返回数据时,都会将添加了 @ModelAttribute 注解的方法的返回值,一起返回给前端。@ModelAttribute 注解中的 info 表示返回数据的 key。11.1 数据回显基本用法2019-11-13T22:28:52+00:002019-11-13T22:28:52+00:00http://springboot.javaboy.org/2019/1113/data-return<h2 id="111-数据回显基本用法">11.1 数据回显基本用法</h2>
<p>数据回显就是当用户数据提交失败时,自动填充好已经输入的数据。一般来说,如果使用 Ajax 来做数据提交,基本上是没有数据回显这个需求的,但是如果是通过表单做数据提交,那么数据回显就非常有必要了。</p>
<h3 id="1111-简单数据类型">11.1.1 简单数据类型</h3>
<p>简单数据类型,实际上框架在这里没有提供任何形式的支持,就是我们自己手动配置。我们继续在第 10 小节的例子上演示 Demo。加入提交的 Student 数据不符合要求,那么重新回到添加 Student 页面,并且预设之前已经填好的数据。</p>
<p>首先我们先来改造一下 student.jsp 页面:</p>
<div class="language-jsp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nt"><form</span> <span class="na">action=</span><span class="s">"/addstudent"</span> <span class="na">method=</span><span class="s">"post"</span><span class="nt">></span>
<span class="nt"><table></span>
<span class="nt"><tr></span>
<span class="nt"><td></span>学生编号:<span class="nt"></td></span>
<span class="nt"><td><input</span> <span class="na">type=</span><span class="s">"text"</span> <span class="na">name=</span><span class="s">"id"</span> <span class="na">value=</span><span class="s">"${id}"</span><span class="nt">></td></span>
<span class="nt"></tr></span>
<span class="nt"><tr></span>
<span class="nt"><td></span>学生姓名:<span class="nt"></td></span>
<span class="nt"><td><input</span> <span class="na">type=</span><span class="s">"text"</span> <span class="na">name=</span><span class="s">"name"</span> <span class="na">value=</span><span class="s">"${name}"</span><span class="nt">></td></span>
<span class="nt"></tr></span>
<span class="nt"><tr></span>
<span class="nt"><td></span>学生邮箱:<span class="nt"></td></span>
<span class="nt"><td><input</span> <span class="na">type=</span><span class="s">"text"</span> <span class="na">name=</span><span class="s">"email"</span> <span class="na">value=</span><span class="s">"${email}"</span><span class="nt">></td></span>
<span class="nt"></tr></span>
<span class="nt"><tr></span>
<span class="nt"><td></span>学生年龄:<span class="nt"></td></span>
<span class="nt"><td><input</span> <span class="na">type=</span><span class="s">"text"</span> <span class="na">name=</span><span class="s">"age"</span> <span class="na">value=</span><span class="s">"${age}"</span><span class="nt">></td></span>
<span class="nt"></tr></span>
<span class="nt"><tr></span>
<span class="nt"><td</span> <span class="na">colspan=</span><span class="s">"2"</span><span class="nt">></span>
<span class="nt"><input</span> <span class="na">type=</span><span class="s">"submit"</span> <span class="na">value=</span><span class="s">"提交"</span><span class="nt">></span>
<span class="nt"></td></span>
<span class="nt"></tr></span>
<span class="nt"></table></span>
<span class="nt"></form></span>
</code></pre></div></div>
<p>在接收数据时,使用简单数据类型去接收:</p>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nd">@RequestMapping</span><span class="o">(</span><span class="s">"/addstudent"</span><span class="o">)</span>
<span class="kd">public</span> <span class="nc">String</span> <span class="nf">addStudent2</span><span class="o">(</span><span class="nc">Integer</span> <span class="n">id</span><span class="o">,</span> <span class="nc">String</span> <span class="n">name</span><span class="o">,</span> <span class="nc">String</span> <span class="n">email</span><span class="o">,</span> <span class="nc">Integer</span> <span class="n">age</span><span class="o">,</span> <span class="nc">Model</span> <span class="n">model</span><span class="o">)</span> <span class="o">{</span>
<span class="n">model</span><span class="o">.</span><span class="na">addAttribute</span><span class="o">(</span><span class="s">"id"</span><span class="o">,</span> <span class="n">id</span><span class="o">);</span>
<span class="n">model</span><span class="o">.</span><span class="na">addAttribute</span><span class="o">(</span><span class="s">"name"</span><span class="o">,</span> <span class="n">name</span><span class="o">);</span>
<span class="n">model</span><span class="o">.</span><span class="na">addAttribute</span><span class="o">(</span><span class="s">"email"</span><span class="o">,</span> <span class="n">email</span><span class="o">);</span>
<span class="n">model</span><span class="o">.</span><span class="na">addAttribute</span><span class="o">(</span><span class="s">"age"</span><span class="o">,</span> <span class="n">age</span><span class="o">);</span>
<span class="k">return</span> <span class="s">"student"</span><span class="o">;</span>
<span class="o">}</span>
</code></pre></div></div>
<p>这种方式,相当于框架没有做任何工作,就是我们手动做数据回显的。此时访问页面,服务端会再次定位到该页面,而且数据已经预填好。</p>
<h3 id="1112-实体类">11.1.2 实体类</h3>
<p>上面这种简单数据类型的回显,实际上非常麻烦,因为需要开发者在服务端一个一个手动设置。如果使用对象的话,就没有这么麻烦了,因为 SpringMVC 在页面跳转时,会自动将对象填充进返回的数据中。</p>
<p>此时,首先修改一下 student.jsp 页面:</p>
<div class="language-jsp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nt"><form</span> <span class="na">action=</span><span class="s">"/addstudent"</span> <span class="na">method=</span><span class="s">"post"</span><span class="nt">></span>
<span class="nt"><table></span>
<span class="nt"><tr></span>
<span class="nt"><td></span>学生编号:<span class="nt"></td></span>
<span class="nt"><td><input</span> <span class="na">type=</span><span class="s">"text"</span> <span class="na">name=</span><span class="s">"id"</span> <span class="na">value=</span><span class="s">"${student.id}"</span><span class="nt">></td></span>
<span class="nt"></tr></span>
<span class="nt"><tr></span>
<span class="nt"><td></span>学生姓名:<span class="nt"></td></span>
<span class="nt"><td><input</span> <span class="na">type=</span><span class="s">"text"</span> <span class="na">name=</span><span class="s">"name"</span> <span class="na">value=</span><span class="s">"${student.name}"</span><span class="nt">></td></span>
<span class="nt"></tr></span>
<span class="nt"><tr></span>
<span class="nt"><td></span>学生邮箱:<span class="nt"></td></span>
<span class="nt"><td><input</span> <span class="na">type=</span><span class="s">"text"</span> <span class="na">name=</span><span class="s">"email"</span> <span class="na">value=</span><span class="s">"${student.email}"</span><span class="nt">></td></span>
<span class="nt"></tr></span>
<span class="nt"><tr></span>
<span class="nt"><td></span>学生年龄:<span class="nt"></td></span>
<span class="nt"><td><input</span> <span class="na">type=</span><span class="s">"text"</span> <span class="na">name=</span><span class="s">"age"</span> <span class="na">value=</span><span class="s">"${student.age}"</span><span class="nt">></td></span>
<span class="nt"></tr></span>
<span class="nt"><tr></span>
<span class="nt"><td</span> <span class="na">colspan=</span><span class="s">"2"</span><span class="nt">></span>
<span class="nt"><input</span> <span class="na">type=</span><span class="s">"submit"</span> <span class="na">value=</span><span class="s">"提交"</span><span class="nt">></span>
<span class="nt"></td></span>
<span class="nt"></tr></span>
<span class="nt"></table></span>
<span class="nt"></form></span>
</code></pre></div></div>
<p>注意,在预填数据中,多了一个 student. 前缀。这 student 就是服务端接收数据的变量名,服务端的变量名和这里的 student 要保持一直。服务端定义如下:</p>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nd">@RequestMapping</span><span class="o">(</span><span class="s">"/addstudent"</span><span class="o">)</span>
<span class="kd">public</span> <span class="nc">String</span> <span class="nf">addStudent</span><span class="o">(</span><span class="nd">@Validated</span><span class="o">(</span><span class="nc">ValidationGroup2</span><span class="o">.</span><span class="na">class</span><span class="o">)</span> <span class="nc">Student</span> <span class="n">student</span><span class="o">,</span> <span class="nc">BindingResult</span> <span class="n">result</span><span class="o">)</span> <span class="o">{</span>
<span class="k">if</span> <span class="o">(</span><span class="n">result</span> <span class="o">!=</span> <span class="kc">null</span><span class="o">)</span> <span class="o">{</span>
<span class="c1">//校验未通过,获取所有的异常信息并展示出来</span>
<span class="nc">List</span><span class="o"><</span><span class="nc">ObjectError</span><span class="o">></span> <span class="n">allErrors</span> <span class="o">=</span> <span class="n">result</span><span class="o">.</span><span class="na">getAllErrors</span><span class="o">();</span>
<span class="k">for</span> <span class="o">(</span><span class="nc">ObjectError</span> <span class="n">allError</span> <span class="o">:</span> <span class="n">allErrors</span><span class="o">)</span> <span class="o">{</span>
<span class="nc">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">println</span><span class="o">(</span><span class="n">allError</span><span class="o">.</span><span class="na">getObjectName</span><span class="o">()+</span><span class="s">":"</span><span class="o">+</span><span class="n">allError</span><span class="o">.</span><span class="na">getDefaultMessage</span><span class="o">());</span>
<span class="o">}</span>
<span class="k">return</span> <span class="s">"student"</span><span class="o">;</span>
<span class="o">}</span>
<span class="k">return</span> <span class="s">"hello"</span><span class="o">;</span>
<span class="o">}</span>
</code></pre></div></div>
<p>注意,服务端什么都不用做,就说要返回的页面就行了,student 这个变量会被自动填充到返回的 Model 中。变量名就是填充时候的 key。如果想自定义这个 key,可以在参数中写出来 Model,然后手动加入 Student 对象,就像简单数据类型回显那样。</p>
<p>另一种定义回显变量别名的方式,就是使用 @ModelAttribute 注解。</p>江南一点雨wangsong0210@gmail.com11.1 数据回显基本用法 数据回显就是当用户数据提交失败时,自动填充好已经输入的数据。一般来说,如果使用 Ajax 来做数据提交,基本上是没有数据回显这个需求的,但是如果是通过表单做数据提交,那么数据回显就非常有必要了。 11.1.1 简单数据类型 简单数据类型,实际上框架在这里没有提供任何形式的支持,就是我们自己手动配置。我们继续在第 10 小节的例子上演示 Demo。加入提交的 Student 数据不符合要求,那么重新回到添加 Student 页面,并且预设之前已经填好的数据。 首先我们先来改造一下 student.jsp 页面: <form action="/addstudent" method="post"> <table> <tr> <td>学生编号:</td> <td><input type="text" name="id" value="${id}"></td> </tr> <tr> <td>学生姓名:</td> <td><input type="text" name="name" value="${name}"></td> </tr> <tr> <td>学生邮箱:</td> <td><input type="text" name="email" value="${email}"></td> </tr> <tr> <td>学生年龄:</td> <td><input type="text" name="age" value="${age}"></td> </tr> <tr> <td colspan="2"> <input type="submit" value="提交"> </td> </tr> </table> </form> 在接收数据时,使用简单数据类型去接收: @RequestMapping("/addstudent") public String addStudent2(Integer id, String name, String email, Integer age, Model model) { model.addAttribute("id", id); model.addAttribute("name", name); model.addAttribute("email", email); model.addAttribute("age", age); return "student"; } 这种方式,相当于框架没有做任何工作,就是我们手动做数据回显的。此时访问页面,服务端会再次定位到该页面,而且数据已经预填好。 11.1.2 实体类 上面这种简单数据类型的回显,实际上非常麻烦,因为需要开发者在服务端一个一个手动设置。如果使用对象的话,就没有这么麻烦了,因为 SpringMVC 在页面跳转时,会自动将对象填充进返回的数据中。 此时,首先修改一下 student.jsp 页面: <form action="/addstudent" method="post"> <table> <tr> <td>学生编号:</td> <td><input type="text" name="id" value="${student.id}"></td> </tr> <tr> <td>学生姓名:</td> <td><input type="text" name="name" value="${student.name}"></td> </tr> <tr> <td>学生邮箱:</td> <td><input type="text" name="email" value="${student.email}"></td> </tr> <tr> <td>学生年龄:</td> <td><input type="text" name="age" value="${student.age}"></td> </tr> <tr> <td colspan="2"> <input type="submit" value="提交"> </td> </tr> </table> </form> 注意,在预填数据中,多了一个 student. 前缀。这 student 就是服务端接收数据的变量名,服务端的变量名和这里的 student 要保持一直。服务端定义如下: @RequestMapping("/addstudent") public String addStudent(@Validated(ValidationGroup2.class) Student student, BindingResult result) { if (result != null) { //校验未通过,获取所有的异常信息并展示出来 List<ObjectError> allErrors = result.getAllErrors(); for (ObjectError allError : allErrors) { System.out.println(allError.getObjectName()+":"+allError.getDefaultMessage()); } return "student"; } return "hello"; } 注意,服务端什么都不用做,就说要返回的页面就行了,student 这个变量会被自动填充到返回的 Model 中。变量名就是填充时候的 key。如果想自定义这个 key,可以在参数中写出来 Model,然后手动加入 Student 对象,就像简单数据类型回显那样。 另一种定义回显变量别名的方式,就是使用 @ModelAttribute 注解。10. 服务端数据校验2019-11-12T22:28:52+00:002019-11-12T22:28:52+00:00http://springboot.javaboy.org/2019/1112/validation<h2 id="10-服务端数据校验">10. 服务端数据校验</h2>
<p>B/S 系统中对 http 请求数据的校验多数在客户端进行,这也是出于简单及用户体验性上考虑,但是在一些安全性要求高的系统中服务端校验是不可缺少的,实际上,几乎所有的系统,凡是涉及到数据校验,都需要在服务端进行二次校验。为什么要在服务端进行二次校验呢?这需要理解客户端校验和服务端校验各自的目的。</p>
<ol>
<li>客户端校验,我们主要是为了提高用户体验,例如用户输入一个邮箱地址,要校验这个邮箱地址是否合法,没有必要发送到服务端进行校验,直接在前端用 js 进行校验即可。但是大家需要明白的是,前端校验无法代替后端校验,前端校验可以有效的提高用户体验,但是无法确保数据完整性,因为在 B/S 架构中,用户可以方便的拿到请求地址,然后直接发送请求,传递非法参数。</li>
<li>服务端校验,虽然用户体验不好,但是可以有效的保证数据安全与完整性。</li>
<li>综上,实际项目中,两个一起用。</li>
</ol>
<p>Spring 支持 JSR-303 验证框架,JSR-303 是 JAVA EE 6 中的一项子规范,叫做 Bean Validation,官方参考实现是 Hibernate Validator(与Hibernate ORM 没有关系),JSR-303 用于对 Java Bean 中的字段的值进行验证。</p>
<h3 id="101-普通校验">10.1 普通校验</h3>
<p>普通校验,是这里最基本的用法。</p>
<p>首先,我们需要加入校验需要的依赖:</p>
<div class="language-xml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nt"><dependency></span>
<span class="nt"><groupId></span>org.hibernate<span class="nt"></groupId></span>
<span class="nt"><artifactId></span>hibernate-validator<span class="nt"></artifactId></span>
<span class="nt"><version></span>6.1.0.Final<span class="nt"></version></span>
<span class="nt"></dependency></span>
</code></pre></div></div>
<p>接下来,在 SpringMVC 的配置文件中配置校验的 Bean:</p>
<div class="language-xml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nt"><bean</span> <span class="na">class=</span><span class="s">"org.springframework.validation.beanvalidation.LocalValidatorFactoryBean"</span> <span class="na">id=</span><span class="s">"validatorFactoryBean"</span><span class="nt">></span>
<span class="nt"><property</span> <span class="na">name=</span><span class="s">"providerClass"</span> <span class="na">value=</span><span class="s">"org.hibernate.validator.HibernateValidator"</span><span class="nt">/></span>
<span class="nt"></bean></span>
<span class="nt"><mvc:annotation-driven</span> <span class="na">validator=</span><span class="s">"validatorFactoryBean"</span><span class="nt">/></span>
</code></pre></div></div>
<p>配置时,提供一个 LocalValidatorFactoryBean 的实例,然后 Bean 的校验使用 HibernateValidator。</p>
<p>这样,配置就算完成了。</p>
<p>接下来,我们提供一个添加学生的页面:</p>
<div class="language-html highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nt"><form</span> <span class="na">action=</span><span class="s">"/addstudent"</span> <span class="na">method=</span><span class="s">"post"</span><span class="nt">></span>
<span class="nt"><table></span>
<span class="nt"><tr></span>
<span class="nt"><td></span>学生编号:<span class="nt"></td></span>
<span class="nt"><td><input</span> <span class="na">type=</span><span class="s">"text"</span> <span class="na">name=</span><span class="s">"id"</span><span class="nt">></td></span>
<span class="nt"></tr></span>
<span class="nt"><tr></span>
<span class="nt"><td></span>学生姓名:<span class="nt"></td></span>
<span class="nt"><td><input</span> <span class="na">type=</span><span class="s">"text"</span> <span class="na">name=</span><span class="s">"name"</span><span class="nt">></td></span>
<span class="nt"></tr></span>
<span class="nt"><tr></span>
<span class="nt"><td></span>学生邮箱:<span class="nt"></td></span>
<span class="nt"><td><input</span> <span class="na">type=</span><span class="s">"text"</span> <span class="na">name=</span><span class="s">"email"</span><span class="nt">></td></span>
<span class="nt"></tr></span>
<span class="nt"><tr></span>
<span class="nt"><td></span>学生年龄:<span class="nt"></td></span>
<span class="nt"><td><input</span> <span class="na">type=</span><span class="s">"text"</span> <span class="na">name=</span><span class="s">"age"</span><span class="nt">></td></span>
<span class="nt"></tr></span>
<span class="nt"><tr></span>
<span class="nt"><td</span> <span class="na">colspan=</span><span class="s">"2"</span><span class="nt">></span>
<span class="nt"><input</span> <span class="na">type=</span><span class="s">"submit"</span> <span class="na">value=</span><span class="s">"提交"</span><span class="nt">></span>
<span class="nt"></td></span>
<span class="nt"></tr></span>
<span class="nt"></table></span>
<span class="nt"></form></span>
</code></pre></div></div>
<p>在这里需要提交的数据中,假设学生编号不能为空,学生姓名长度不能超过 10 且不能为空,邮箱地址要合法,年龄不能超过 150。那么在定义实体类的时候,就可以加入这个判断条件了。</p>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">public</span> <span class="kd">class</span> <span class="nc">Student</span> <span class="o">{</span>
<span class="nd">@NotNull</span>
<span class="kd">private</span> <span class="nc">Integer</span> <span class="n">id</span><span class="o">;</span>
<span class="nd">@NotNull</span>
<span class="nd">@Size</span><span class="o">(</span><span class="n">min</span> <span class="o">=</span> <span class="mi">2</span><span class="o">,</span><span class="n">max</span> <span class="o">=</span> <span class="mi">10</span><span class="o">)</span>
<span class="kd">private</span> <span class="nc">String</span> <span class="n">name</span><span class="o">;</span>
<span class="nd">@Email</span>
<span class="kd">private</span> <span class="nc">String</span> <span class="n">email</span><span class="o">;</span>
<span class="nd">@Max</span><span class="o">(</span><span class="mi">150</span><span class="o">)</span>
<span class="kd">private</span> <span class="nc">Integer</span> <span class="n">age</span><span class="o">;</span>
<span class="kd">public</span> <span class="nc">String</span> <span class="nf">getEmail</span><span class="o">()</span> <span class="o">{</span>
<span class="k">return</span> <span class="n">email</span><span class="o">;</span>
<span class="o">}</span>
<span class="nd">@Override</span>
<span class="kd">public</span> <span class="nc">String</span> <span class="nf">toString</span><span class="o">()</span> <span class="o">{</span>
<span class="k">return</span> <span class="s">"Student{"</span> <span class="o">+</span>
<span class="s">"id="</span> <span class="o">+</span> <span class="n">id</span> <span class="o">+</span>
<span class="s">", name='"</span> <span class="o">+</span> <span class="n">name</span> <span class="o">+</span> <span class="sc">'\''</span> <span class="o">+</span>
<span class="s">", email='"</span> <span class="o">+</span> <span class="n">email</span> <span class="o">+</span> <span class="sc">'\''</span> <span class="o">+</span>
<span class="s">", age="</span> <span class="o">+</span> <span class="n">age</span> <span class="o">+</span>
<span class="sc">'}'</span><span class="o">;</span>
<span class="o">}</span>
<span class="kd">public</span> <span class="kt">void</span> <span class="nf">setEmail</span><span class="o">(</span><span class="nc">String</span> <span class="n">email</span><span class="o">)</span> <span class="o">{</span>
<span class="k">this</span><span class="o">.</span><span class="na">email</span> <span class="o">=</span> <span class="n">email</span><span class="o">;</span>
<span class="o">}</span>
<span class="kd">public</span> <span class="nc">Integer</span> <span class="nf">getAge</span><span class="o">()</span> <span class="o">{</span>
<span class="k">return</span> <span class="n">age</span><span class="o">;</span>
<span class="o">}</span>
<span class="kd">public</span> <span class="kt">void</span> <span class="nf">setAge</span><span class="o">(</span><span class="nc">Integer</span> <span class="n">age</span><span class="o">)</span> <span class="o">{</span>
<span class="k">this</span><span class="o">.</span><span class="na">age</span> <span class="o">=</span> <span class="n">age</span><span class="o">;</span>
<span class="o">}</span>
<span class="kd">public</span> <span class="nc">Integer</span> <span class="nf">getId</span><span class="o">()</span> <span class="o">{</span>
<span class="k">return</span> <span class="n">id</span><span class="o">;</span>
<span class="o">}</span>
<span class="kd">public</span> <span class="kt">void</span> <span class="nf">setId</span><span class="o">(</span><span class="nc">Integer</span> <span class="n">id</span><span class="o">)</span> <span class="o">{</span>
<span class="k">this</span><span class="o">.</span><span class="na">id</span> <span class="o">=</span> <span class="n">id</span><span class="o">;</span>
<span class="o">}</span>
<span class="kd">public</span> <span class="nc">String</span> <span class="nf">getName</span><span class="o">()</span> <span class="o">{</span>
<span class="k">return</span> <span class="n">name</span><span class="o">;</span>
<span class="o">}</span>
<span class="kd">public</span> <span class="kt">void</span> <span class="nf">setName</span><span class="o">(</span><span class="nc">String</span> <span class="n">name</span><span class="o">)</span> <span class="o">{</span>
<span class="k">this</span><span class="o">.</span><span class="na">name</span> <span class="o">=</span> <span class="n">name</span><span class="o">;</span>
<span class="o">}</span>
<span class="o">}</span>
</code></pre></div></div>
<p>在这里:</p>
<ul>
<li>@NotNull 表示这个字段不能为空</li>
<li>@Size 中描述了这个字符串长度的限制</li>
<li>@Email 表示这个字段的值必须是一个邮箱地址</li>
<li>@Max 表示这个字段的最大值</li>
</ul>
<p>定义完成后,接下来,在 Controller 中定义接口:</p>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nd">@Controller</span>
<span class="kd">public</span> <span class="kd">class</span> <span class="nc">StudentController</span> <span class="o">{</span>
<span class="nd">@RequestMapping</span><span class="o">(</span><span class="s">"/addstudent"</span><span class="o">)</span>
<span class="nd">@ResponseBody</span>
<span class="kd">public</span> <span class="kt">void</span> <span class="nf">addStudent</span><span class="o">(</span><span class="nd">@Validated</span> <span class="nc">Student</span> <span class="n">student</span><span class="o">,</span> <span class="nc">BindingResult</span> <span class="n">result</span><span class="o">)</span> <span class="o">{</span>
<span class="k">if</span> <span class="o">(</span><span class="n">result</span> <span class="o">!=</span> <span class="kc">null</span><span class="o">)</span> <span class="o">{</span>
<span class="c1">//校验未通过,获取所有的异常信息并展示出来</span>
<span class="nc">List</span><span class="o"><</span><span class="nc">ObjectError</span><span class="o">></span> <span class="n">allErrors</span> <span class="o">=</span> <span class="n">result</span><span class="o">.</span><span class="na">getAllErrors</span><span class="o">();</span>
<span class="k">for</span> <span class="o">(</span><span class="nc">ObjectError</span> <span class="n">allError</span> <span class="o">:</span> <span class="n">allErrors</span><span class="o">)</span> <span class="o">{</span>
<span class="nc">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">println</span><span class="o">(</span><span class="n">allError</span><span class="o">.</span><span class="na">getObjectName</span><span class="o">()+</span><span class="s">":"</span><span class="o">+</span><span class="n">allError</span><span class="o">.</span><span class="na">getDefaultMessage</span><span class="o">());</span>
<span class="o">}</span>
<span class="o">}</span>
<span class="o">}</span>
<span class="o">}</span>
</code></pre></div></div>
<p>在这里:</p>
<ul>
<li>@Validated 表示 Student 中定义的校验规则将会生效</li>
<li>BindingResult 表示出错信息,如果这个变量不为空,表示有错误,否则校验通过。</li>
</ul>
<p>接下来就可以启动项目了。访问 jsp 页面,然后添加 Student,查看校验规则是否生效。</p>
<p>默认情况下,打印出来的错误信息时系统默认的错误信息,这个错误信息,我们也可以自定义。自定义方式如下:</p>
<p>由于 properties 文件中的中文会乱码,所以需要我们先修改一下 IDEA 配置,点 File–>Settings->Editor–>File Encodings,如下:</p>
<p><img src="http://springmvc.javaboy.org/assets/images/img/10-1.png" alt="" title="10-1.png" /></p>
<p>然后定义错误提示文本,在 resources 目录下新建一个 MyMessage.properties 文件,内容如下:</p>
<div class="language-properties highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="py">student.id.notnull</span><span class="p">=</span><span class="s">id 不能为空</span>
<span class="py">student.name.notnull</span><span class="p">=</span><span class="s">name 不能为空</span>
<span class="py">student.name.length</span><span class="p">=</span><span class="s">name 最小长度为 2 ,最大长度为 10</span>
<span class="py">student.email.error</span><span class="p">=</span><span class="s">email 地址非法</span>
<span class="py">student.age.error</span><span class="p">=</span><span class="s">年龄不能超过 150</span>
</code></pre></div></div>
<p>接下来,在 SpringMVC 配置中,加载这个配置文件:</p>
<div class="language-xml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nt"><bean</span> <span class="na">class=</span><span class="s">"org.springframework.validation.beanvalidation.LocalValidatorFactoryBean"</span> <span class="na">id=</span><span class="s">"validatorFactoryBean"</span><span class="nt">></span>
<span class="nt"><property</span> <span class="na">name=</span><span class="s">"providerClass"</span> <span class="na">value=</span><span class="s">"org.hibernate.validator.HibernateValidator"</span><span class="nt">/></span>
<span class="nt"><property</span> <span class="na">name=</span><span class="s">"validationMessageSource"</span> <span class="na">ref=</span><span class="s">"bundleMessageSource"</span><span class="nt">/></span>
<span class="nt"></bean></span>
<span class="nt"><bean</span> <span class="na">class=</span><span class="s">"org.springframework.context.support.ReloadableResourceBundleMessageSource"</span> <span class="na">id=</span><span class="s">"bundleMessageSource"</span><span class="nt">></span>
<span class="nt"><property</span> <span class="na">name=</span><span class="s">"basenames"</span><span class="nt">></span>
<span class="nt"><list></span>
<span class="nt"><value></span>classpath:MyMessage<span class="nt"></value></span>
<span class="nt"></list></span>
<span class="nt"></property></span>
<span class="nt"><property</span> <span class="na">name=</span><span class="s">"defaultEncoding"</span> <span class="na">value=</span><span class="s">"UTF-8"</span><span class="nt">/></span>
<span class="nt"><property</span> <span class="na">name=</span><span class="s">"cacheSeconds"</span> <span class="na">value=</span><span class="s">"300"</span><span class="nt">/></span>
<span class="nt"></bean></span>
<span class="nt"><mvc:annotation-driven</span> <span class="na">validator=</span><span class="s">"validatorFactoryBean"</span><span class="nt">/></span>
</code></pre></div></div>
<p>最后,在实体类上的注解中,加上校验出错时的信息:</p>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">public</span> <span class="kd">class</span> <span class="nc">Student</span> <span class="o">{</span>
<span class="nd">@NotNull</span><span class="o">(</span><span class="n">message</span> <span class="o">=</span> <span class="s">"{student.id.notnull}"</span><span class="o">)</span>
<span class="kd">private</span> <span class="nc">Integer</span> <span class="n">id</span><span class="o">;</span>
<span class="nd">@NotNull</span><span class="o">(</span><span class="n">message</span> <span class="o">=</span> <span class="s">"{student.name.notnull}"</span><span class="o">)</span>
<span class="nd">@Size</span><span class="o">(</span><span class="n">min</span> <span class="o">=</span> <span class="mi">2</span><span class="o">,</span><span class="n">max</span> <span class="o">=</span> <span class="mi">10</span><span class="o">,</span><span class="n">message</span> <span class="o">=</span> <span class="s">"{student.name.length}"</span><span class="o">)</span>
<span class="kd">private</span> <span class="nc">String</span> <span class="n">name</span><span class="o">;</span>
<span class="nd">@Email</span><span class="o">(</span><span class="n">message</span> <span class="o">=</span> <span class="s">"{student.email.error}"</span><span class="o">)</span>
<span class="kd">private</span> <span class="nc">String</span> <span class="n">email</span><span class="o">;</span>
<span class="nd">@Max</span><span class="o">(</span><span class="n">value</span> <span class="o">=</span> <span class="mi">150</span><span class="o">,</span><span class="n">message</span> <span class="o">=</span> <span class="s">"{student.age.error}"</span><span class="o">)</span>
<span class="kd">private</span> <span class="nc">Integer</span> <span class="n">age</span><span class="o">;</span>
<span class="kd">public</span> <span class="nc">String</span> <span class="nf">getEmail</span><span class="o">()</span> <span class="o">{</span>
<span class="k">return</span> <span class="n">email</span><span class="o">;</span>
<span class="o">}</span>
<span class="nd">@Override</span>
<span class="kd">public</span> <span class="nc">String</span> <span class="nf">toString</span><span class="o">()</span> <span class="o">{</span>
<span class="k">return</span> <span class="s">"Student{"</span> <span class="o">+</span>
<span class="s">"id="</span> <span class="o">+</span> <span class="n">id</span> <span class="o">+</span>
<span class="s">", name='"</span> <span class="o">+</span> <span class="n">name</span> <span class="o">+</span> <span class="sc">'\''</span> <span class="o">+</span>
<span class="s">", email='"</span> <span class="o">+</span> <span class="n">email</span> <span class="o">+</span> <span class="sc">'\''</span> <span class="o">+</span>
<span class="s">", age="</span> <span class="o">+</span> <span class="n">age</span> <span class="o">+</span>
<span class="sc">'}'</span><span class="o">;</span>
<span class="o">}</span>
<span class="kd">public</span> <span class="kt">void</span> <span class="nf">setEmail</span><span class="o">(</span><span class="nc">String</span> <span class="n">email</span><span class="o">)</span> <span class="o">{</span>
<span class="k">this</span><span class="o">.</span><span class="na">email</span> <span class="o">=</span> <span class="n">email</span><span class="o">;</span>
<span class="o">}</span>
<span class="kd">public</span> <span class="nc">Integer</span> <span class="nf">getAge</span><span class="o">()</span> <span class="o">{</span>
<span class="k">return</span> <span class="n">age</span><span class="o">;</span>
<span class="o">}</span>
<span class="kd">public</span> <span class="kt">void</span> <span class="nf">setAge</span><span class="o">(</span><span class="nc">Integer</span> <span class="n">age</span><span class="o">)</span> <span class="o">{</span>
<span class="k">this</span><span class="o">.</span><span class="na">age</span> <span class="o">=</span> <span class="n">age</span><span class="o">;</span>
<span class="o">}</span>
<span class="kd">public</span> <span class="nc">Integer</span> <span class="nf">getId</span><span class="o">()</span> <span class="o">{</span>
<span class="k">return</span> <span class="n">id</span><span class="o">;</span>
<span class="o">}</span>
<span class="kd">public</span> <span class="kt">void</span> <span class="nf">setId</span><span class="o">(</span><span class="nc">Integer</span> <span class="n">id</span><span class="o">)</span> <span class="o">{</span>
<span class="k">this</span><span class="o">.</span><span class="na">id</span> <span class="o">=</span> <span class="n">id</span><span class="o">;</span>
<span class="o">}</span>
<span class="kd">public</span> <span class="nc">String</span> <span class="nf">getName</span><span class="o">()</span> <span class="o">{</span>
<span class="k">return</span> <span class="n">name</span><span class="o">;</span>
<span class="o">}</span>
<span class="kd">public</span> <span class="kt">void</span> <span class="nf">setName</span><span class="o">(</span><span class="nc">String</span> <span class="n">name</span><span class="o">)</span> <span class="o">{</span>
<span class="k">this</span><span class="o">.</span><span class="na">name</span> <span class="o">=</span> <span class="n">name</span><span class="o">;</span>
<span class="o">}</span>
<span class="o">}</span>
</code></pre></div></div>
<p>配置完成后,如果校验再出错,就会展示我们自己的出错信息了。</p>
<h3 id="102-分组校验">10.2 分组校验</h3>
<p>由于校验规则都是定义在实体类上面的,但是,在不同的数据提交环境下,校验规则可能不一样。例如,用户的 id 是自增长的,添加的时候,可以不用传递用户 id,但是修改的时候则必须传递用户 id,这种情况下,就需要使用分组校验。</p>
<p>分组校验,首先需要定义校验组,所谓的校验组,其实就是空接口:</p>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">public</span> <span class="kd">interface</span> <span class="nc">ValidationGroup1</span> <span class="o">{</span>
<span class="o">}</span>
<span class="kd">public</span> <span class="kd">interface</span> <span class="nc">ValidationGroup2</span> <span class="o">{</span>
<span class="o">}</span>
</code></pre></div></div>
<p>然后,在实体类中,指定每一个校验规则所属的组:</p>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">public</span> <span class="kd">class</span> <span class="nc">Student</span> <span class="o">{</span>
<span class="nd">@NotNull</span><span class="o">(</span><span class="n">message</span> <span class="o">=</span> <span class="s">"{student.id.notnull}"</span><span class="o">,</span><span class="n">groups</span> <span class="o">=</span> <span class="nc">ValidationGroup1</span><span class="o">.</span><span class="na">class</span><span class="o">)</span>
<span class="kd">private</span> <span class="nc">Integer</span> <span class="n">id</span><span class="o">;</span>
<span class="nd">@NotNull</span><span class="o">(</span><span class="n">message</span> <span class="o">=</span> <span class="s">"{student.name.notnull}"</span><span class="o">,</span><span class="n">groups</span> <span class="o">=</span> <span class="o">{</span><span class="nc">ValidationGroup1</span><span class="o">.</span><span class="na">class</span><span class="o">,</span> <span class="nc">ValidationGroup2</span><span class="o">.</span><span class="na">class</span><span class="o">})</span>
<span class="nd">@Size</span><span class="o">(</span><span class="n">min</span> <span class="o">=</span> <span class="mi">2</span><span class="o">,</span><span class="n">max</span> <span class="o">=</span> <span class="mi">10</span><span class="o">,</span><span class="n">message</span> <span class="o">=</span> <span class="s">"{student.name.length}"</span><span class="o">,</span><span class="n">groups</span> <span class="o">=</span> <span class="o">{</span><span class="nc">ValidationGroup1</span><span class="o">.</span><span class="na">class</span><span class="o">,</span> <span class="nc">ValidationGroup2</span><span class="o">.</span><span class="na">class</span><span class="o">})</span>
<span class="kd">private</span> <span class="nc">String</span> <span class="n">name</span><span class="o">;</span>
<span class="nd">@Email</span><span class="o">(</span><span class="n">message</span> <span class="o">=</span> <span class="s">"{student.email.error}"</span><span class="o">,</span><span class="n">groups</span> <span class="o">=</span> <span class="o">{</span><span class="nc">ValidationGroup1</span><span class="o">.</span><span class="na">class</span><span class="o">,</span> <span class="nc">ValidationGroup2</span><span class="o">.</span><span class="na">class</span><span class="o">})</span>
<span class="kd">private</span> <span class="nc">String</span> <span class="n">email</span><span class="o">;</span>
<span class="nd">@Max</span><span class="o">(</span><span class="n">value</span> <span class="o">=</span> <span class="mi">150</span><span class="o">,</span><span class="n">message</span> <span class="o">=</span> <span class="s">"{student.age.error}"</span><span class="o">,</span><span class="n">groups</span> <span class="o">=</span> <span class="o">{</span><span class="nc">ValidationGroup2</span><span class="o">.</span><span class="na">class</span><span class="o">})</span>
<span class="kd">private</span> <span class="nc">Integer</span> <span class="n">age</span><span class="o">;</span>
<span class="kd">public</span> <span class="nc">String</span> <span class="nf">getEmail</span><span class="o">()</span> <span class="o">{</span>
<span class="k">return</span> <span class="n">email</span><span class="o">;</span>
<span class="o">}</span>
<span class="nd">@Override</span>
<span class="kd">public</span> <span class="nc">String</span> <span class="nf">toString</span><span class="o">()</span> <span class="o">{</span>
<span class="k">return</span> <span class="s">"Student{"</span> <span class="o">+</span>
<span class="s">"id="</span> <span class="o">+</span> <span class="n">id</span> <span class="o">+</span>
<span class="s">", name='"</span> <span class="o">+</span> <span class="n">name</span> <span class="o">+</span> <span class="sc">'\''</span> <span class="o">+</span>
<span class="s">", email='"</span> <span class="o">+</span> <span class="n">email</span> <span class="o">+</span> <span class="sc">'\''</span> <span class="o">+</span>
<span class="s">", age="</span> <span class="o">+</span> <span class="n">age</span> <span class="o">+</span>
<span class="sc">'}'</span><span class="o">;</span>
<span class="o">}</span>
<span class="kd">public</span> <span class="kt">void</span> <span class="nf">setEmail</span><span class="o">(</span><span class="nc">String</span> <span class="n">email</span><span class="o">)</span> <span class="o">{</span>
<span class="k">this</span><span class="o">.</span><span class="na">email</span> <span class="o">=</span> <span class="n">email</span><span class="o">;</span>
<span class="o">}</span>
<span class="kd">public</span> <span class="nc">Integer</span> <span class="nf">getAge</span><span class="o">()</span> <span class="o">{</span>
<span class="k">return</span> <span class="n">age</span><span class="o">;</span>
<span class="o">}</span>
<span class="kd">public</span> <span class="kt">void</span> <span class="nf">setAge</span><span class="o">(</span><span class="nc">Integer</span> <span class="n">age</span><span class="o">)</span> <span class="o">{</span>
<span class="k">this</span><span class="o">.</span><span class="na">age</span> <span class="o">=</span> <span class="n">age</span><span class="o">;</span>
<span class="o">}</span>
<span class="kd">public</span> <span class="nc">Integer</span> <span class="nf">getId</span><span class="o">()</span> <span class="o">{</span>
<span class="k">return</span> <span class="n">id</span><span class="o">;</span>
<span class="o">}</span>
<span class="kd">public</span> <span class="kt">void</span> <span class="nf">setId</span><span class="o">(</span><span class="nc">Integer</span> <span class="n">id</span><span class="o">)</span> <span class="o">{</span>
<span class="k">this</span><span class="o">.</span><span class="na">id</span> <span class="o">=</span> <span class="n">id</span><span class="o">;</span>
<span class="o">}</span>
<span class="kd">public</span> <span class="nc">String</span> <span class="nf">getName</span><span class="o">()</span> <span class="o">{</span>
<span class="k">return</span> <span class="n">name</span><span class="o">;</span>
<span class="o">}</span>
<span class="kd">public</span> <span class="kt">void</span> <span class="nf">setName</span><span class="o">(</span><span class="nc">String</span> <span class="n">name</span><span class="o">)</span> <span class="o">{</span>
<span class="k">this</span><span class="o">.</span><span class="na">name</span> <span class="o">=</span> <span class="n">name</span><span class="o">;</span>
<span class="o">}</span>
<span class="o">}</span>
</code></pre></div></div>
<p>在 group 中指定每一个校验规则所属的组,一个规则可以属于一个组,也可以属于多个组。</p>
<p>最后,在接收参数的地方,指定校验组:</p>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nd">@Controller</span>
<span class="kd">public</span> <span class="kd">class</span> <span class="nc">StudentController</span> <span class="o">{</span>
<span class="nd">@RequestMapping</span><span class="o">(</span><span class="s">"/addstudent"</span><span class="o">)</span>
<span class="nd">@ResponseBody</span>
<span class="kd">public</span> <span class="kt">void</span> <span class="nf">addStudent</span><span class="o">(</span><span class="nd">@Validated</span><span class="o">(</span><span class="nc">ValidationGroup2</span><span class="o">.</span><span class="na">class</span><span class="o">)</span> <span class="nc">Student</span> <span class="n">student</span><span class="o">,</span> <span class="nc">BindingResult</span> <span class="n">result</span><span class="o">)</span> <span class="o">{</span>
<span class="k">if</span> <span class="o">(</span><span class="n">result</span> <span class="o">!=</span> <span class="kc">null</span><span class="o">)</span> <span class="o">{</span>
<span class="c1">//校验未通过,获取所有的异常信息并展示出来</span>
<span class="nc">List</span><span class="o"><</span><span class="nc">ObjectError</span><span class="o">></span> <span class="n">allErrors</span> <span class="o">=</span> <span class="n">result</span><span class="o">.</span><span class="na">getAllErrors</span><span class="o">();</span>
<span class="k">for</span> <span class="o">(</span><span class="nc">ObjectError</span> <span class="n">allError</span> <span class="o">:</span> <span class="n">allErrors</span><span class="o">)</span> <span class="o">{</span>
<span class="nc">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">println</span><span class="o">(</span><span class="n">allError</span><span class="o">.</span><span class="na">getObjectName</span><span class="o">()+</span><span class="s">":"</span><span class="o">+</span><span class="n">allError</span><span class="o">.</span><span class="na">getDefaultMessage</span><span class="o">());</span>
<span class="o">}</span>
<span class="o">}</span>
<span class="o">}</span>
<span class="o">}</span>
</code></pre></div></div>
<p>配置完成后,属于 ValidationGroup2 这个组的校验规则,才会生效。</p>
<h3 id="103-校验注解">10.3 校验注解</h3>
<p>校验注解,主要有如下几种:</p>
<ul>
<li>@Null 被注解的元素必须为 null</li>
<li>@NotNull 被注解的元素必须不为 null</li>
<li>@AssertTrue 被注解的元素必须为 true</li>
<li>@AssertFalse 被注解的元素必须为 false</li>
<li>@Min(value) 被注解的元素必须是一个数字,其值必须大于等于指定的最小值</li>
<li>@Max(value) 被注解的元素必须是一个数字,其值必须小于等于指定的最大值</li>
<li>@DecimalMin(value) 被注解的元素必须是一个数字,其值必须大于等于指定的最小值</li>
<li>@DecimalMax(value) 被注解的元素必须是一个数字,其值必须小于等于指定的最大值</li>
<li>@Size(max=, min=) 被注解的元素的大小必须在指定的范围内</li>
<li>@Digits (integer, fraction) 被注解的元素必须是一个数字,其值必须在可接受的范围内</li>
<li>@Past 被注解的元素必须是一个过去的日期</li>
<li>@Future 被注解的元素必须是一个将来的日期</li>
<li>@Pattern(regex=,flag=) 被注解的元素必须符合指定的正则表达式</li>
<li>@NotBlank(message =) 验证字符串非 null,且长度必须大于0</li>
<li>@Email 被注解的元素必须是电子邮箱地址</li>
<li>@Length(min=,max=) 被注解的字符串的大小必须在指定的范围内</li>
<li>@NotEmpty 被注解的字符串的必须非空</li>
<li>@Range(min=,max=,message=) 被注解的元素必须在合适的范围内</li>
</ul>江南一点雨wangsong0210@gmail.com10. 服务端数据校验 B/S 系统中对 http 请求数据的校验多数在客户端进行,这也是出于简单及用户体验性上考虑,但是在一些安全性要求高的系统中服务端校验是不可缺少的,实际上,几乎所有的系统,凡是涉及到数据校验,都需要在服务端进行二次校验。为什么要在服务端进行二次校验呢?这需要理解客户端校验和服务端校验各自的目的。 客户端校验,我们主要是为了提高用户体验,例如用户输入一个邮箱地址,要校验这个邮箱地址是否合法,没有必要发送到服务端进行校验,直接在前端用 js 进行校验即可。但是大家需要明白的是,前端校验无法代替后端校验,前端校验可以有效的提高用户体验,但是无法确保数据完整性,因为在 B/S 架构中,用户可以方便的拿到请求地址,然后直接发送请求,传递非法参数。 服务端校验,虽然用户体验不好,但是可以有效的保证数据安全与完整性。 综上,实际项目中,两个一起用。 Spring 支持 JSR-303 验证框架,JSR-303 是 JAVA EE 6 中的一项子规范,叫做 Bean Validation,官方参考实现是 Hibernate Validator(与Hibernate ORM 没有关系),JSR-303 用于对 Java Bean 中的字段的值进行验证。 10.1 普通校验 普通校验,是这里最基本的用法。 首先,我们需要加入校验需要的依赖: <dependency> <groupId>org.hibernate</groupId> <artifactId>hibernate-validator</artifactId> <version>6.1.0.Final</version> </dependency> 接下来,在 SpringMVC 的配置文件中配置校验的 Bean: <bean class="org.springframework.validation.beanvalidation.LocalValidatorFactoryBean" id="validatorFactoryBean"> <property name="providerClass" value="org.hibernate.validator.HibernateValidator"/> </bean> <mvc:annotation-driven validator="validatorFactoryBean"/> 配置时,提供一个 LocalValidatorFactoryBean 的实例,然后 Bean 的校验使用 HibernateValidator。 这样,配置就算完成了。 接下来,我们提供一个添加学生的页面: <form action="/addstudent" method="post"> <table> <tr> <td>学生编号:</td> <td><input type="text" name="id"></td> </tr> <tr> <td>学生姓名:</td> <td><input type="text" name="name"></td> </tr> <tr> <td>学生邮箱:</td> <td><input type="text" name="email"></td> </tr> <tr> <td>学生年龄:</td> <td><input type="text" name="age"></td> </tr> <tr> <td colspan="2"> <input type="submit" value="提交"> </td> </tr> </table> </form> 在这里需要提交的数据中,假设学生编号不能为空,学生姓名长度不能超过 10 且不能为空,邮箱地址要合法,年龄不能超过 150。那么在定义实体类的时候,就可以加入这个判断条件了。 public class Student { @NotNull private Integer id; @NotNull @Size(min = 2,max = 10) private String name; @Email private String email; @Max(150) private Integer age; public String getEmail() { return email; } @Override public String toString() { return "Student{" + "id=" + id + ", name='" + name + '\'' + ", email='" + email + '\'' + ", age=" + age + '}'; } public void setEmail(String email) { this.email = email; } public Integer getAge() { return age; } public void setAge(Integer age) { this.age = age; } public Integer getId() { return id; } public void setId(Integer id) { this.id = id; } public String getName() { return name; } public void setName(String name) { this.name = name; } } 在这里: @NotNull 表示这个字段不能为空 @Size 中描述了这个字符串长度的限制 @Email 表示这个字段的值必须是一个邮箱地址 @Max 表示这个字段的最大值 定义完成后,接下来,在 Controller 中定义接口: @Controller public class StudentController { @RequestMapping("/addstudent") @ResponseBody public void addStudent(@Validated Student student, BindingResult result) { if (result != null) { //校验未通过,获取所有的异常信息并展示出来 List<ObjectError> allErrors = result.getAllErrors(); for (ObjectError allError : allErrors) { System.out.println(allError.getObjectName()+":"+allError.getDefaultMessage()); } } } } 在这里: @Validated 表示 Student 中定义的校验规则将会生效 BindingResult 表示出错信息,如果这个变量不为空,表示有错误,否则校验通过。 接下来就可以启动项目了。访问 jsp 页面,然后添加 Student,查看校验规则是否生效。 默认情况下,打印出来的错误信息时系统默认的错误信息,这个错误信息,我们也可以自定义。自定义方式如下: 由于 properties 文件中的中文会乱码,所以需要我们先修改一下 IDEA 配置,点 File–>Settings->Editor–>File Encodings,如下: 然后定义错误提示文本,在 resources 目录下新建一个 MyMessage.properties 文件,内容如下: student.id.notnull=id 不能为空 student.name.notnull=name 不能为空 student.name.length=name 最小长度为 2 ,最大长度为 10 student.email.error=email 地址非法 student.age.error=年龄不能超过 150 接下来,在 SpringMVC 配置中,加载这个配置文件: <bean class="org.springframework.validation.beanvalidation.LocalValidatorFactoryBean" id="validatorFactoryBean"> <property name="providerClass" value="org.hibernate.validator.HibernateValidator"/> <property name="validationMessageSource" ref="bundleMessageSource"/> </bean> <bean class="org.springframework.context.support.ReloadableResourceBundleMessageSource" id="bundleMessageSource"> <property name="basenames"> <list> <value>classpath:MyMessage</value> </list> </property> <property name="defaultEncoding" value="UTF-8"/> <property name="cacheSeconds" value="300"/> </bean> <mvc:annotation-driven validator="validatorFactoryBean"/> 最后,在实体类上的注解中,加上校验出错时的信息: public class Student { @NotNull(message = "{student.id.notnull}") private Integer id; @NotNull(message = "{student.name.notnull}") @Size(min = 2,max = 10,message = "{student.name.length}") private String name; @Email(message = "{student.email.error}") private String email; @Max(value = 150,message = "{student.age.error}") private Integer age; public String getEmail() { return email; } @Override public String toString() { return "Student{" + "id=" + id + ", name='" + name + '\'' + ", email='" + email + '\'' + ", age=" + age + '}'; } public void setEmail(String email) { this.email = email; } public Integer getAge() { return age; } public void setAge(Integer age) { this.age = age; } public Integer getId() { return id; } public void setId(Integer id) { this.id = id; } public String getName() { return name; } public void setName(String name) { this.name = name; } } 配置完成后,如果校验再出错,就会展示我们自己的出错信息了。 10.2 分组校验 由于校验规则都是定义在实体类上面的,但是,在不同的数据提交环境下,校验规则可能不一样。例如,用户的 id 是自增长的,添加的时候,可以不用传递用户 id,但是修改的时候则必须传递用户 id,这种情况下,就需要使用分组校验。 分组校验,首先需要定义校验组,所谓的校验组,其实就是空接口: public interface ValidationGroup1 { } public interface ValidationGroup2 { } 然后,在实体类中,指定每一个校验规则所属的组: public class Student { @NotNull(message = "{student.id.notnull}",groups = ValidationGroup1.class) private Integer id; @NotNull(message = "{student.name.notnull}",groups = {ValidationGroup1.class, ValidationGroup2.class}) @Size(min = 2,max = 10,message = "{student.name.length}",groups = {ValidationGroup1.class, ValidationGroup2.class}) private String name; @Email(message = "{student.email.error}",groups = {ValidationGroup1.class, ValidationGroup2.class}) private String email; @Max(value = 150,message = "{student.age.error}",groups = {ValidationGroup2.class}) private Integer age; public String getEmail() { return email; } @Override public String toString() { return "Student{" + "id=" + id + ", name='" + name + '\'' + ", email='" + email + '\'' + ", age=" + age + '}'; } public void setEmail(String email) { this.email = email; } public Integer getAge() { return age; } public void setAge(Integer age) { this.age = age; } public Integer getId() { return id; } public void setId(Integer id) { this.id = id; } public String getName() { return name; } public void setName(String name) { this.name = name; } } 在 group 中指定每一个校验规则所属的组,一个规则可以属于一个组,也可以属于多个组。 最后,在接收参数的地方,指定校验组: @Controller public class StudentController { @RequestMapping("/addstudent") @ResponseBody public void addStudent(@Validated(ValidationGroup2.class) Student student, BindingResult result) { if (result != null) { //校验未通过,获取所有的异常信息并展示出来 List<ObjectError> allErrors = result.getAllErrors(); for (ObjectError allError : allErrors) { System.out.println(allError.getObjectName()+":"+allError.getDefaultMessage()); } } } } 配置完成后,属于 ValidationGroup2 这个组的校验规则,才会生效。 10.3 校验注解 校验注解,主要有如下几种: @Null 被注解的元素必须为 null @NotNull 被注解的元素必须不为 null @AssertTrue 被注解的元素必须为 true @AssertFalse 被注解的元素必须为 false @Min(value) 被注解的元素必须是一个数字,其值必须大于等于指定的最小值 @Max(value) 被注解的元素必须是一个数字,其值必须小于等于指定的最大值 @DecimalMin(value) 被注解的元素必须是一个数字,其值必须大于等于指定的最小值 @DecimalMax(value) 被注解的元素必须是一个数字,其值必须小于等于指定的最大值 @Size(max=, min=) 被注解的元素的大小必须在指定的范围内 @Digits (integer, fraction) 被注解的元素必须是一个数字,其值必须在可接受的范围内 @Past 被注解的元素必须是一个过去的日期 @Future 被注解的元素必须是一个将来的日期 @Pattern(regex=,flag=) 被注解的元素必须符合指定的正则表达式 @NotBlank(message =) 验证字符串非 null,且长度必须大于0 @Email 被注解的元素必须是电子邮箱地址 @Length(min=,max=) 被注解的字符串的大小必须在指定的范围内 @NotEmpty 被注解的字符串的必须非空 @Range(min=,max=,message=) 被注解的元素必须在合适的范围内9. 全局异常处理2019-11-11T22:28:52+00:002019-11-11T22:28:52+00:00http://springboot.javaboy.org/2019/1111/exception<h2 id="9-全局异常处理">9. 全局异常处理</h2>
<p>项目中,可能会抛出多个异常,我们不可以直接将异常的堆栈信息展示给用户,有两个原因:</p>
<ol>
<li>用户体验不好</li>
<li>非常不安全</li>
</ol>
<p>所以,针对异常,我们可以自定义异常处理,SpringMVC 中,针对全局异常也提供了相应的解决方案,主要是通过 @ControllerAdvice 和 @ExceptionHandler 两个注解来处理的。</p>
<p>以第八节的文件上传大小超出限制为例,自定义异常,只需要提供一个异常处理类即可:</p>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nd">@ControllerAdvice</span><span class="c1">//表示这是一个增强版的 Controller,主要用来做全局数据处理</span>
<span class="kd">public</span> <span class="kd">class</span> <span class="nc">MyException</span> <span class="o">{</span>
<span class="nd">@ExceptionHandler</span><span class="o">(</span><span class="nc">Exception</span><span class="o">.</span><span class="na">class</span><span class="o">)</span>
<span class="kd">public</span> <span class="nc">ModelAndView</span> <span class="nf">fileuploadException</span><span class="o">(</span><span class="nc">Exception</span> <span class="n">e</span><span class="o">)</span> <span class="o">{</span>
<span class="nc">ModelAndView</span> <span class="n">error</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">ModelAndView</span><span class="o">(</span><span class="s">"error"</span><span class="o">);</span>
<span class="n">error</span><span class="o">.</span><span class="na">addObject</span><span class="o">(</span><span class="s">"error"</span><span class="o">,</span> <span class="n">e</span><span class="o">.</span><span class="na">getMessage</span><span class="o">());</span>
<span class="k">return</span> <span class="n">error</span><span class="o">;</span>
<span class="o">}</span>
<span class="o">}</span>
</code></pre></div></div>
<p>在这里:</p>
<ul>
<li>@ControllerAdvice 表示这是一个增强版的 Controller,主要用来做全局数据处理</li>
<li>@ExceptionHandler 表示这是一个异常处理方法,这个注解的参数,表示需要拦截的异常,参数为 Exception 表示拦截所有异常,这里也可以具体到某一个异常,如果具体到某一个异常,那么发生了其他异常则不会被拦截到。</li>
<li>异常方法的定义,和 Controller 中方法的定义一样,可以返回 ModelAndview,也可以返回 String 或者 void</li>
</ul>
<p>例如如下代码,指挥拦截文件上传异常,其他异常和它没关系,不会进入到自定义异常处理的方法中来。</p>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nd">@ControllerAdvice</span><span class="c1">//表示这是一个增强版的 Controller,主要用来做全局数据处理</span>
<span class="kd">public</span> <span class="kd">class</span> <span class="nc">MyException</span> <span class="o">{</span>
<span class="nd">@ExceptionHandler</span><span class="o">(</span><span class="nc">MaxUploadSizeExceededException</span><span class="o">.</span><span class="na">class</span><span class="o">)</span>
<span class="kd">public</span> <span class="nc">ModelAndView</span> <span class="nf">fileuploadException</span><span class="o">(</span><span class="nc">MaxUploadSizeExceededException</span> <span class="n">e</span><span class="o">)</span> <span class="o">{</span>
<span class="nc">ModelAndView</span> <span class="n">error</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">ModelAndView</span><span class="o">(</span><span class="s">"error"</span><span class="o">);</span>
<span class="n">error</span><span class="o">.</span><span class="na">addObject</span><span class="o">(</span><span class="s">"error"</span><span class="o">,</span> <span class="n">e</span><span class="o">.</span><span class="na">getMessage</span><span class="o">());</span>
<span class="k">return</span> <span class="n">error</span><span class="o">;</span>
<span class="o">}</span>
<span class="o">}</span>
</code></pre></div></div>江南一点雨wangsong0210@gmail.com9. 全局异常处理 项目中,可能会抛出多个异常,我们不可以直接将异常的堆栈信息展示给用户,有两个原因: 用户体验不好 非常不安全 所以,针对异常,我们可以自定义异常处理,SpringMVC 中,针对全局异常也提供了相应的解决方案,主要是通过 @ControllerAdvice 和 @ExceptionHandler 两个注解来处理的。 以第八节的文件上传大小超出限制为例,自定义异常,只需要提供一个异常处理类即可: @ControllerAdvice//表示这是一个增强版的 Controller,主要用来做全局数据处理 public class MyException { @ExceptionHandler(Exception.class) public ModelAndView fileuploadException(Exception e) { ModelAndView error = new ModelAndView("error"); error.addObject("error", e.getMessage()); return error; } } 在这里: @ControllerAdvice 表示这是一个增强版的 Controller,主要用来做全局数据处理 @ExceptionHandler 表示这是一个异常处理方法,这个注解的参数,表示需要拦截的异常,参数为 Exception 表示拦截所有异常,这里也可以具体到某一个异常,如果具体到某一个异常,那么发生了其他异常则不会被拦截到。 异常方法的定义,和 Controller 中方法的定义一样,可以返回 ModelAndview,也可以返回 String 或者 void 例如如下代码,指挥拦截文件上传异常,其他异常和它没关系,不会进入到自定义异常处理的方法中来。 @ControllerAdvice//表示这是一个增强版的 Controller,主要用来做全局数据处理 public class MyException { @ExceptionHandler(MaxUploadSizeExceededException.class) public ModelAndView fileuploadException(MaxUploadSizeExceededException e) { ModelAndView error = new ModelAndView("error"); error.addObject("error", e.getMessage()); return error; } }