搜索词>>REST 耗时0.0040
  • RESTful API 设计指南

    RESTful API 设计指南<h2>前言</h2> <p>    网络应用程序,分为前端和后端两个部分。当前的发展趋势,就是前端设备层出不穷(手机、平板、桌面电脑、其他专用设备......)。</p> <p>    因此,必须有一种统一的机制,方便不同的前端设备与后端进行通信。这导致API构架的流行,甚至出现"API First"的设计思想。RESTful API是目前比较成熟的一套互联网应用程序的API设计理论。我以前写过一篇《理解RESTful架构》,探讨如何理解这个概念。</p> <p>    今天,我将介绍RESTful API的设计细节,探讨如何设计一套合理、好用的API。</p> <h2>一、协议</h2> API与用户的通信协议,总是使用HTTP(s)协议。<br />   <h2>二、域名</h2> 应该尽量将API部署在专用域名之下。 <pre> <code class="language-html">https://api.leftso.com</code></pre> 如果确定API很简单,不会有进一步扩展,可以考虑放在主域名下。 <pre> <code class="language-html">https://leftso.com/api/</code></pre> <h2>三、版本(Versioning)</h2> 应该将API的版本号放入URL。 <pre> <code class="language-html">https://api.leftso.com/v1/</code></pre> 另一种做法是,将版本号放在HTTP头信息中,但不如放入URL方便和直观。\ <h2>四、路径(Endpoint)</h2> <p>路径又称"终点"(endpoint),表示API的具体网址。</p> <p>在RESTful架构中,每个网址代表一种资源(resource),所以网址中不能有动词,只能有名词,而且所用的名词往往与数据库的表格名对应。一般来说,数据库中的表都是同种记录的"集合"(collection),所以API中的名词也应该使用复数。</p> <p>举例来说,有一个API提供动物园(zoo)的信息,还包括各种动物和雇员的信息,则它的路径应该设计成下面这样。<br />  </p> <pre> <code class="language-html">https://api.example.com/v1/zoos https://api.example.com/v1/animals https://api.example.com/v1/employees</code></pre> <h2>五、HTTP动词</h2> <p>对于资源的具体操作类型,由HTTP动词表示。</p> <p>常用的HTTP动词有下面五个(括号里是对应的SQL命令)。</p> <pre> <code class="language-html">GET(SELECT):从服务器取出资源(一项或多项)。 POST(CREATE):在服务器新建一个资源。 PUT(UPDATE):在服务器更新资源(客户端提供改变后的完整资源)。 PATCH(UPDATE):在服务器更新资源(客户端提供改变的属性)。 DELETE(DELETE):从服务器删除资源。</code></pre> <br /> 还有两个不常用的HTTP动词。 <pre> <code class="language-html">HEAD:获取资源的元数据。 OPTIONS:获取信息,关于资源的哪些属性是客户端可以改变的。</code></pre> <br /> 下面是一些例子。 <pre> <code class="language-html">GET /zoos:列出所有动物园 POST /zoos:新建一个动物园 GET /zoos/ID:获取某个指定动物园的信息 PUT /zoos/ID:更新某个指定动物园的信息(提供该动物园的全部信息) PATCH /zoos/ID:更新某个指定动物园的信息(提供该动物园的部分信息) DELETE /zoos/ID:删除某个动物园 GET /zoos/ID/animals:列出某个指定动物园的所有动物 DELETE /zoos/ID/animals/ID:删除某个指定动物园的指定动物</code></pre> <h2>六、过滤信息(Filtering)</h2> <p>如果记录数量很多,服务器不可能都将它们返回给用户。API应该提供参数,过滤返回结果。</p> <p>下面是一些常见的参数。<br />  </p> <pre> <code class="language-html">?limit=10:指定返回记录的数量 ?offset=10:指定返回记录的开始位置。 ?page=2&per_page=100:指定第几页,以及每页的记录数。 ?sortby=name&order=asc:指定返回结果按照哪个属性排序,以及排序顺序。 ?animal_type_id=1:指定筛选条件</code></pre> 参数的设计允许存在冗余,即允许API路径和URL参数偶尔有重复。比如,GET /zoo/ID/animals 与 GET /animals?zoo_id=ID 的含义是相同的。<br />   <h2>七、状态码(Status Codes)</h2> 服务器向用户返回的状态码和提示信息,常见的有以下一些(方括号中是该状态码对应的HTTP动词)。 <pre> <code class="language-html">200 OK - [GET]:服务器成功返回用户请求的数据,该操作是幂等的(Idempotent)。 201 CREATED - [POST/PUT/PATCH]:用户新建或修改数据成功。 202 Accepted - [*]:表示一个请求已经进入后台排队(异步任务) 204 NO CONTENT - [DELETE]:用户删除数据成功。 400 INVALID REQUEST - [POST/PUT/PATCH]:用户发出的请求有错误,服务器没有进行新建或修改数据的操作,该操作是幂等的。 401 Unauthorized - [*]:表示用户没有权限(令牌、用户名、密码错误)。 403 Forbidden - [*] 表示用户得到授权(与401错误相对),但是访问是被禁止的。 404 NOT FOUND - [*]:用户发出的请求针对的是不存在的记录,服务器没有进行操作,该操作是幂等的。 406 Not Acceptable - [GET]:用户请求的格式不可得(比如用户请求JSON格式,但是只有XML格式)。 410 Gone -[GET]:用户请求的资源被永久删除,且不会再得到的。 422 Unprocesable entity - [POST/PUT/PATCH] 当创建一个对象时,发生一个验证错误。 500 INTERNAL SERVER ERROR - [*]:服务器发生错误,用户将无法判断发出的请求是否成功。</code></pre> <h2>八、错误处理(Error handling)</h2> 如果状态码是4xx,就应该向用户返回出错信息。一般来说,返回的信息中将error作为键名,出错信息作为键值即可。 <pre> <code class="language-json">{ error: "Invalid API key" }</code></pre> <h2>九、返回结果</h2> 针对不同操作,服务器向用户返回的结果应该符合以下规范。 <pre> <code class="language-html">GET /collection:返回资源对象的列表(数组) GET /collection/resource:返回单个资源对象 POST /collection:返回新生成的资源对象 PUT /collection/resource:返回完整的资源对象 PATCH /collection/resource:返回完整的资源对象 DELETE /collection/resource:返回一个空文档</code></pre>   <h2>十、Hypermedia API</h2> <p>RESTful API最好做到Hypermedia,即返回结果中提供链接,连向其他API方法,使得用户不查文档,也知道下一步应该做什么。</p> <p>比如,当用户向api.example.com的根目录发出请求,会得到这样一个文档。<br />  </p> <pre> <code class="language-json">{"link": { "rel": "collection https://www.example.com/zoos", "href": "https://api.example.com/zoos", "title": "List of zoos", "type": "application/vnd.yourformat+json" }}</code></pre> <p>上面代码表示,文档中有一个link属性,用户读取这个属性就知道下一步该调用什么API了。rel表示这个API与当前网址的关系(collection关系,并给出该collection的网址),href表示API的路径,title表示API的标题,type表示返回类型。</p> <p>Hypermedia API的设计被称为HATEOAS。Github的API就是这种设计,访问api.github.com会得到一个所有可用API的网址列表。<br />  </p> <pre> <code class="language-json">{ "current_user_url": "https://api.github.com/user", "authorizations_url": "https://api.github.com/authorizations", // ... }</code></pre> 从上面可以看到,如果想获取当前用户的信息,应该去访问api.github.com/user,然后就得到了下面结果。 <pre> <code class="language-json">{ "message": "Requires authentication", "documentation_url": "https://developer.github.com/v3" }</code></pre> 上面代码表示,服务器给出了提示信息,以及文档的网址。 <h2>十一、其他</h2> <ul> <li>API的身份认证应该使用OAuth 2.0框架。</li> <li>服务器返回的数据格式,应该尽量使用JSON,避免使用XML。</li> </ul>
  • 有一些REST技巧

    有一些REST技巧<p style="text-align:start"><span style="color:#333333"><span style="font-family:Tahoma,Arial,Verdana,sans-serif"><span style="background-color:#ffffff">在之前的博客  文章中,我介绍了实现REST体系结构的一些想法和提示。在这篇文章中,我会介绍更多的想法和提示。</span></span></span></p> <h3 style="margin-left:0px; margin-right:0px; text-align:start">高速缓存</h3> <ul style="margin-left:10px; margin-right:0px"> <li>缓存是原始论文的重要组成部分。</li> <li>战略包括验证(<em>客户检查它有最新版本</em>)和到期(<em>客户假定它有最新版本直到指定时间</em>)</li> <li>有效期: <ul style="margin-left:10px; margin-right:0px"> <li>当资源即将到期时,Expires头告诉客户端。值0意味着避免缓存</li> </ul> </li> <li> <ul style="margin-left:10px; margin-right:0px"> <li>缓存控制 <ul style="margin-left:10px; margin-right:0px"> <li>使用max-age指令指定响应应被视为有效的时间长度; s-maxage共享缓存</li> <li>也可以在请求中使用no-cache意味着重新验证服务器的响应</li> </ul> </li> </ul> </li> <li>验证 <ul style="margin-left:10px; margin-right:0px"> <li>Etag - 资源的唯一版本。与If-none-match请求标头一起使用</li> <li>Last-Modified - 告诉客户端资源上次更改的时间</li> </ul> </li> </ul> <h3 style="margin-left:0px; margin-right:0px; text-align:start">控制器API</h3> <ul style="margin-left:10px; margin-right:0px"> <li>当某些东西完全适合CRUD操作时,请考虑使用Controller API</li> </ul> <h3 style="margin-left:0px; margin-right:0px; text-align:start">处理日期</h3> <ul style="margin-left:10px; margin-right:0px"> <li>在您的日期使用ISO-8601 - 更适合自然分类,处理时区,地区数据,大多数编程语言的支持</li> <li>接受任何时区,因为世界上任何人都可能会调用您的API</li> <li>以UTC格式存储,不在服务器的时区中。坚持时不应该有抵消。</li> <li>以UTC返回。允许客户根据需要调整其时区</li> <li>如果你不需要,不要使用时间。如果只有日期就足够了,只需要保留日期。这意味着,时区复杂性消失。</li> </ul> <h3 style="margin-left:0px; margin-right:0px; text-align:start">头</h3> <ul style="margin-left:10px; margin-right:0px"> <li>HEAD操作应该返回响应头</li> </ul> <h3 style="margin-left:0px; margin-right:0px; text-align:start">头</h3> <ul style="margin-left:10px; margin-right:0px"> <li>总是返回什么标题是有用的。考虑: <ul style="margin-left:10px; margin-right:0px"> <li>内容类型</li> <li>内容长度</li> <li>上一次更改</li> <li>ETag的</li> <li>位置</li> </ul> </li> </ul> <h3 style="margin-left:0px; margin-right:0px; text-align:start">超媒体(优势)</h3> <ul style="margin-left:10px; margin-right:0px"> <li>更少的耦合</li> <li>一致的链接格式=>更干净的客户端代码</li> <li>开发人员的工作效率:API更易于浏览</li> <li>更容易以更细化的方式引入服务</li> <li>代码更易于调试 - 消息始终具有通过自链接创建它们的URL</li> </ul> <h3 style="margin-left:0px; margin-right:0px; text-align:start">超媒体(选择)</h3> <ul style="margin-left:10px; margin-right:0px"> <li>HAL - 减少地址耦合</li> <li>SIREN - 减少地址和动作耦合</li> <li>集合+ JSON(CJ) - 减少地址,动作和对象耦合</li> </ul> <h3 style="margin-left:0px; margin-right:0px; text-align:start">幂等</h3> <ul style="margin-left:10px; margin-right:0px"> <li>可以多次调用并返回相同的结果</li> <li>选项,GET,HEAD,PUT和DELETE都是幂等的</li> </ul> <h3 style="margin-left:0px; margin-right:0px; text-align:start">长时间运行的请求</h3> <ul style="margin-left:10px; margin-right:0px"> <li>有些操作需要很长时间。在这种情况下,请考虑返回202,并将位置字段设置为客户端可以轮询的URL以检查操作进度。</li> </ul> <h3 style="margin-left:0px; margin-right:0px; text-align:start">方法不允许</h3> <ul style="margin-left:10px; margin-right:0px"> <li>如果一个API只支持GET,它应该为任何PUT,POST,DELETE等返回一个<a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/405" rel="external nofollow" style="padding:0px; margin:0px; outline:none; list-style:none; border:0px none; color:#326693; text-decoration:none; transition:all 0.2s ease-in-out" target="_blank">405</a></li> </ul> <h3 style="margin-left:0px; margin-right:0px; text-align:start">必须忽略原则</h3> <ul style="margin-left:10px; margin-right:0px"> <li>客户应该忽略他们不感​​兴趣的数据。这使API更容易向后兼容。如果一个API返回额外的数据,而有些客户不期待它,他们会忽略它。</li> </ul> <h3 style="margin-left:0px; margin-right:0px; text-align:start">不能接受的</h3> <ul style="margin-left:10px; margin-right:0px"> <li>当某个资源不支持特定的媒体类型时,<em>当所请求的媒体类型无法提供时</em>,它应该返回406(即,<em>Masse,规则:406(“不可接受”)</em></li> </ul> <h3 style="margin-left:0px; margin-right:0px; text-align:start">OPTIONS</h3> <ul style="margin-left:10px; margin-right:0px"> <li>选项应该返回资源上可用的操作</li> </ul> <h3 style="margin-left:0px; margin-right:0px; text-align:start">部分更新</h3> <ul style="margin-left:10px; margin-right:0px"> <li>使用<a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Methods/PATCH" rel="external nofollow" style="padding:0px; margin:0px; outline:none; list-style:none; border:0px none; color:#326693; text-decoration:none; transition:all 0.2s ease-in-out" target="_blank">PATCH</a>处理部分更新 </li> </ul> <h3 style="margin-left:0px; margin-right:0px; text-align:start">询问</h3> <ul style="margin-left:10px; margin-right:0px"> <li>应该使用URI的查询组件来过滤集合</li> </ul> <h3 style="margin-left:0px; margin-right:0px; text-align:start">资源创建</h3> <ul style="margin-left:10px; margin-right:0px"> <li>当一个资源成功创建后,应该返回一个<a href="https://httpstatuses.com/201" rel="external nofollow" style="padding:0px; margin:0px; outline:none; list-style:none; border:0px none; color:#326693; text-decoration:none; transition:all 0.2s ease-in-out" target="_blank">201</a></li> <li>该位置头部应注明网址获取资源。</li> </ul> <h3 style="margin-left:0px; margin-right:0px; text-align:start">安全</h3> <ul style="margin-left:10px; margin-right:0px"> <li>如果操作不修改资源,则认为操作是<em>安全的</em></li> <li>选项,GET和HEAD是安全的</li> </ul> <h3 style="margin-left:0px; margin-right:0px; text-align:start">自我链接</h3> <ul style="margin-left:10px; margin-right:0px"> <li>响应组织应始终包含一个自我链接 - 用于返回资源的URL。</li> </ul> <h3 style="margin-left:0px; margin-right:0px; text-align:start">单数还是复数?</h3> <ul style="margin-left:10px; margin-right:0px"> <li>对单数文档类型资源使用Singular - 只能有一个。例如:/ humans / 12343343 / head</li> <li>否则是复数</li> </ul>
  • spring boot 入门之spring session&RESTful APIs

    spring boot 入门之spring session实现restful apis。通过spring boot或者spring mvc整合spring session的方式来实现session的共享达到应用可以水平扩展集群。<h2>一.简介</h2>    spring boot 入门之spring session实现restful apis。通过前面的了解和使用,我们知道可以通过spring boot或者spring mvc整合spring session的方式来实现session的共享达到应用可以水平扩展集群。session默认情况是存了一个cookie在响应的数据里面,然后每次通过cookie来验证session的,但是restful设计中一般不会使用cookie,所以spring session提供了将session id存放在http协议的header中,本文主要讲解如何使用。 <h2>二.编码介绍</h2> <h3>HttpSession & RESTful APIs</h3> 原理:Spring Session 可以允许session在header里面提供来实现RESTful APIs <h3>Spring配置</h3> 在创建一个spring boot项目后,加入相关的依赖。现在我们就可以来进行相关配置。这个配置主要负责创建一个servlet Filter替换原有的HttpSession 使用spring session.主要添加以下spring 相关配置: <pre> <code class="language-java">@Configuration @EnableRedisHttpSession //① public class HttpSessionConfig { @Bean public LettuceConnectionFactory connectionFactory() { return new LettuceConnectionFactory(); //②注意,spring boot项目这里不需要创建一个链接工厂,在application配置文件中配置了相关信息会默认生成一个链接工厂供使用。 } @Bean public HttpSessionStrategy httpSessionStrategy() { return new HeaderHttpSessionStrategy(); //③ } }</code></pre>   ①<code>@EnableRedisHttpSession</code> 注解用来创建一个spring的过滤器类,类的名称为 <code>springSessionRepositoryFilter</code> 这个过滤器用来替换原生的 <code>HttpSession</code> 使用spring session。这个例子中,Spring Session通过Redis来实现存储。<br />   ②创建了一个<code>RedisConnectionFactory</code> 来使Spring Session链接到Redis Server。关于spring data redis相关配置可以参考之前的spring redis整合。(注意,spring boot项目这里不需要创建一个链接工厂,在application配置文件中配置了相关信息会默认生成一个链接工厂供使用。)<br />   ③我们自定义了Spring  Session的HttpSession ,将它整合到了HTTP协议的header里面,替换掉默认的放在cookie中。 <h3>HttpSessionListener 即session监听处理</h3> Spring Session支持<code>HttpSessionListener</code>,通过将<code>SessionDestroyedEvent</code> 和SessionCreatedEvent转换成 <code>HttpSessionEvent</code> 被定义在SessionEventHttpSessionListenerAdapter来实现。需要使用监听,注意以下几点: <ul> <li>确保你所实现的<code>SessionRepository</code> 支持并且配置到启动<code>SessionDestroyedEvent</code> 和SessionCreatedEvent</li> <li>将<code>SessionEventHttpSessionListenerAdapter</code> 配置为spring容器中的一个bean</li> <li>注入每一个<code>HttpSessionListener</code> 到SessionEventHttpSessionListenerAdapter</li> </ul> 如果你使用Redis的方式记录HttpSession,你需要将每一个HttpSessionListener配置为bean。例如,假设你想要支持Spring Security的并发控制,并且需要使用HttpSessionEventPublisher,你可以简单地将HttpSessionEventPublisher添加为一个bean。 在Java配置中,这可能如下所示: <pre> <code class="language-java">@Configuration @EnableRedisHttpSession public class RedisHttpSessionConfig { @Bean public HttpSessionEventPublisher httpSessionEventPublisher() { return new HttpSessionEventPublisher(); } // ... }</code></pre> 或者xml <pre> <code class="language-xml"><bean class="org.springframework.security.web.session.HttpSessionEventPublisher"/></code></pre>
  • spring boot 文件上传 REST风格API ajax方式-Java编程

    Java编程之Spring Boot 文件上传 REST风格API ajax方式<h2>一、摘要说明</h2>   这篇文章将告诉你在spring boot(REST 风格)WEB项目中如何用ajax请求上传文件。<br /> <br />   本文章中需要使用的工具: <ol> <li>Spring Boot 1.4.3.RELEASE</li> <li>Spring 4.3.5.RELEASE</li> <li>Thymeleaf</li> <li>jQuery (webjars)</li> <li>Maven</li> <li>Embedded Tomcat 8.5.6</li> <li>Google Chrome 浏览器(Network Inspect)</li> </ol> <h2>二、项目结构</h2> 一个标准的maven项目结构<br /> <img alt="项目结构" class="img-thumbnail" src="/resources/assist/images/blog/811600c9be364e56b3c68ebbe78e0b07.png" /> <h2>三、项目依赖</h2> 声明一个外部的jQuery webjar依赖项,用于HTML表单中的Ajax请求。<br /> <br /> <strong>pom.xml</strong> <pre> <code class="language-xml"><project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>com.mkyong</groupId> <artifactId>spring-boot-file-upload</artifactId> <packaging>jar</packaging> <version>1.0</version> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>1.4.3.RELEASE</version> </parent> <properties> <java.version>1.8</java.version> </properties> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-thymeleaf</artifactId> </dependency> <!-- hot swapping, disable cache for template, enable live reload --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-devtools</artifactId> <optional>true</optional> </dependency> <dependency> <groupId>org.webjars</groupId> <artifactId>jquery</artifactId> <version>2.2.4</version> </dependency> </dependencies> <build> <plugins> <!-- Package as an executable jar/war --> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins> </build> </project></code></pre> <h2>四、文件上传</h2> 为了支持AJAX请求和响应, 最简单的方式是返回<code>ResponseEntity</code>. <h2>4.1下面的例子演示了上传文件的三种可能方式:</h2> <ol> <li>单个文件上传 – <code>MultipartFile</code></li> <li>多个文件上传– <code>MultipartFile[]</code></li> <li>Map file upload to a Model – <code>@ModelAttribute</code></li> </ol> <strong>RestUploadController.java</strong> <pre> <code class="language-java">import com.mkyong.model.UploadModel; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.http.HttpHeaders; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.util.StringUtils; import org.springframework.web.bind.annotation.ModelAttribute; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; import org.springframework.web.multipart.MultipartFile; import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; import java.util.Arrays; import java.util.List; import java.util.stream.Collectors; @RestController public class RestUploadController { private final Logger logger = LoggerFactory.getLogger(RestUploadController.class); //Save the uploaded file to this folder private static String UPLOADED_FOLDER = "F://temp//"; // 3.1.1 Single file upload @PostMapping("/api/upload") // If not @RestController, uncomment this //@ResponseBody public ResponseEntity<?> uploadFile( @RequestParam("file") MultipartFile uploadfile) { logger.debug("Single file upload!"); if (uploadfile.isEmpty()) { return new ResponseEntity("please select a file!", HttpStatus.OK); } try { saveUploadedFiles(Arrays.asList(uploadfile)); } catch (IOException e) { return new ResponseEntity<>(HttpStatus.BAD_REQUEST); } return new ResponseEntity("Successfully uploaded - " + uploadfile.getOriginalFilename(), new HttpHeaders(), HttpStatus.OK); } // 3.1.2 Multiple file upload @PostMapping("/api/upload/multi") public ResponseEntity<?> uploadFileMulti( @RequestParam("extraField") String extraField, @RequestParam("files") MultipartFile[] uploadfiles) { logger.debug("Multiple file upload!"); // Get file name String uploadedFileName = Arrays.stream(uploadfiles).map(x -> x.getOriginalFilename()) .filter(x -> !StringUtils.isEmpty(x)).collect(Collectors.joining(" , ")); if (StringUtils.isEmpty(uploadedFileName)) { return new ResponseEntity("please select a file!", HttpStatus.OK); } try { saveUploadedFiles(Arrays.asList(uploadfiles)); } catch (IOException e) { return new ResponseEntity<>(HttpStatus.BAD_REQUEST); } return new ResponseEntity("Successfully uploaded - " + uploadedFileName, HttpStatus.OK); } // 3.1.3 maps html form to a Model @PostMapping("/api/upload/multi/model") public ResponseEntity<?> multiUploadFileModel(@ModelAttribute UploadModel model) { logger.debug("Multiple file upload! With UploadModel"); try { saveUploadedFiles(Arrays.asList(model.getFiles())); } catch (IOException e) { return new ResponseEntity<>(HttpStatus.BAD_REQUEST); } return new ResponseEntity("Successfully uploaded!", HttpStatus.OK); } //save file private void saveUploadedFiles(List<MultipartFile> files) throws IOException { for (MultipartFile file : files) { if (file.isEmpty()) { continue; //next pls } byte[] bytes = file.getBytes(); Path path = Paths.get(UPLOADED_FOLDER + file.getOriginalFilename()); Files.write(path, bytes); } } }</code></pre> <h2>4.2选择4.1中的第三种方式上传文件并且使用注解@ModelAttribute</h2> <strong>UploadModel.java</strong> <pre> <code class="language-java">import org.springframework.web.multipart.MultipartFile; public class UploadModel { private String extraField; private MultipartFile[] files; //getters and setters }</code></pre> <h2>五、视图文件的编写</h2> HTML form for multiple file uploads.<br /> <strong>upload.html</strong> <pre> <code class="language-html"><!DOCTYPE html> <html xmlns:th="http://www.thymeleaf.org"> <body> <h1>Spring Boot - Multiple file upload example - AJAX</h1> <form method="POST" enctype="multipart/form-data" id="fileUploadForm"> <input type="text" name="extraField"/><br/><br/> <input type="file" name="files"/><br/><br/> <input type="file" name="files"/><br/><br/> <input type="submit" value="Submit" id="btnSubmit"/> </form> <h1>Ajax Post Result</h1> <pre> <span id="result"></span> </pre> <script type="text/javascript" src="webjars/jquery/2.2.4/jquery.min.js"></script> <script type="text/javascript" src="js/main.js"></script> </body> </html></code></pre> <h2>六、jQuery – Ajax Request</h2> jQuery通过f<code>#id获取form</code>,并且发送multipart form 数据通过ajax请求.<br /> <strong>resources/static/js/main.js</strong> <pre> <code class="language-javascript">$(document).ready(function () { $("#btnSubmit").click(function (event) { //stop submit the form, we will post it manually. event.preventDefault(); fire_ajax_submit(); }); }); function fire_ajax_submit() { // Get form var form = $('#fileUploadForm')[0]; var data = new FormData(form); data.append("CustomField", "This is some extra data, testing"); $("#btnSubmit").prop("disabled", true); $.ajax({ type: "POST", enctype: 'multipart/form-data', url: "/api/upload/multi", data: data, //http://api.jquery.com/jQuery.ajax/ //https://developer.mozilla.org/en-US/docs/Web/API/FormData/Using_FormData_Objects processData: false, //prevent jQuery from automatically transforming the data into a query string contentType: false, cache: false, timeout: 600000, success: function (data) { $("#result").text(data); console.log("SUCCESS : ", data); $("#btnSubmit").prop("disabled", false); }, error: function (e) { $("#result").text(e.responseText); console.log("ERROR : ", e); $("#btnSubmit").prop("disabled", false); } }); }</code></pre> <h2>七、Exception Handler异常拦截</h2> 拦截来自 Ajax请求的异常, 只需要继承 <code>ResponseEntityExceptionHandler</code> 并且返回 <code>ResponseEntity</code>.<br /> <strong>RestGlobalExceptionHandler.java</strong> <pre> <code class="language-java">import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.ControllerAdvice; import org.springframework.web.bind.annotation.ExceptionHandler; import org.springframework.web.bind.annotation.ResponseBody; import org.springframework.web.multipart.MultipartException; import org.springframework.web.servlet.mvc.method.annotation.ResponseEntityExceptionHandler; import javax.servlet.http.HttpServletRequest; //http://docs.spring.io/spring-boot/docs/current/reference/htmlsingle/#boot-features-error-handling @ControllerAdvice public class RestGlobalExceptionHandler extends ResponseEntityExceptionHandler { // Catch file size exceeded exception! @ExceptionHandler(MultipartException.class) @ResponseBody ResponseEntity<?> handleControllerException(HttpServletRequest request, Throwable ex) { HttpStatus status = getStatus(request); return new ResponseEntity(ex.getMessage(), status); // example //return new ResponseEntity("success", responseHeaders, HttpStatus.OK); } private HttpStatus getStatus(HttpServletRequest request) { Integer statusCode = (Integer) request.getAttribute("javax.servlet.error.status_code"); if (statusCode == null) { return HttpStatus.INTERNAL_SERVER_ERROR; } return HttpStatus.valueOf(statusCode); } }</code></pre> <h2>八、演示</h2> 使用默认的嵌入式Tomcat启动Spring启动<code>mvn spring-boot:run</code>.<br /> 8.1访问http://localhost:8080/,选择几个文件并单击submit以触发ajax请求。<br /> <img alt="访问并上传" class="img-thumbnail" src="/resources/assist/images/blog/19d6d8c691d04a079fd5a18adc7560fa.png" /><br /> 8.2 Google Chrome浏览器,检查“网络检查”中的请求和响应<br /> <img alt="观察请求" class="img-thumbnail" src="/resources/assist/images/blog/ae51363c32cf415b943dbdc57d659470.png" /><br /> 8.4 Google Chrome, “Request Payload”<br /> <img alt="上传内容" class="img-thumbnail" src="/resources/assist/images/blog/7f1ff5d5dde64d89ae7afd664c5df0f5.png" /> <h2>9.curl工具测试</h2> More testing with <code>cURL</code> command.<br /> 9.1测试单个文件上传 <pre> <code>$ curl -F file=@"f:\\data.txt" http://localhost:8080/api/upload/ Successfully uploaded - data.txt</code></pre> <br /> 9.2.测试多个文件上传 <pre> <code>$ curl -F extraField="abc" -F files=@"f://data.txt" -F files=@"f://data2.txt" http://localhost:8080/api/upload/multi/ Successfully uploaded - data.txt , data2.txt</code></pre> 9.3测试多个文件上传使用maps to Model模式 <pre> <code>$ curl -F extraField="abc" -F files=@"f://data.txt" -F files=@"f://data2.txt" http://localhost:8080/api/upload/multi/model Successfully uploaded! </code></pre> <br /> 9.4测试一个大文件(超过100MB),将会提示下面的错误信息 <pre> <code>$ curl -F file=@"F://movies//300//Sample.mkv" http://localhost:8080/api/upload/ Attachment size exceeds the allowable limit! (10MB)</code></pre> <h2>十、curl测试加自定义错误对象测试</h2> 10.1创建一个自定义对象去存放异常信息<br /> <strong>CustomError.java</strong> <pre> <code class="language-java">public class CustomError { String errCode; String errDesc; public CustomError(String errCode, String errDesc) { this.errCode = errCode; this.errDesc = errDesc; } //getters and setters }</code></pre> 10.2更新全局的异常拦截支持自定义异常信息<br /> <strong>RestGlobalExceptionHandler.java</strong> <pre> <code class="language-java">import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.ControllerAdvice; import org.springframework.web.bind.annotation.ExceptionHandler; import org.springframework.web.bind.annotation.ResponseBody; import org.springframework.web.multipart.MultipartException; import org.springframework.web.servlet.mvc.method.annotation.ResponseEntityExceptionHandler; import javax.servlet.http.HttpServletRequest; @ControllerAdvice public class RestGlobalExceptionHandler extends ResponseEntityExceptionHandler { @ExceptionHandler(MultipartException.class) @ResponseBody ResponseEntity<?> handleControllerException(HttpServletRequest request, Throwable ex) { HttpStatus status = getStatus(request); return new ResponseEntity(new CustomError("0x000123", "Attachment size exceeds the allowable limit! (10MB)"), status); //return new ResponseEntity("Attachment size exceeds the allowable limit! (10MB)", status); } private HttpStatus getStatus(HttpServletRequest request) { Integer statusCode = (Integer) request.getAttribute("javax.servlet.error.status_code"); if (statusCode == null) { return HttpStatus.INTERNAL_SERVER_ERROR; } return HttpStatus.valueOf(statusCode); } }</code></pre> <br /> 10.3再次上传一个大文件 <pre> <code>$ curl -F file=@"F://movies//300//Sample.mkv" http://localhost:8080/api/upload/ {"errCode":"0x000123","errDesc":"Attachment size exceeds the allowable limit! (10MB)"}</code></pre> <br /> <a href="http://www.mkyong.com/wp-content/uploads/2017/01/spring-boot-file-upload-ajax-rest.zip" rel="external nofollow" target="_blank">demo项目下载</a>
  • 使用OAuth2安全的Spring security REST API(含demo代码下载)

    使用OAuth2安全的Spring REST API,Secure Spring REST API using OAuth2(含demo代码下载)<p>    让我们<code>Spring REST API</code>使用<code>OAuth2</code>这个时间来保护我们,这是一个简单的指南,显示使用REST API来保护REST API所需的内容<code>Spring OAuth2</code>。我们的用例符合<code>Resource-owner Password Grant</code>OAUth2规范的流程。我们将使用两个不同的客户端(Postman和<code>Spring RestTemplate</code>基于Java 的应用程序)访问我们的OAuth2受保护的REST资源。</p> <p>    如果您已经熟悉OAuth2概念,您可能希望跳过该理论,并直接跳转到代码中。一如以往,在本文末尾附件中可以找到完整的代码。让我们开始吧。<br />  </p> <h3>    什么是OAuth2</h3> <p>    OAuth2是一个标准化的授权协议/框架。根据官方OAuth2规范:</p> <p>    OAuth 2.0授权框架使得第三方应用程序可以通过协调资源所有者和HTTP服务之间的批准交互来代替资源所有者来<u>获取</u>对HTTP服务的<u>有限访问权限</u>,也可以允许第三方应用程序以自己的身份获得访问权限。</p> <p>    Google,Facebook等大型玩家已经在使用自己的OAuth2实现了很长一段时间。企业也正在朝OAuth2采纳方向发展。</p> <p>我发现OAuth2规范比较简单。然而,如果你想开始更快,可以在这里找到一篇关于OAuth2基础知识的优秀文章,从而深入了解OAUth2理论概念。</p> <p>Spring Security OAuth项目提供了使用Spring开发符合OAuth2标准的实现所需的所有API。官方的Spring安全oauth项目提供了一个实施OAuth2的综合示例。这个帖子的代码示例灵感来自这个例子本身。这篇文章的目的是为了保护我们的REST API,只需使用所需的最低限度的功能。</p> <p>至少你应该知道OAuth2中的四个关键概念:<br />  </p> <h3>OAuth2角色</h3> <p>OAuth2定义了四个角色:</p> <ul> <li><strong><code>resource owner</code>:</strong><br /> 可能是你 能够授予访问受保护资源的实体。当资源所有者是个人时,它被称为最终用户。</li> <li><strong><code>resource server</code>:</strong><br /> 托管受保护资源的服务器,能够使用访问令牌接受和响应受保护的资源请求。</li> <li><strong><code>client</code>:</strong><br /> 代表资源所有者及其授权的应用程序生成受保护的资源请求。它可能是一个移动应用程序,要求您访问您的Facebook订阅源,REST客户端尝试访问REST API,一个网站[Stackoverflow例如]使用Facebook帐户提供备用登录选项。</li> <li><strong><code>authorization server</code>:</strong><br /> 服务器在成功验证资源所有者并获得授权后,向客户端发出访问令牌。</li> </ul> <p>在我们的示例中,我们的REST API只能通过资源服务器进行访问,这将需要存在请求的访问令牌<br />  </p> <h3>2. OAuth2授权授权类型</h3> <p>授权授权是表示资源所有者授权(访问其受保护的资源)的凭据,由客户端使用以获取访问令牌。规范定义了四种授权类型:</p> <ul> <li><code>authorization code</code></li> <li><code>implicit</code></li> <li><code>resource owner password credentials</code></li> <li><code>client credentials</code></li> </ul> <p>我们将使用<code>resource owner password credentials</code>授权类型。原因很简单,我们没有实现将我们重定向到登录页面的视图。仅客户端[Postman或RestTemplate为基础的Java客户端例如]拥有资源所有者的凭据,并且他们将这些凭证以及客户机凭证提供给授权服务器,以便最终接收访问令牌[和可选刷新令牌],然后使用该令牌实际访问资源。</p> <p>一个常见的例子是<code>GMail app</code>您的智能手机上的[客户端],您需要您的凭据并使用它们进行连接<code>GMail servers</code>。它还显示“密码凭证授予”最适合当客户端和服务器与信任在同一个公司时,您不想向第三方提供凭据。</p> <h3>OAuth2令牌</h3> <p>令牌是实现特定的随机字符串,由授权服务器生成,并在客户端请求时发出。</p> <ul> <li><code>Access Token</code> :发送每个请求,通常有效期很短的一段时间[一小时例如]</li> <li><code>Refresh Token</code> :主要用于获取新的访问令牌,不发送每个请求,通常比访问令牌寿命更长。</li> </ul> <strong>HTTPS上的一个词</strong>:对于任何类型的安全实现,从基本身份验证到完整的OAuth2实现<strong><code>HTTPS</code></strong>都是必须的。没有HTTPS,无论您的实现是什么,安全性都将受到威胁。 <h3>OAuth2访问令牌范围</h3> <p>客户端可以使用范围[想要访问此用户的Facebook帐户的Feed和照片]来查询具有特定访问权限的资源,授权服务器又返回显示实际授予客户端访问权限的范围[资源所有者只允许Feed访问,没有照片例如]。</p> <hr />我们进入代码 <p>我们来实现使用Spring Security实现OAuth的必要构建块,以便访问我们的REST资源。</p> <h3>资源服务器</h3> <p>资源服务器托管客户端感兴趣的资源[我们的REST API]。资源位于<code>/user/</code>。<code>@EnableResourceServer</code>注释,应用于OAuth2资源服务器,启用使用传入OAuth2令牌对请求进行身份验证的Spring Security过滤器。类<code>ResourceServerConfigurerAdapter</code>实现<code>ResourceServerConfigurer</code>提供了调整由OAuth2安全保护的访问规则和路径的方法。</p> <pre> <code class="language-java">package com.websystique.springmvc.security;   import org.springframework.context.annotation.Configuration; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.oauth2.config.annotation.web.configuration.EnableResourceServer; import org.springframework.security.oauth2.config.annotation.web.configuration.ResourceServerConfigurerAdapter; import org.springframework.security.oauth2.config.annotation.web.configurers.ResourceServerSecurityConfigurer; import org.springframework.security.oauth2.provider.error.OAuth2AccessDeniedHandler;   @Configuration @EnableResourceServer public class ResourceServerConfiguration extends ResourceServerConfigurerAdapter {       private static final String RESOURCE_ID = "my_rest_api";           @Override     public void configure(ResourceServerSecurityConfigurer resources) {         resources.resourceId(RESOURCE_ID).stateless(false);     }       @Override     public void configure(HttpSecurity http) throws Exception {         http.         anonymous().disable()         .requestMatchers().antMatchers("/user/**")         .and().authorizeRequests()         .antMatchers("/user/**").access("hasRole('ADMIN')")         .and().exceptionHandling().accessDeniedHandler(new OAuth2AccessDeniedHandler());     }   }</code></pre> <h3><br /> 授权服务器是一个负责验证凭据的人员,如果凭据确定,提供令牌[刷新令牌以及访问令牌]。它还包含有关注册的客户端和可能的访问范围和授权类型的信息。令牌存储用于存储令牌。我们将使用内存中的令牌存储。<code>@EnableAuthorizationServer</code>在当前应用程序上下文中启用授权服务器(即AuthorizationEndpoint和TokenEndpoint)。类<code>AuthorizationServerConfigurerAdapter</code>实现<code>AuthorizationServerConfigurer</code>,它提供了配置授权服务器的所有必要方法。2.授权服务器</h3> <pre> <code class="language-java"> package com.websystique.springmvc.security;   import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.context.annotation.Configuration; import org.springframework.security.authentication.AuthenticationManager; import org.springframework.security.oauth2.config.annotation.configurers.ClientDetailsServiceConfigurer; import org.springframework.security.oauth2.config.annotation.web.configuration.AuthorizationServerConfigurerAdapter; import org.springframework.security.oauth2.config.annotation.web.configuration.EnableAuthorizationServer; import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerEndpointsConfigurer; import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerSecurityConfigurer; import org.springframework.security.oauth2.provider.approval.UserApprovalHandler; import org.springframework.security.oauth2.provider.token.TokenStore;   @Configuration @EnableAuthorizationServer public class AuthorizationServerConfiguration extends AuthorizationServerConfigurerAdapter {       private static String REALM="MY_OAUTH_REALM";           @Autowired     private TokenStore tokenStore;       @Autowired     private UserApprovalHandler userApprovalHandler;       @Autowired     @Qualifier("authenticationManagerBean")     private AuthenticationManager authenticationManager;       @Override     public void configure(ClientDetailsServiceConfigurer clients) throws Exception {           clients.inMemory()             .withClient("my-trusted-client")             .authorizedGrantTypes("password", "authorization_code", "refresh_token", "implicit")             .authorities("ROLE_CLIENT", "ROLE_TRUSTED_CLIENT")             .scopes("read", "write", "trust")             .secret("secret")             .accessTokenValiditySeconds(120).//Access token is only valid for 2 minutes.             refreshTokenValiditySeconds(600);//Refresh token is only valid for 10 minutes.     }       @Override     public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {         endpoints.tokenStore(tokenStore).userApprovalHandler(userApprovalHandler)                 .authenticationManager(authenticationManager);     }       @Override     public void configure(AuthorizationServerSecurityConfigurer oauthServer) throws Exception {         oauthServer.realm(REALM+"/client");     }   }</code></pre> <p><br /> <br /> 向客户端注册客户端“我的信任客户端”和密码“秘密”以及允许的角色和范围。以上配置</p> <ul> <li>指定任何生成的访问令牌只有120秒有效</li> <li>指定任何生成的刷新令牌只有600秒有效</li> </ul> <h3>3.安全配置</h3> <p>粘在一起 端点<code>/oauth/token</code>用于请求令牌[访问或刷新]。资源所有者[bill,bob]在这里配置。</p> <pre> <code class="language-java">package com.websystique.springmvc.security;   import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.security.authentication.AuthenticationManager; import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; import org.springframework.security.oauth2.provider.ClientDetailsService; import org.springframework.security.oauth2.provider.approval.ApprovalStore; import org.springframework.security.oauth2.provider.approval.TokenApprovalStore; import org.springframework.security.oauth2.provider.approval.TokenStoreUserApprovalHandler; import org.springframework.security.oauth2.provider.request.DefaultOAuth2RequestFactory; import org.springframework.security.oauth2.provider.token.TokenStore; import org.springframework.security.oauth2.provider.token.store.InMemoryTokenStore;   @Configuration @EnableWebSecurity public class OAuth2SecurityConfiguration extends WebSecurityConfigurerAdapter {       @Autowired     private ClientDetailsService clientDetailsService;           @Autowired     public void globalUserDetails(AuthenticationManagerBuilder auth) throws Exception {         auth.inMemoryAuthentication()         .withUser("bill").password("abc123").roles("ADMIN").and()         .withUser("bob").password("abc123").roles("USER");     }       @Override     protected void configure(HttpSecurity http) throws Exception {         http         .csrf().disable()         .anonymous().disable()         .authorizeRequests()         .antMatchers("/oauth/token").permitAll();     }       @Override     @Bean     public AuthenticationManager authenticationManagerBean() throws Exception {         return super.authenticationManagerBean();     }         @Bean     public TokenStore tokenStore() {         return new InMemoryTokenStore();     }       @Bean     @Autowired     public TokenStoreUserApprovalHandler userApprovalHandler(TokenStore tokenStore){         TokenStoreUserApprovalHandler handler = new TokenStoreUserApprovalHandler();         handler.setTokenStore(tokenStore);         handler.setRequestFactory(new DefaultOAuth2RequestFactory(clientDetailsService));         handler.setClientDetailsService(clientDetailsService);         return handler;     }           @Bean     @Autowired     public ApprovalStore approvalStore(TokenStore tokenStore) throws Exception {         TokenApprovalStore store = new TokenApprovalStore();         store.setTokenStore(tokenStore);         return store;     }       }</code></pre> <p>另外,启用全局方法安全性,如果我们想要使用它,它将激活@PreFilter,@PostFilter,@PreAuthorize @PostAuthorize注释。</p> <pre> <code class="language-java"> package com.websystique.springmvc.security;   import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Configuration; import org.springframework.security.access.expression.method.MethodSecurityExpressionHandler; import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity; import org.springframework.security.config.annotation.method.configuration.GlobalMethodSecurityConfiguration; import org.springframework.security.oauth2.provider.expression.OAuth2MethodSecurityExpressionHandler;   @Configuration @EnableGlobalMethodSecurity(prePostEnabled = true, proxyTargetClass = true) public class MethodSecurityConfig extends GlobalMethodSecurityConfiguration {     @Autowired     private OAuth2SecurityConfiguration securityConfig;       @Override     protected MethodSecurityExpressionHandler createExpressionHandler() {         return new OAuth2MethodSecurityExpressionHandler();     } }</code></pre> <h3>4.终点及其目的</h3> <ul> <li>尝试访问资源[REST API],无需任何授权[将当然失败]。<br /> <code>GET http://localhost:8080/SpringSecurityOAuth2Example/user/</code></li> <li>问令牌[接入+刷新]使用<strong>HTTP POST</strong>上<code>/oauth/token</code>,与grant_type =密码和资源所有者凭证REQ-PARAMS。另外,在授权头中发送客户端凭据。 <p> </p> <p><code>POST http://localhost:8080/SpringSecurityOAuth2Example/oauth/token?grant_type=password&username=bill&password=abc123</code></p> </li> <li>要求通过有效的刷新令牌新的访问令牌,使用<strong>HTTP POST</strong>上<code>/oauth/token</code>,与grant_type = refresh_token,并发送实际的刷新令牌。另外,在授权头中发送客户端凭据。 <p> </p> <p><code>POST http://localhost:8080/SpringSecurityOAuth2Example/oauth/token?grant_type=refresh_token&refresh_token=094b7d23-973f-4cc1-83ad-8ffd43de1845</code></p> </li> <li>通过使用<code>access_token</code>具有请求的查询参数提供访问令牌来访问资源。<br /> <code>GET http://localhost:8080/SpringSecurityOAuth2Example/user/?access_token=3525d0e4-d881-49e7-9f91-bcfd18259109</code></li> </ul> <h3>5.休息API</h3> <p>我在大部分帖子中使用的简单的Spring REST API。</p> <pre> <code class="language-java"> package com.websystique.springmvc.controller;    import java.util.List;    import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.HttpHeaders; import org.springframework.http.HttpStatus; import org.springframework.http.MediaType; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; import org.springframework.web.bind.annotation.RestController; import org.springframework.web.util.UriComponentsBuilder;    import com.websystique.springmvc.model.User; import com.websystique.springmvc.service.UserService;    @RestController public class HelloWorldRestController {        @Autowired     UserService userService;  //Service which will do all data retrieval/manipulation work               //-------------------Retrieve All Users--------------------------------------------------------            @RequestMapping(value = "/user/", method = RequestMethod.GET)     public ResponseEntity<List<User>> listAllUsers() {         List<User> users = userService.findAllUsers();         if(users.isEmpty()){             return new ResponseEntity<List<User>>(HttpStatus.NO_CONTENT);//You many decide to return HttpStatus.NOT_FOUND         }         return new ResponseEntity<List<User>>(users, HttpStatus.OK);     }           //-------------------Retrieve Single User--------------------------------------------------------            @RequestMapping(value = "/user/{id}", method = RequestMethod.GET, produces = {MediaType.APPLICATION_JSON_VALUE,MediaType.APPLICATION_XML_VALUE})     public ResponseEntity<User> getUser(@PathVariable("id") long id) {         System.out.println("Fetching User with id " + id);         User user = userService.findById(id);         if (user == null) {             System.out.println("User with id " + id + " not found");             return new ResponseEntity<User>(HttpStatus.NOT_FOUND);         }         return new ResponseEntity<User>(user, HttpStatus.OK);     }                      //-------------------Create a User--------------------------------------------------------            @RequestMapping(value = "/user/", method = RequestMethod.POST)     public ResponseEntity<Void> createUser(@RequestBody User user, UriComponentsBuilder ucBuilder) {         System.out.println("Creating User " + user.getName());            if (userService.isUserExist(user)) {             System.out.println("A User with name " + user.getName() + " already exist");             return new ResponseEntity<Void>(HttpStatus.CONFLICT);         }            userService.saveUser(user);            HttpHeaders headers = new HttpHeaders();         headers.setLocation(ucBuilder.path("/user/{id}").buildAndExpand(user.getId()).toUri());         return new ResponseEntity<Void>(headers, HttpStatus.CREATED);     }               //------------------- Update a User --------------------------------------------------------            @RequestMapping(value = "/user/{id}", method = RequestMethod.PUT)     public ResponseEntity<User> updateUser(@PathVariable("id") long id, @RequestBody User user) {         System.out.println("Updating User " + id);                    User currentUser = userService.findById(id);                    if (currentUser==null) {             System.out.println("User with id " + id + " not found");             return new ResponseEntity<User>(HttpStatus.NOT_FOUND);         }            currentUser.setName(user.getName());         currentUser.setAge(user.getAge());         currentUser.setSalary(user.getSalary());                    userService.updateUser(currentUser);         return new ResponseEntity<User>(currentUser, HttpStatus.OK);     }        //------------------- Delete a User --------------------------------------------------------            @RequestMapping(value = "/user/{id}", method = RequestMethod.DELETE)     public ResponseEntity<User> deleteUser(@PathVariable("id") long id) {         System.out.println("Fetching & Deleting User with id " + id);            User user = userService.findById(id);         if (user == null) {             System.out.println("Unable to delete. User with id " + id + " not found");             return new ResponseEntity<User>(HttpStatus.NOT_FOUND);         }            userService.deleteUserById(id);         return new ResponseEntity<User>(HttpStatus.NO_CONTENT);     }               //------------------- Delete All Users --------------------------------------------------------            @RequestMapping(value = "/user/", method = RequestMethod.DELETE)     public ResponseEntity<User> deleteAllUsers() {         System.out.println("Deleting All Users");            userService.deleteAllUsers();         return new ResponseEntity<User>(HttpStatus.NO_CONTENT);     }    }</code></pre> <h3><br /> 运行应用程序</h3> <p>运行它并使用两个不同的客户端进行测试。<br /> <br />  </p> <h4>客户端1:Postman</h4> <p>尝试访问一个没有任何信息的资源,将获得一个401。</p> <p><img alt="SpringOAuth2_img1" class="img-thumbnail" src="/resources/assist/images/blog/bbd70c98-9e02-46e9-b4b7-c4bdfadb1d93.png" /></p> <p>让我们得到令牌。首先添加一个<strong>客户端凭证</strong> [my-trusted-client / secret] 的授权头文件。</p> <p><img alt="SpringOAuth2_img2" class="img-thumbnail" src="/resources/assist/images/blog/9c19299c-5b53-4dfb-97f7-30b81963ac1f.png" /></p> <p>单击更新请求,验证标题标签中的标题。</p> <p><img alt="SpringOAuth2_img3" class="img-thumbnail" src="/resources/assist/images/blog/e404605e-f273-476d-a582-0147146e96d9.png" /></p> <p>发送POST请求时,您将收到包含响应<code>access-token</code>以及<code>refresh-token</code>。</p> <p><img alt="SpringOAuth2_img4" class="img-thumbnail" src="/resources/assist/images/blog/a0e2e85e-0a1b-40f8-86e4-3c9f1c062276.png" /></p> <p>将这些令牌保存在某个地方,您将需要它们。现在,您可以使用此访问令牌(有效期为2分钟)来访问资源。</p> <p><img alt="SpringOAuth2_img5" class="img-thumbnail" src="/resources/assist/images/blog/77e1a43f-c866-4b33-b1f0-f1c7d5232f89.png" /></p> <p>2分钟后,access-token将过期,您的进一步资源请求将失败。</p> <p><img alt="SpringOAuth2_img6" class="img-thumbnail" src="/resources/assist/images/blog/dc75aaec-9ce3-4fe0-8bb7-c98e3e8c6054.png" /></p> <p>我们需要一个新的访问令牌。通过刷新令牌来触发一个帖子,以获得全新的访问令牌。</p> <p><img alt="SpringOAuth2_img7" class="img-thumbnail" src="/resources/assist/images/blog/34a5980f-0d49-416a-adf4-fe4182056648.png" /></p> <p>使用这个新的访问令牌访问资源。</p> <p><img alt="SpringOAuth2_img8" class="img-thumbnail" src="/resources/assist/images/blog/4e0b3771-92ed-438a-bc0f-15de7bc6fcf1.png" /></p> <p>刷新令牌也过期[10分钟]。之后,您将看到刷新请求失败。</p> <p><img alt="SpringOAuth2_img9" class="img-thumbnail" src="/resources/assist/images/blog/c8760e84-cdfa-4760-ad3e-0ff07768c117.png" /></p> <p>这意味着您需要请求新的刷新+访问令牌,如步骤2中所示。</p> <h4>客户端2:基于RestTemplate的java应用程序</h4> <p>方法<strong>sendTokenRequest</strong>用于实际获取令牌。然后,我们收到的访问令牌将被用于每个请求。如果需要,您可以在下面的示例中轻松实现刷新令牌流。</p> <pre> <code class="language-java">package com.websystique.springmvc; import java.net.URI; import java.util.Arrays; import java.util.LinkedHashMap; import java.util.List; import org.apache.commons.codec.binary.Base64; import org.springframework.http.HttpEntity; import org.springframework.http.HttpHeaders; import org.springframework.http.HttpMethod; import org.springframework.http.MediaType; import org.springframework.http.ResponseEntity; import org.springframework.util.Assert; import org.springframework.web.client.RestTemplate; import com.websystique.springmvc.model.AuthTokenInfo; import com.websystique.springmvc.model.User; public class SpringRestClient { public static final String REST_SERVICE_URI = "http://localhost:8080/SpringSecurityOAuth2Example"; public static final String AUTH_SERVER_URI = "http://localhost:8080/SpringSecurityOAuth2Example/oauth/token"; public static final String QPM_PASSWORD_GRANT = "?grant_type=password&username=bill&password=abc123"; public static final String QPM_ACCESS_TOKEN = "?access_token="; /* * Prepare HTTP Headers. */ private static HttpHeaders getHeaders(){ HttpHeaders headers = new HttpHeaders(); headers.setAccept(Arrays.asList(MediaType.APPLICATION_JSON)); return headers; } /* * Add HTTP Authorization header, using Basic-Authentication to send client-credentials. */ private static HttpHeaders getHeadersWithClientCredentials(){ String plainClientCredentials="my-trusted-client:secret"; String base64ClientCredentials = new String(Base64.encodeBase64(plainClientCredentials.getBytes())); HttpHeaders headers = getHeaders(); headers.add("Authorization", "Basic " + base64ClientCredentials); return headers; } /* * Send a POST request [on /oauth/token] to get an access-token, which will then be send with each request. */ @SuppressWarnings({ "unchecked"}) private static AuthTokenInfo sendTokenRequest(){ RestTemplate restTemplate = new RestTemplate(); HttpEntity<String> request = new HttpEntity<String>(getHeadersWithClientCredentials()); ResponseEntity<Object> response = restTemplate.exchange(AUTH_SERVER_URI+QPM_PASSWORD_GRANT, HttpMethod.POST, request, Object.class); LinkedHashMap<String, Object> map = (LinkedHashMap<String, Object>)response.getBody(); AuthTokenInfo tokenInfo = null; if(map!=null){ tokenInfo = new AuthTokenInfo(); tokenInfo.setAccess_token((String)map.get("access_token")); tokenInfo.setToken_type((String)map.get("token_type")); tokenInfo.setRefresh_token((String)map.get("refresh_token")); tokenInfo.setExpires_in((int)map.get("expires_in")); tokenInfo.setScope((String)map.get("scope")); System.out.println(tokenInfo); //System.out.println("access_token ="+map.get("access_token")+", token_type="+map.get("token_type")+", refresh_token="+map.get("refresh_token") //+", expires_in="+map.get("expires_in")+", scope="+map.get("scope"));; }else{ System.out.println("No user exist----------"); } return tokenInfo; } /* * Send a GET request to get list of all users. */ @SuppressWarnings({ "unchecked", "rawtypes" }) private static void listAllUsers(AuthTokenInfo tokenInfo){ Assert.notNull(tokenInfo, "Authenticate first please......"); System.out.println("\nTesting listAllUsers API-----------"); RestTemplate restTemplate = new RestTemplate(); HttpEntity<String> request = new HttpEntity<String>(getHeaders()); ResponseEntity<List> response = restTemplate.exchange(REST_SERVICE_URI+"/user/"+QPM_ACCESS_TOKEN+tokenInfo.getAccess_token(), HttpMethod.GET, request, List.class); List<LinkedHashMap<String, Object>> usersMap = (List<LinkedHashMap<String, Object>>)response.getBody(); if(usersMap!=null){ for(LinkedHashMap<String, Object> map : usersMap){ System.out.println("User : id="+map.get("id")+", Name="+map.get("name")+", Age="+map.get("age")+", Salary="+map.get("salary"));; } }else{ System.out.println("No user exist----------"); } } /* * Send a GET request to get a specific user. */ private static void getUser(AuthTokenInfo tokenInfo){ Assert.notNull(tokenInfo, "Authenticate first please......"); System.out.println("\nTesting getUser API----------"); RestTemplate restTemplate = new RestTemplate(); HttpEntity<String> request = new HttpEntity<String>(getHeaders()); ResponseEntity<User> response = restTemplate.exchange(REST_SERVICE_URI+"/user/1"+QPM_ACCESS_TOKEN+tokenInfo.getAccess_token(), HttpMethod.GET, request, User.class); User user = response.getBody(); System.out.println(user); } /* * Send a POST request to create a new user. */ private static void createUser(AuthTokenInfo tokenInfo) { Assert.notNull(tokenInfo, "Authenticate first please......"); System.out.println("\nTesting create User API----------"); RestTemplate restTemplate = new RestTemplate(); User user = new User(0,"Sarah",51,134); HttpEntity<Object> request = new HttpEntity<Object>(user, getHeaders()); URI uri = restTemplate.postForLocation(REST_SERVICE_URI+"/user/"+QPM_ACCESS_TOKEN+tokenInfo.getAccess_token(), request, User.class); System.out.println("Location : "+uri.toASCIIString()); } /* * Send a PUT request to update an existing user. */ private static void updateUser(AuthTokenInfo tokenInfo) { Assert.notNull(tokenInfo, "Authenticate first please......"); System.out.println("\nTesting update User API----------"); RestTemplate restTemplate = new RestTemplate(); User user = new User(1,"Tomy",33, 70000); HttpEntity<Object> request = new HttpEntity<Object>(user, getHeaders()); ResponseEntity<User> response = restTemplate.exchange(REST_SERVICE_URI+"/user/1"+QPM_ACCESS_TOKEN+tokenInfo.getAccess_token(), HttpMethod.PUT, request, User.class); System.out.println(response.getBody()); } /* * Send a DELETE request to delete a specific user. */ private static void deleteUser(AuthTokenInfo tokenInfo) { Assert.notNull(tokenInfo, "Authenticate first please......"); System.out.println("\nTesting delete User API----------"); RestTemplate restTemplate = new RestTemplate(); HttpEntity<String> request = new HttpEntity<String>(getHeaders()); restTemplate.exchange(REST_SERVICE_URI+"/user/3"+QPM_ACCESS_TOKEN+tokenInfo.getAccess_token(), HttpMethod.DELETE, request, User.class); } /* * Send a DELETE request to delete all users. */ private static void deleteAllUsers(AuthTokenInfo tokenInfo) { Assert.notNull(tokenInfo, "Authenticate first please......"); System.out.println("\nTesting all delete Users API----------"); RestTemplate restTemplate = new RestTemplate(); HttpEntity<String> request = new HttpEntity<String>(getHeaders()); restTemplate.exchange(REST_SERVICE_URI+"/user/"+QPM_ACCESS_TOKEN+tokenInfo.getAccess_token(), HttpMethod.DELETE, request, User.class); } public static void main(String args[]){ AuthTokenInfo tokenInfo = sendTokenRequest(); listAllUsers(tokenInfo); getUser(tokenInfo); createUser(tokenInfo); listAllUsers(tokenInfo); updateUser(tokenInfo); listAllUsers(tokenInfo); deleteUser(tokenInfo); listAllUsers(tokenInfo); deleteAllUsers(tokenInfo); listAllUsers(tokenInfo); } }</code></pre> <br /> 以上代码将产生以下输出: <pre> <code>AuthTokenInfo [access_token=fceed386-5923-4bf8-b193-1d76f95da4c4, token_type=bearer, refresh_token=29d28ee2-9d09-483f-a2d6-7f93e7a31667, expires_in=71, scope=read write trust] Testing listAllUsers API----------- User : id=1, Name=Sam, Age=30, Salary=70000.0 User : id=2, Name=Tom, Age=40, Salary=50000.0 User : id=3, Name=Jerome, Age=45, Salary=30000.0 User : id=4, Name=Silvia, Age=50, Salary=40000.0 Testing getUser API---------- User [id=1, name=Sam, age=30, salary=70000.0] Testing create User API---------- Location : http://localhost:8080/SpringSecurityOAuth2Example/user/5 Testing listAllUsers API----------- User : id=1, Name=Sam, Age=30, Salary=70000.0 User : id=2, Name=Tom, Age=40, Salary=50000.0 User : id=3, Name=Jerome, Age=45, Salary=30000.0 User : id=4, Name=Silvia, Age=50, Salary=40000.0 User : id=5, Name=Sarah, Age=51, Salary=134.0 Testing update User API---------- User [id=1, name=Tomy, age=33, salary=70000.0] Testing listAllUsers API----------- User : id=1, Name=Tomy, Age=33, Salary=70000.0 User : id=2, Name=Tom, Age=40, Salary=50000.0 User : id=3, Name=Jerome, Age=45, Salary=30000.0 User : id=4, Name=Silvia, Age=50, Salary=40000.0 User : id=5, Name=Sarah, Age=51, Salary=134.0 Testing delete User API---------- Testing listAllUsers API----------- User : id=1, Name=Tomy, Age=33, Salary=70000.0 User : id=2, Name=Tom, Age=40, Salary=50000.0 User : id=4, Name=Silvia, Age=50, Salary=40000.0 User : id=5, Name=Sarah, Age=51, Salary=134.0 Testing all delete Users API---------- Testing listAllUsers API----------- No user exist----------</code></pre>   <h3>项目结构</h3> <img alt="10" class="img-thumbnail" src="/resources/assist/images/blog/aab904e5-276f-4fac-8bd4-3762adb1a28c.png" /> <h3>pom.xml</h3> <pre> <code class="language-xml"><project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>com.websystique.springmvc</groupId> <artifactId>SpringSecurityOAuth2Example</artifactId> <version>1.0.0</version> <packaging>war</packaging> <name>SpringSecurityOAuth2Example</name> <properties> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <springframework.version>4.3.1.RELEASE</springframework.version> <springsecurity.version>4.1.1.RELEASE</springsecurity.version> <springsecurityoauth2.version>2.0.10.RELEASE</springsecurityoauth2.version> <jackson.library>2.7.5</jackson.library> </properties> <dependencies> <!-- Spring --> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-core</artifactId> <version>${springframework.version}</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-web</artifactId> <version>${springframework.version}</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-webmvc</artifactId> <version>${springframework.version}</version> </dependency> <!-- Spring Security --> <dependency> <groupId>org.springframework.security</groupId> <artifactId>spring-security-web</artifactId> <version>${springsecurity.version}</version> </dependency> <dependency> <groupId>org.springframework.security</groupId> <artifactId>spring-security-config</artifactId> <version>${springsecurity.version}</version> </dependency> <!-- Spring Security OAuth2--> <dependency> <groupId>org.springframework.security.oauth</groupId> <artifactId>spring-security-oauth2</artifactId> <version>${springsecurityoauth2.version}</version> </dependency> <!-- Jackson libraries --> <dependency> <groupId>com.fasterxml.jackson.core</groupId> <artifactId>jackson-databind</artifactId> <version>${jackson.library}</version> </dependency> <dependency> <groupId>com.fasterxml.jackson.dataformat</groupId> <artifactId>jackson-dataformat-xml</artifactId> <version>${jackson.library}</version> </dependency> <dependency> <groupId>javax.servlet</groupId> <artifactId>javax.servlet-api</artifactId> <version>3.1.0</version> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-compiler-plugin</artifactId> <version>3.2</version> <configuration> <source>1.7</source> <target>1.7</target> </configuration> </plugin> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-war-plugin</artifactId> <version>2.4</version> <configuration> <warSourceDirectory>src/main/webapp</warSourceDirectory> <warName>SpringSecurityOAuth2Example</warName> <failOnMissingWebXml>false</failOnMissingWebXml> </configuration> </plugin> </plugins> <finalName>SpringSecurityOAuth2Example</finalName> </build> </project></code></pre> <h4><strong><a href="http://websystique.com/?smd_process_download=1&download_id=2926" rel="external nofollow" target="_blank"><em><u>下载源代码</u></em></a></strong></h4> <br />  
  • spring boot REST 通过Swagger2生成接口文档(含例子源码下载)

    Swagger2 java中Swagger2 生成spring boot REST接口文档,spring boot,Swagger2<h2>一、使用背景</h2>     如今,REST和微服务已经有了很大的发展势头。但是,REST规范中并没有提供一种规范来编写我们的对外REST接口API文档。每个人都在用自己的方式记录api文档,因此没有一种标准规范能够让我们很容易的理解和使用该接口。我们需要一个共同的规范和统一的工具来解决文档的难易理解文档的混乱格式。<br />     Swagger(在谷歌、IBM、微软等公司的支持下)做了一个公共的文档风格来填补上述问题。在本博客中,我们将会学习怎么使用Swagger的swagger 2注解去生成REST API文档。 <h2>二、本博客将会讲解以下内容</h2> <ol> <li>什么是Swagger?</li> <li>项目结构和技术堆栈</li> <li>创建REST api文档</li> <li>Swagger2的配置</li> <li>Swagger2注释的使用</li> <li>一个简单的例子</li> </ol> <h2>三、什么是Swagger?</h2>     Swagger(现在是“开放API计划”)是一种规范和框架,它使用一种人人都能理解的通用语言来描述REST API。还有其他一些可用的框架,比如RAML、求和等等,但是,考虑到它在开发者社区中的特性和接受度,在这个时候,Swagger是最受欢迎的。<br /> <br />     它提供了人类可读和机器可读的文档格式。它提供了JSON和UI支持。JSON可以用作机器可读的格式,而 Swagger-UI 是用于可视显示的,通过浏览api文档,人们很容易理解它。 <h2>四、项目结构和技术堆栈</h2>   项目结构图如下:<br /> <img alt="项目结构图" class="img-thumbnail" src="/resources/assist/images/blog/63e7415b2e5f4c49a20676f6b04740d0.png" /><br />   我们将在这个演示中使用下面的技术 <ol> <li>Eclipse开发工具</li> <li>Maven 构建工具</li> <li>Spring Boot 应用框架</li> <li>Spring Rest 的REST API框架</li> <li>Swagger2 来作为REST API文档框架</li> <li>Java 1.8</li> </ol> <h2>五、创建REST api</h2>     我们将首先创建一些REST api接口,这些api接口将用于演示Swagger的文档功能。我们将使用Spring boot风格来显示rest API,以获得更快的开发时间。<br /> <br />     1.去spring boot官网创建一个web带Rest接口依赖的spring boot项目。提供其他maven GAV坐标并下载该项目。这个屏幕看起来是这样的:<br /> <img alt="spring boot" class="img-thumbnail" src="/resources/assist/images/blog/a72040b07cff4db29c43d0eed45d37b6.jpg" />    解压缩并将项目导入到Eclipse中,作为现有的maven项目。在此步骤中,将从maven存储库下载所有必需的依赖项。在这个步骤中执行一个新的mvn清洁安装,这样所有spring boot相关的依赖组件都得到了正确的下载。<br /> <br /> 2.打开application.properties配置文件。添加以下属性。他将会从 <code>/swagger2-demo</code>context 路径启动应用. <pre> <code>server.contextPath=/swagger2-demo</code></pre> 3.添加一个REST的contrller,名称为<code>Swagger2DemoRestController,他将会</code>为学生实体提供基本的基于REST的功能。<br /> <strong>Swagger2DemoRestController.java</strong> <pre> <code class="language-java">package com.example.springbootswagger2.controller; import java.util.ArrayList; import java.util.List; import java.util.stream.Collectors; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import com.example.springbootswagger2.model.Student; @RestController public class Swagger2DemoRestController { List<Student> students = new ArrayList<Student>(); { students.add(new Student("Sajal", "IV", "India")); students.add(new Student("Lokesh", "V", "India")); students.add(new Student("Kajal", "III", "USA")); students.add(new Student("Sukesh", "VI", "USA")); } @RequestMapping(value = "/getStudents") public List<Student> getStudents() { return students; } @RequestMapping(value = "/getStudent/{name}") public Student getStudent(@PathVariable(value = "name") String name) { return students.stream().filter(x -> x.getName().equalsIgnoreCase(name)).collect(Collectors.toList()).get(0); } @RequestMapping(value = "/getStudentByCountry/{country}") public List<Student> getStudentByCountry(@PathVariable(value = "country") String country) { System.out.println("Searching Student in country : " + country); List<Student> studentsByCountry = students.stream().filter(x -> x.getCountry().equalsIgnoreCase(country)) .collect(Collectors.toList()); System.out.println(studentsByCountry); return studentsByCountry; } @RequestMapping(value = "/getStudentByClass/{cls}") public List<Student> getStudentByClass(@PathVariable(value = "cls") String cls) { return students.stream().filter(x -> x.getCls().equalsIgnoreCase(cls)).collect(Collectors.toList()); } }</code></pre> <br /> <strong>Student.java</strong> <pre> <code class="language-java">package com.example.springbootswagger2.model; public class Student { private String name; private String cls; private String country; public Student(String name, String cls, String country) { super(); this.name = name; this.cls = cls; this.country = country; } public String getName() { return name; } public String getCls() { return cls; } public String getCountry() { return country; } @Override public String toString() { return "Student [name=" + name + ", cls=" + cls + ", country=" + country + "]"; } }</code></pre> 4.以spring boot方式启动这个程序应用。测试下面的一两个REST端点来检查他们是否正常工作: <ul> <li>http://localhost:8080/swagger2-demo/getStudents</li> <li>http://localhost:8080/swagger2-demo/getStudent/sajal</li> <li>http://localhost:8080/swagger2-demo/getStudentByCountry/india</li> <li>http://localhost:8080/swagger2-demo/getStudentByClass/v</li> </ul> <h2>六、配置Swagger2</h2> 我们的REST api已经准备好了。现在,我们为项目增加swagger 2 的支持 <h3>6.1首先添加Swagger2 的maven依赖库</h3> 打开项目中的pom.xml文件,添加以下两个swagger依赖。<code>springfox-swagger2</code> 、<code>springfox-swagger-ui</code> <pre> <code class="language-xml"><dependency> <groupId>io.springfox</groupId> <artifactId>springfox-swagger2</artifactId> <version>2.6.1</version> </dependency> <dependency> <groupId>io.springfox</groupId> <artifactId>springfox-swagger-ui</artifactId> <version>2.6.1</version> </dependency></code></pre> <br /> <span style="color:#2ecc71"><strong>提示:</strong></span><em>实际上,Swagger的API有两种类型,并在不同的工件中维护。今天我们将使用springfox,因为这个版本可以很好地适应任何基于spring的配置。我们还可以很容易地尝试其他配置,这应该提供相同的功能——配置中没有任何变化。</em><br />   <h3>6.2添加Swagger2配置</h3> 使用Java config的方式添加配置。为了帮助你理解这个配置,我在代码中写了相关的注释 <pre> <code class="language-java">package com.example.springbootswagger2.configuration; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry; import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter; import com.google.common.base.Predicates; import springfox.documentation.builders.RequestHandlerSelectors; import springfox.documentation.spi.DocumentationType; import springfox.documentation.spring.web.plugins.Docket; import springfox.documentation.swagger2.annotations.EnableSwagger2; @Configuration @EnableSwagger2 public class Swagger2UiConfiguration extends WebMvcConfigurerAdapter { @Bean public Docket api() { // @formatter:off //Register the controllers to swagger //Also it is configuring the Swagger Docket return new Docket(DocumentationType.SWAGGER_2).select() // .apis(RequestHandlerSelectors.any()) .apis(Predicates.not(RequestHandlerSelectors.basePackage("org.springframework.boot"))) // .paths(PathSelectors.any()) // .paths(PathSelectors.ant("/swagger2-demo")) .build(); // @formatter:on } @Override public void addResourceHandlers(ResourceHandlerRegistry registry) { //enabling swagger-ui part for visual documentation registry.addResourceHandler("swagger-ui.html").addResourceLocations("classpath:/META-INF/resources/"); registry.addResourceHandler("/webjars/**").addResourceLocations("classpath:/META-INF/resources/webjars/"); } }</code></pre> <h3>6.3验证Swagger2的JSON格式文档</h3>     maven构建并启动服务器。打开链接http://localhost:8080/swagger2-demo/v2/api-docs,会生成一个JSON格式的文档。这并不是那么容易理解和理解的,实际上Swagger已经提供该文档在其他三方工具中使用,例如当今流行的API管理工具,它提供了API网关、API缓存、API文档等功能。<br /> <img alt="JSON格式文档" class="img-thumbnail" src="/resources/assist/images/blog/ce4275a7c4554bcf95d61e67950f06a5.jpg" /> <h3>6.4验证Swagger2 UI文档</h3>     打开链接 http://localhost:8080/swagger2-demo/swagger-ui.html 在浏览器中来查看Swagger UI文档 <h3><img alt="swagger2 UI文档" class="img-thumbnail" src="/resources/assist/images/blog/d0562f6eaddc4f2bbdeb6ed29e8f2b7a.png" />6.5Swagger2 注解的使用</h3> 默认生成的API文档很好,但是它们缺乏详细的API级别信息。Swagger提供了一些注释,可以将这些详细信息添加到api中。如。<br /> <code>@Api</code> –我们可以添加这个注解在controller上,去添加一个基本的controller说明 <pre> <code class="language-java">@Api(value = "Swagger2DemoRestController", description = "REST APIs related to Student Entity!!!!") @RestController public class Swagger2DemoRestController { //... }</code></pre> <code>@ApiOperation and @ApiResponses</code> – 我们添加这个注解到任何controller的rest方法上来给方法添加基本的描述。例如: <pre> <code class="language-java">@ApiOperation(value = "Get list of Students in the System ", response = Iterable.class, tags = "getStudents") @ApiResponses(value = { @ApiResponse(code = 200, message = "Success|OK"), @ApiResponse(code = 401, message = "not authorized!"), @ApiResponse(code = 403, message = "forbidden!!!"), @ApiResponse(code = 404, message = "not found!!!") }) @RequestMapping(value = "/getStudents") public List<Student> getStudents() { return students; }</code></pre> 在这里,我们可以向方法中添加标签,来在<code>swagger-ui</code>中添加一些分组。<br /> <code>@ApiModelProperty</code> – 这个注解用来在数据模型对象中的属性上添加一些描述,会在Swagger UI中展示模型的属性。例如: <pre> <code class="language-java">@ApiModelProperty(notes = "Name of the Student",name="name",required=true,value="test name") private String name;</code></pre> Controller 和 Model 类添加了swagger2注解之后,代码清单:<br /> <strong>Swagger2DemoRestController.java</strong> <pre> <code class="language-java">package com.example.springbootswagger2.controller; import java.util.ArrayList; import java.util.List; import java.util.stream.Collectors; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import com.example.springbootswagger2.model.Student; import io.swagger.annotations.Api; import io.swagger.annotations.ApiOperation; import io.swagger.annotations.ApiResponse; import io.swagger.annotations.ApiResponses; @Api(value = "Swagger2DemoRestController", description = "REST Apis related to Student Entity!!!!") @RestController public class Swagger2DemoRestController { List<Student> students = new ArrayList<Student>(); { students.add(new Student("Sajal", "IV", "India")); students.add(new Student("Lokesh", "V", "India")); students.add(new Student("Kajal", "III", "USA")); students.add(new Student("Sukesh", "VI", "USA")); } @ApiOperation(value = "Get list of Students in the System ", response = Iterable.class, tags = "getStudents") @ApiResponses(value = { @ApiResponse(code = 200, message = "Suceess|OK"), @ApiResponse(code = 401, message = "not authorized!"), @ApiResponse(code = 403, message = "forbidden!!!"), @ApiResponse(code = 404, message = "not found!!!") }) @RequestMapping(value = "/getStudents") public List<Student> getStudents() { return students; } @ApiOperation(value = "Get specific Student in the System ", response = Student.class, tags = "getStudent") @RequestMapping(value = "/getStudent/{name}") public Student getStudent(@PathVariable(value = "name") String name) { return students.stream().filter(x -> x.getName().equalsIgnoreCase(name)).collect(Collectors.toList()).get(0); } @ApiOperation(value = "Get specific Student By Country in the System ", response = Student.class, tags = "getStudentByCountry") @RequestMapping(value = "/getStudentByCountry/{country}") public List<Student> getStudentByCountry(@PathVariable(value = "country") String country) { System.out.println("Searching Student in country : " + country); List<Student> studentsByCountry = students.stream().filter(x -> x.getCountry().equalsIgnoreCase(country)) .collect(Collectors.toList()); System.out.println(studentsByCountry); return studentsByCountry; } // @ApiOperation(value = "Get specific Student By Class in the System ",response = Student.class,tags="getStudentByClass") @RequestMapping(value = "/getStudentByClass/{cls}") public List<Student> getStudentByClass(@PathVariable(value = "cls") String cls) { return students.stream().filter(x -> x.getCls().equalsIgnoreCase(cls)).collect(Collectors.toList()); } }</code></pre> <strong>Student.java</strong> <pre> <code class="language-java">package com.example.springbootswagger2.model; import io.swagger.annotations.ApiModelProperty; public class Student { @ApiModelProperty(notes = "Name of the Student",name="name",required=true,value="test name") private String name; @ApiModelProperty(notes = "Class of the Student",name="cls",required=true,value="test class") private String cls; @ApiModelProperty(notes = "Country of the Student",name="country",required=true,value="test country") private String country; public Student(String name, String cls, String country) { super(); this.name = name; this.cls = cls; this.country = country; } public String getName() { return name; } public String getCls() { return cls; } public String getCountry() { return country; } @Override public String toString() { return "Student [name=" + name + ", cls=" + cls + ", country=" + country + "]"; } }</code></pre> <h2>七、例子</h2> 现在,当我们的REST api得到适当的注释时,让我们看看最终的输出。打开http://localhost:8080 / swagger2-demo / swagger-ui。在浏览器中查看Swagger ui 文档。<br /> <img alt="ui" class="img-thumbnail" src="/resources/assist/images/blog/0bd0e5f3384b4edf90950252d4d74648.jpg" /><br /> <br /> <a href="http://howtodoinjava.com/wp-content/uploads/2017/07/spring-boot-swagger2.zip" rel="external nofollow" target="_blank">demo源码下载</a>
  • Spring Boot 2.0 – Spring Boot REST HATEOAS例子

    在这个Spring HATEOAS示例中,我们将学习如何将HATEOAS链接添加到在spring boot项目中创建的现有REST API。我们将使用类org.springframework.hateoas.ResourceSupport沿org.springframework.hateoas.mvc.ControllerLinkBuilder和org.springframework.hateoas.Link Spring HATEOAS模块提供的类。<h2>前言</h2> <blockquote> <div style="margin-left:0px; margin-right:0px; text-align:start"><span style="color:#333333"><span style="font-family:varela round,sans-serif">在这个</span><strong>Spring HATEOAS</strong><span style="font-family:varela round,sans-serif">示例中,我们将学习如何将</span><strong>HATEOAS</strong><span style="font-family:varela round,sans-serif">链接添加到在spring boot项目中创建的现有REST API。我们将使用类</span><br /> <code><a href="https://docs.spring.io/spring-hateoas/docs/current/api/org/springframework/hateoas/ResourceSupport.html" rel="external nofollow" style="font-family: "varela round", sans-serif; box-sizing: border-box; transition: all 0.1s ease-in-out; color: rgb(3, 102, 214); text-decoration: none;" target="_blank" >org.springframework.hateoas.ResourceSupport</a><span style="font-family:varela round,sans-serif"> 、</span></code><br /> <code><a href="https://docs.spring.io/spring-hateoas/docs/current/api/org/springframework/hateoas/mvc/ControllerLinkBuilder.html" rel="external nofollow" style="box-sizing:border-box; transition:all 0.1s ease-in-out; color:#0366d6; text-decoration:none" target="_blank" >org.springframework.hateoas.mvc.ControllerLinkBuilder</a></code><span style="font-family:varela round,sans-serif">和</span><br /> <code><a href="https://docs.spring.io/spring-hateoas/docs/current/api/org/springframework/hateoas/Link.html" rel="external nofollow" style="box-sizing:border-box; transition:all 0.1s ease-in-out; color:#0366d6; text-decoration:none" target="_blank" >org.springframework.hateoas.Link</a> </code><span style="font-family:varela round,sans-serif">Spring HATEOAS模块提供的类。</span></span></div> </blockquote> <p style="margin-left:0px; margin-right:0px; text-align:start"><span style="color:#333333"><span style="font-family:"varela round",sans-serif"><span style="background-color:#ffffff">为了演示链接的创建,我们将首先创建一些REST API并查看它们的输出。然后,我们将HATEOAS链接应用到REST资源,然后我们将比较有无链接的输出。</span></span></span><br /> HATEOAS(The Hypermedia As The Engine Of Application Statue)<br />  </p> <h2 style="margin-left:0px; margin-right:0px; text-align:start">项目结构</h2> <img srcset="" width="" size="" class="img-thumbnail" alt="Spring HATEOAS示例 - 项目结构" src="/resources/assist/images/blog/d4448712de3a4ffdadf633d00da45e7d.png" /> <h2 style="margin-left:0px; margin-right:0px; text-align:start">创建REST API</h2> 在这个例子中,我创建了三个带有端点的REST API,如下所示: <ul> <li>/employees</li> <li>/employees/{id}</li> <li>/employees/{id}/report</li> </ul> <h4 style="margin-left:0px; margin-right:0px; text-align:start">REST资源模型</h4> EmployeeListVO: <pre> <code class="language-java">@XmlRootElement (name="employees") public class EmployeeListVO implements Serializable { private static final long serialVersionUID = 1L; private List<EmployeeVO> employees = new ArrayList<EmployeeVO>(); public List<EmployeeVO> getEmployees() { return employees; } public void setEmployees(List<EmployeeVO> employees) { this.employees = employees; } }</code></pre> EmployeeListVO: <pre> <code class="language-java">@XmlRootElement(name = "employee") @XmlAccessorType(XmlAccessType.NONE) public class EmployeeVO implements Serializable { private static final long serialVersionUID = 1L; public EmployeeVO(Integer id, String firstName, String lastName, String email) { super(); this.employeeId = id; this.firstName = firstName; this.lastName = lastName; this.email = email; } public EmployeeVO() { } @XmlAttribute private Integer employeeId; @XmlElement private String firstName; @XmlElement private String lastName; @XmlElement private String email; //removed getters and setters for readability @Override public String toString() { return "EmployeeVO [id=" + employeeId + ", firstName=" + firstName + ", lastName=" + lastName + ", email=" + email + "]"; } }</code></pre> EmployeeReport: <pre> <code class="language-java">@XmlRootElement(name="employee-report") public class EmployeeReport implements Serializable { private static final long serialVersionUID = 1L; //根据需要添加其他信息 }</code></pre> <h4>REST Controller</h4> <strong>EmployeeRESTController:</strong> <pre> <code class="language-java">@RestController public class EmployeeRESTController { @RequestMapping(value = "/employees") public EmployeeListVO getAllEmployees() { EmployeeListVO employeesList = new EmployeeListVO(); for (EmployeeVO employee : EmployeeDB.getEmployeeList()) { employeesList.getEmployees().add(employee); } return employeesList; } @RequestMapping(value = "/employees/{id}") public ResponseEntity<EmployeeVO> getEmployeeById (@PathVariable("id") int id) { if (id <= 3) { EmployeeVO employee = EmployeeDB.getEmployeeList().get(id-1); return new ResponseEntity<EmployeeVO>(employee, HttpStatus.OK); } return new ResponseEntity<EmployeeVO>(HttpStatus.NOT_FOUND); } @RequestMapping(value = "/employees/{id}/report") public ResponseEntity<EmployeeReport> getReportByEmployeeById (@PathVariable("id") int id) { //Do some operation and return report return null; } }</code></pre> <h4 style="margin-left:0px; margin-right:0px; text-align:start">DAO层</h4> <p style="margin-left:0px; margin-right:0px; text-align:start"><span style="color:#333333"><span style="font-family:"varela round",sans-serif"><span style="background-color:#ffffff">我已经创建了<code>EmployeeDB</code>类来模拟DAO层。在实际应用中,从数据源获取数据将是更复杂的代码。</span></span></span></p> <strong>EmployeeDB</strong> <pre> <code class="language-java">public class EmployeeDB { public static List<EmployeeVO> getEmployeeList() { List<EmployeeVO> list = new ArrayList<>(); EmployeeVO empOne = new EmployeeVO(1, "Lokesh", "Gupta", "demo@gmail.com"); EmployeeVO empTwo = new EmployeeVO(2, "Amit", "Singhal", "asinghal@yahoo.com"); EmployeeVO empThree = new EmployeeVO(3, "Kirti", "Mishra", "kmishra@gmail.com"); list.add(empOne); list.add(empTwo); list.add(empThree); return list; } }</code></pre>   <h4 style="margin-left:0px; margin-right:0px; text-align:start">启动类:</h4> <pre> <code class="language-java">@SpringBootApplication public class Application { public static void main(String[] args) { ApplicationContext ctx = SpringApplication.run(Application.class, args); } }</code></pre> <h4 style="margin-left:0px; margin-right:0px; text-align:start"><br /> Maven - pom.xml</h4> <p style="margin-left:0px; margin-right:0px; text-align:start"><span style="color:#333333"><span style="font-family:"varela round",sans-serif"><span style="background-color:#ffffff">我们来看看用于这个项目的pom.xml文件。</span></span></span></p> <div style="text-align:start"> <pre> <code class="language-xml"><project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>com.demo</groupId> <artifactId>springbootdemo</artifactId> <version>0.0.1-SNAPSHOT</version> <packaging>jar</packaging> <name>springbootdemo</name> <url>http://maven.apache.org</url> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.0.0.RELEASE</version> </parent> <properties> <java.version>1.8</java.version> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> </properties> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-hateoas</artifactId> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins> </build> <repositories> <repository> <id>repository.spring.release</id> <name>Spring GA Repository</name> <url>http://repo.spring.io/release</url> </repository> </repositories> </project></code></pre> </div>   <h4 style="margin-left:0px; margin-right:0px; text-align:start">API输出</h4> <strong>/employees</strong> <pre> <code class="language-json">{ "employees": [ { "employeeId": 1, "firstName": "Lokesh", "lastName": "Gupta", "email": "demo@gmail.com" }, { "employeeId": 2, "firstName": "Amit", "lastName": "Singhal", "email": "asinghal@yahoo.com" }, { "employeeId": 3, "firstName": "Kirti", "lastName": "Mishra", "email": "kmishra@gmail.com" } ] }</code></pre> <strong>/employees/{id}</strong> <pre> <code class="language-json">{ "employeeId": 1, "firstName": "Lokesh", "lastName": "Gupta", "email": "demo@gmail.com" }</code></pre> <h2 style="margin-left:0px; margin-right:0px; text-align:start"><br /> 如何将HATEOAS链接添加到REST资源</h2> <h4 style="margin-left:0px; margin-right:0px; text-align:start">步骤1)使用ResourceSupport类扩展资源模型</h4> <p style="margin-left:0px; margin-right:0px; text-align:start"><span style="color:#333333"><span style="font-family:"varela round",sans-serif"><span style="background-color:#ffffff">扩展您想添加HATEOAS链接的所有模型类 - 包括<code>org.springframework.hateoas.ResourceSupport</code>类。</span></span></span></p> <pre> <code class="language-java">@XmlRootElement(name = "employee") @XmlAccessorType(XmlAccessType.NONE) public class EmployeeVO extends ResourceSupport implements Serializable { //rest all code is same } //... @XmlRootElement (name="employees") public class EmployeeListVO extends ResourceSupport implements Serializable { //rest all code is same } //... @XmlRootElement(name="employee-report") public class EmployeeReport extends ResourceSupport implements Serializable { //rest all code is same }</code></pre>   <h4 style="margin-left:0px; margin-right:0px; text-align:start">步骤2)在REST控制器中构建和添加链接</h4> <p style="margin-left:0px; margin-right:0px; text-align:start"><span style="color:#333333"><span style="font-family:"varela round",sans-serif"><span style="background-color:#ffffff">要添加链接,您需要<code>ControllerLinkBuilder</code>和<code>Link</code>类。<code>Link</code>是要在REST资源中添加的链接的最终表示形式。而<code>ControllerLinkBuilder</code>有助于使用基于构建器模式的各种方法构建链接。</span></span></span></p> <h4 style="margin-left:0px; margin-right:0px; text-align:start">添加链接到收集资源</h4> <p style="margin-left:0px; margin-right:0px; text-align:start"><span style="color:#333333"><span style="font-family:"varela round",sans-serif"><span style="background-color:#ffffff">这里我们将添加两种类型的链接。在第一个链接中,收集资源将指向自己。在第二种类型的链接中,集合中的每个资源都将指向它的URI位置,其中可以使用完整的表示形式。另外,每个资源都会有方法链接,可以根据这些链接对个别资源执行一些操作。</span></span></span><br />  </p> <pre> <code class="language-java">@RequestMapping(value = "/employees") public EmployeeListVO getAllEmployees() { EmployeeListVO employeesList = new EmployeeListVO(); for (EmployeeVO employee : EmployeeDB.getEmployeeList()) { //Adding self link employee 'singular' resource Link link = ControllerLinkBuilder .linkTo(EmployeeRESTController.class) .slash(employee.getEmployeeId()) .withSelfRel(); //Add link to singular resource employee.add(link); //Adding method link employee 'singular' resource ResponseEntity<EmployeeReport> methodLinkBuilder = ControllerLinkBuilder .methodOn(EmployeeRESTController.class).getReportByEmployeeById(employee.getEmployeeId()); Link reportLink = ControllerLinkBuilder .linkTo(methodLinkBuilder) .withRel("employee-report"); //Add link to singular resource employee.add(reportLink); employeesList.getEmployees().add(employee); } //Adding self link employee collection resource Link selfLink = ControllerLinkBuilder .linkTo(ControllerLinkBuilder .methodOn(EmployeeRESTController.class).getAllEmployees()) .withSelfRel(); //Add link to collection resource employeesList.add(selfLink); return employeesList; }</code></pre> 输出: <pre> <code class="language-json">{ "employees": [ { "employeeId": 1, "firstName": "Lokesh", "lastName": "Gupta", "email": "howtodoinjava@gmail.com", "_links": { "self": { "href": "http://localhost:8080/1" }, "employee-report": { "href": "http://localhost:8080/employees/1/report" } } }, { "employeeId": 2, "firstName": "Amit", "lastName": "Singhal", "email": "asinghal@yahoo.com", "_links": { "self": { "href": "http://localhost:8080/2" }, "employee-report": { "href": "http://localhost:8080/employees/2/report" } } }, { "employeeId": 3, "firstName": "Kirti", "lastName": "Mishra", "email": "kmishra@gmail.com", "_links": { "self": { "href": "http://localhost:8080/3" }, "employee-report": { "href": "http://localhost:8080/employees/3/report" } } } ], "_links": { "self": { "href": "http://localhost:8080/employees" } } }</code></pre>   <h4 style="margin-left:0px; margin-right:0px; text-align:start">添加链接到单个资源</h4> <p style="margin-left:0px; margin-right:0px; text-align:start"><span style="color:#333333"><span style="font-family:"varela round",sans-serif"><span style="background-color:#ffffff">添加单数资源的链接与我们在前面部分看到的完全相同。单数资源表示通常具有更多信息/字段以及附加链接。</span></span></span></p> <pre> <code class="language-java">@RequestMapping(value = "/employees/{id}") public ResponseEntity<EmployeeVO> getEmployeeById (@PathVariable("id") int id) { if (id <= 3) { EmployeeVO employee = EmployeeDB.getEmployeeList().get(id-1); //Self link Link selfLink = ControllerLinkBuilder .linkTo(EmployeeRESTController.class) .slash(employee.getEmployeeId()) .withSelfRel(); //Method link Link reportLink = ControllerLinkBuilder .linkTo(ControllerLinkBuilder.methodOn(EmployeeRESTController.class) .getReportByEmployeeById(employee.getEmployeeId())) .withRel("report"); employee.add(selfLink); employee.add(reportLink); return new ResponseEntity<EmployeeVO>(employee, HttpStatus.OK); } return new ResponseEntity<EmployeeVO>(HttpStatus.NOT_FOUND); }</code></pre> 输出: <pre> <code class="language-json">{ "employeeId": 1, "firstName": "Lokesh", "lastName": "Gupta", "email": "howtodoinjava@gmail.com", "_links": { "self": { "href": "http://localhost:8080/1" }, "report": { "href": "http://localhost:8080/employees/1/report" } } }</code></pre> <h2 style="margin-left:0px; margin-right:0px; text-align:start">总结</h2> <p style="margin-left:0px; margin-right:0px; text-align:start"><span style="color:#333333"><span style="font-family:"varela round",sans-serif"><span style="background-color:#ffffff">正如你看到上面的演示,使用<strong>spring hashoas</strong>模块添加HATEOAS链接非常容易和一分钟时间搞定。这将大大增加API的可发现性和实用性。<br /> <a href="http://www.leftso.com/resource/1009.html" target="_blank" >项目源码下载</a></span></span></span></p>
  • Spring WebFlux 和Reactive MongoDB来构建Reactive Rest API

    Spring WebFlux 和Reactive MongoDB来构建Reactive Rest API,Spring 5通过引入一种名为Spring WebFlux的全新响应式框架来支持反应式编程范例。<h2>1.引言</h2> <p style="margin-left:0px; margin-right:0px; text-align:start"><span style="color:rgba(0, 0, 0, 0.87)"><span style="font-family:"Open Sans","Helvetica Neue",Helvetica,Arial,sans-serif"><span style="background-color:#ffffff">Spring 5通过引入一种名为<strong>Spring WebFlux的</strong>全新反应框架来支持响应式编程范例。</span></span></span></p> <p style="margin-left:0px; margin-right:0px; text-align:start"><span style="color:rgba(0, 0, 0, 0.87)"><span style="font-family:"Open Sans","Helvetica Neue",Helvetica,Arial,sans-serif"><span style="background-color:#ffffff">Spring WebFlux是一个自下而上的异步框架。它可以使用Servlet 3.1非阻塞IO API以及其他异步运行时环境(如netty或undertow)在Servlet容器上运行。</span></span></span></p> <p style="margin-left:0px; margin-right:0px; text-align:start"><span style="color:rgba(0, 0, 0, 0.87)"><span style="font-family:"Open Sans","Helvetica Neue",Helvetica,Arial,sans-serif"><span style="background-color:#ffffff">它可以与Spring MVC一起使用。是的,Spring MVC不会去任何地方。这是一个开发人员长期以来使用的流行的Web框架。</span></span></span></p> <p style="margin-left:0px; margin-right:0px; text-align:start"><span style="color:rgba(0, 0, 0, 0.87)"><span style="font-family:"Open Sans","Helvetica Neue",Helvetica,Arial,sans-serif"><span style="background-color:#ffffff">但是你现在可以在新的反应框架和传统的Spring MVC之间做出选择。您可以根据自己的使用情况选择使用它们中的任何一个。</span></span></span><br /> <br />  </p> <p style="margin-left:0px; margin-right:0px; text-align:start"><span style="color:rgba(0, 0, 0, 0.87)"><span style="font-family:"Open Sans","Helvetica Neue",Helvetica,Arial,sans-serif"><span style="background-color:#ffffff">Spring WebFlux使用一个名为Reactor的库作为响应支持。Reactor是<a href="https://github.com/reactive-streams/reactive-streams-jvm#reactive-streams" rel="external nofollow" style="box-sizing:border-box; color:#419be8; text-decoration:none; word-wrap:break-word" target="_blank" >Reactive Streams</a>规范的一个实现。</span></span></span></p> <p style="margin-left:0px; margin-right:0px; text-align:start"><span style="color:rgba(0, 0, 0, 0.87)"><span style="font-family:"Open Sans","Helvetica Neue",Helvetica,Arial,sans-serif"><span style="background-color:#ffffff">Reactor提供两种主要的类型,称为<code>Flux</code>和<code>Mono</code>。这两种类型都实现了<code>Publisher</code>Reactive Streams提供的接口。<code>Flux</code>用于表示0..N个元素的流,<code>Mono</code>用于表示0..1个元素的流。</span></span></span></p> <p style="margin-left:0px; margin-right:0px; text-align:start"><span style="color:rgba(0, 0, 0, 0.87)"><span style="font-family:"Open Sans","Helvetica Neue",Helvetica,Arial,sans-serif"><span style="background-color:#ffffff">虽然Spring使用Reactor作为其大部分内部API的核心依赖,但它也支持在应用程序级别使用RxJava。</span></span></span></p> <p style="margin-left:0px; margin-right:0px; text-align:start"> </p> <h2 style="margin-left:0px; margin-right:0px; text-align:start">2.Spring WebFlux支持的编程模型</h2> <p style="margin-left:0px; margin-right:0px; text-align:start"><span style="color:rgba(0, 0, 0, 0.87)"><span style="font-family:"Open Sans","Helvetica Neue",Helvetica,Arial,sans-serif"><span style="background-color:#ffffff">Spring WebFlux支持两种类型的编程模型:</span></span></span></p> <ol> <li>带有<code>@Controller</code>,<code>@RequestMapping</code>和其他注释的基于注释的传统模型,您在Spring MVC中一直使用。</li> <li>基于Java 8 lambda表达式的全新功能样式模型,用于路由和处理请求。</li> </ol> <p style="margin-left:0px; margin-right:0px; text-align:start"><span style="color:rgba(0, 0, 0, 0.87)"><span style="font-family:"Open Sans","Helvetica Neue",Helvetica,Arial,sans-serif"><span style="background-color:#ffffff">在本文中,我们将使用传统的基于注释的编程模型。我将在未来的文章中撰写功能风格模型。</span></span></span></p> <h2 style="margin-left:0px; margin-right:0px; text-align:start"><br /> 3.让我们在Spring Boot中构建一个Reactive Restful服务</h2> <p style="margin-left:0px; margin-right:0px; text-align:start"><span style="color:rgba(0, 0, 0, 0.87)"><span style="font-family:"Open Sans","Helvetica Neue",Helvetica,Arial,sans-serif"><span style="background-color:#ffffff">在本文中,我们将为迷你Twitter应用程序构建一个Restful API。该应用程序将只有一个称为的域模型<code>Tweet</code>。每个<code>Tweet</code>人都有<code>text</code>一个<code>createdAt</code>领域。</span></span></span></p> <p style="margin-left:0px; margin-right:0px; text-align:start"><span style="color:rgba(0, 0, 0, 0.87)"><span style="font-family:"Open Sans","Helvetica Neue",Helvetica,Arial,sans-serif"><span style="background-color:#ffffff">我们将使用MongoDB作为我们的数据存储以及反应型mongodb驱动程序。我们将构建用于创建,检索,更新和删除Tweet的REST API。所有的REST API都是异步的,并且会返回一个发布者。</span></span></span></p> <p style="margin-left:0px; margin-right:0px; text-align:start"><span style="color:rgba(0, 0, 0, 0.87)"><span style="font-family:"Open Sans","Helvetica Neue",Helvetica,Arial,sans-serif"><span style="background-color:#ffffff">我们还将学习如何将数据从数据库传输到客户端。</span></span></span></p> <p style="margin-left:0px; margin-right:0px; text-align:start"><span style="color:rgba(0, 0, 0, 0.87)"><span style="font-family:"Open Sans","Helvetica Neue",Helvetica,Arial,sans-serif"><span style="background-color:#ffffff">最后,我们将编写集成测试以使用Spring 5提供的新异步WebTestClient测试所有API。</span></span></span></p> <h2 style="margin-left:0px; margin-right:0px; text-align:start">4.创建项目</h2> <p style="margin-left:0px; margin-right:0px; text-align:start"><span style="color:rgba(0, 0, 0, 0.87)"><span style="font-family:"Open Sans","Helvetica Neue",Helvetica,Arial,sans-serif"><span style="background-color:#ffffff">我们使用Spring Initializr Web应用程序来生成我们的应用程序。按照以下步骤生成项目 -</span></span></span></p> <ol> <li>转到<a href="http://start.spring.io/" rel="external nofollow" style="box-sizing:border-box; color:#419be8; text-decoration:none; word-wrap:break-word" target="_blank" >http://start.spring.io</a></li> <li>选择Spring Boot版本<strong>2.x</strong></li> <li>输入工件的值作为<strong>webflux-demo</strong></li> <li>添加<strong>Reactive Web</strong>和<strong>Reactive MongoDB</strong>依赖项</li> <li>点击<strong>生成项目</strong>生成并下载项目。</li> </ol> <p style="margin-left:0px; margin-right:0px; text-align:start"><span style="color:rgba(0, 0, 0, 0.87)"><span style="font-family:"Open Sans","Helvetica Neue",Helvetica,Arial,sans-serif"><span style="background-color:#ffffff"><img srcset="" width="" size="" class="img-thumbnail" alt="spring官网生成项目" src="/resources/assist/images/blog/bc7bae06bc4c4868a7df2ed2f49828c8.png" /></span></span></span><br /> 下载项目后,将其解压缩并导入到您最喜欢的IDE中。该项目的目录结构应该如下所示 -<br /> <span style="color:rgba(0, 0, 0, 0.87)"><span style="font-family:"Open Sans","Helvetica Neue",Helvetica,Arial,sans-serif"><span style="background-color:#ffffff"><img srcset="" width="" size="" class="img-thumbnail" alt="导入spring 官网生成的maven项目" src="/resources/assist/images/blog/94b74bcde26549c2a0f52a246f54b2e5.png" /></span></span></span></p> <h2 style="margin-left:0px; margin-right:0px; text-align:start">配置MongoDB</h2> <p style="margin-left:0px; margin-right:0px; text-align:start"><span style="color:rgba(0, 0, 0, 0.87)"><span style="font-family:"Open Sans","Helvetica Neue",Helvetica,Arial,sans-serif"><span style="background-color:#ffffff">您可以通过简单地将以下属性添加到<code>application.properties</code>文件来配置MongoDB -</span></span></span></p> <pre> <code class="language-html">spring.data.mongodb.uri=mongodb://localhost:27017/webflux_demo</code></pre> <p style="margin-left:0px; margin-right:0px; text-align:start"><span style="color:rgba(0, 0, 0, 0.87)"><span style="font-family:"Open Sans","Helvetica Neue",Helvetica,Arial,sans-serif"><span style="background-color:#ffffff">Spring Boot将在启动时读取此配置并自动配置数据源。</span></span></span></p> <h2 style="margin-left:0px; margin-right:0px; text-align:start">创建领域模型</h2> <p style="margin-left:0px; margin-right:0px; text-align:start"><span style="color:rgba(0, 0, 0, 0.87)"><span style="font-family:"Open Sans","Helvetica Neue",Helvetica,Arial,sans-serif"><span style="background-color:#ffffff">让我们创建我们的领域模型 - <code>Tweet</code>。创建一个名为<code>model</code>inside <code>com.example.webfluxdemo</code>package 的新包,然后创建一个名为<code>Tweet.java</code>以下内容的文件-</span></span></span></p> <pre> <code class="language-java">import org.springframework.data.annotation.Id; import org.springframework.data.mongodb.core.mapping.Document; import javax.validation.constraints.NotBlank; import javax.validation.constraints.NotNull; import javax.validation.constraints.Size; import java.util.Date; @Document(collection = "tweets") public class Tweet { @Id private String id; @NotBlank @Size(max = 140) private String text; @NotNull private Date createdAt = new Date(); public Tweet() { } public Tweet(String text) { this.id = id; this.text = text; } public String getId() { return id; } public void setId(String id) { this.id = id; } public String getText() { return text; } public void setText(String text) { this.text = text; } public Date getCreatedAt() { return createdAt; } public void setCreatedAt(Date createdAt) { this.createdAt = createdAt; } }</code></pre> <p style="margin-left:0px; margin-right:0px; text-align:start">够简单!Tweet模型包含一个<code>text</code>和一个<code>createdAt</code>字段。该<code>text</code>字段用注释<code>@NotBlank</code>和<code>@Size</code>注释确保它不是空白并且最多有140个字符。<br /> <br />  </p> <h2 style="margin-left:0px; margin-right:0px; text-align:start">5.创建存储库</h2> <p style="margin-left:0px; margin-right:0px; text-align:start"><span style="color:rgba(0, 0, 0, 0.87)"><span style="font-family:"Open Sans","Helvetica Neue",Helvetica,Arial,sans-serif"><span style="background-color:#ffffff">接下来,我们将创建将用于访问MongoDB数据库的数据访问层。创建一个名为<code>repository</code>inside 的新包<code>com.example.webfluxdemo</code>,然后<code>TweetRepository.java</code>使用以下内容创建一个新文件-</span></span></span></p> <pre> <code class="language-java">import com.example.webfluxdemo.model.Tweet; import org.springframework.data.mongodb.repository.ReactiveMongoRepository; import org.springframework.stereotype.Repository; @Repository public interface TweetRepository extends ReactiveMongoRepository<Tweet, String> { }</code></pre> <p style="margin-left:0px; margin-right:0px; text-align:start"><span style="color:rgba(0, 0, 0, 0.87)"><span style="font-family:"Open Sans","Helvetica Neue",Helvetica,Arial,sans-serif"><span style="background-color:#ffffff">该<code>TweetRepository</code>接口扩展<a href="https://docs.spring.io/spring-data/mongodb/docs/2.0.0.RC2/api/org/springframework/data/mongodb/repository/ReactiveMongoRepository.html" rel="external nofollow" style="box-sizing:border-box; color:#419be8; text-decoration:none; word-wrap:break-word" target="_blank" ><code>ReactiveMongoRepository</code></a>了文档中的各种CRUD方法。</span></span></span></p> <p style="margin-left:0px; margin-right:0px; text-align:start"><span style="color:rgba(0, 0, 0, 0.87)"><span style="font-family:"Open Sans","Helvetica Neue",Helvetica,Arial,sans-serif"><span style="background-color:#ffffff">Spring Boot自动插入在<a href="https://docs.spring.io/spring-data/mongodb/docs/2.0.0.RC2/api/org/springframework/data/mongodb/repository/support/SimpleReactiveMongoRepository.html" rel="external nofollow" style="box-sizing:border-box; color:#419be8; text-decoration:none; word-wrap:break-word" target="_blank" ><code>SimpleReactiveMongoRepository</code></a>运行时调用的此接口的实现。</span></span></span></p> <p style="margin-left:0px; margin-right:0px; text-align:start"><span style="color:rgba(0, 0, 0, 0.87)"><span style="font-family:"Open Sans","Helvetica Neue",Helvetica,Arial,sans-serif"><span style="background-color:#ffffff">因此,您无需编写任何代码就可以轻松获取文档上的所有CRUD方法。以下是一些可用的方法<code>SimpleReactiveMongoRepository</code>-</span></span></span></p> <pre> <code class="language-java">reactor.core.publisher.Flux<T> findAll(); reactor.core.publisher.Mono<T> findById(ID id); <S extends T> reactor.core.publisher.Mono<S> save(S entity); reactor.core.publisher.Mono<Void> delete(T entity);</code></pre> <p style="margin-left:0px; margin-right:0px; text-align:start"><span style="color:rgba(0, 0, 0, 0.87)"><span style="font-family:"Open Sans","Helvetica Neue",Helvetica,Arial,sans-serif"><span style="background-color:#ffffff">请注意,所有方法都是异步的,并以a <code>Flux</code>或<code>Mono</code>类型的形式返回发布者。</span></span></span></p> <h2 style="margin-left:0px; margin-right:0px; text-align:start">创建控制器端点</h2> <p style="margin-left:0px; margin-right:0px; text-align:start"><span style="color:rgba(0, 0, 0, 0.87)"><span style="font-family:"Open Sans","Helvetica Neue",Helvetica,Arial,sans-serif"><span style="background-color:#ffffff">最后,让我们编写将暴露给客户端的API。创建一个名为<code>controller</code>inside 的新包<code>com.example.webfluxdemo</code>,然后<code>TweetController.java</code>使用以下内容创建一个新文件-</span></span></span><br />  </p> <pre> <code class="language-java">import com.example.webfluxdemo.model.Tweet; import com.example.webfluxdemo.repository.TweetRepository; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.HttpStatus; import org.springframework.http.MediaType; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.*; import reactor.core.publisher.Flux; import reactor.core.publisher.Mono; import javax.validation.Valid; @RestController public class TweetController { @Autowired private TweetRepository tweetRepository; @GetMapping("/tweets") public Flux<Tweet> getAllTweets() { return tweetRepository.findAll(); } @PostMapping("/tweets") public Mono<Tweet> createTweets(@Valid @RequestBody Tweet tweet) { return tweetRepository.save(tweet); } @GetMapping("/tweets/{id}") public Mono<ResponseEntity<Tweet>> getTweetById(@PathVariable(value = "id") String tweetId) { return tweetRepository.findById(tweetId) .map(savedTweet -> ResponseEntity.ok(savedTweet)) .defaultIfEmpty(ResponseEntity.notFound().build()); } @PutMapping("/tweets/{id}") public Mono<ResponseEntity<Tweet>> updateTweet(@PathVariable(value = "id") String tweetId, @Valid @RequestBody Tweet tweet) { return tweetRepository.findById(tweetId) .flatMap(existingTweet -> { existingTweet.setText(tweet.getText()); return tweetRepository.save(existingTweet); }) .map(updatedTweet -> new ResponseEntity<>(updatedTweet, HttpStatus.OK)) .defaultIfEmpty(new ResponseEntity<>(HttpStatus.NOT_FOUND)); } @DeleteMapping("/tweets/{id}") public Mono<ResponseEntity<Void>> deleteTweet(@PathVariable(value = "id") String tweetId) { return tweetRepository.findById(tweetId) .flatMap(existingTweet -> tweetRepository.delete(existingTweet) .then(Mono.just(new ResponseEntity<Void>(HttpStatus.OK))) ) .defaultIfEmpty(new ResponseEntity<>(HttpStatus.NOT_FOUND)); } // Tweets are Sent to the client as Server Sent Events @GetMapping(value = "/stream/tweets", produces = MediaType.TEXT_EVENT_STREAM_VALUE) public Flux<Tweet> streamAllTweets() { return tweetRepository.findAll(); } }</code></pre> <p style="margin-left:0px; margin-right:0px; text-align:start">所有的控制器端点都以Flux或Mono的形式返回一个Publisher。我们将内容类型设置为的最后一个端点非常有趣<code>text/event-stream</code>。它以<strong><a href="https://en.wikipedia.org/wiki/Server-sent_events" rel="external nofollow" style="box-sizing:border-box; color:#419be8; text-decoration:none; word-wrap:break-word" target="_blank" >服务器发送事件</a></strong>的形式将推文<strong><a href="https://en.wikipedia.org/wiki/Server-sent_events" rel="external nofollow" style="box-sizing:border-box; color:#419be8; text-decoration:none; word-wrap:break-word" target="_blank" >发送</a></strong>到像这样的浏览器 -</p> <pre> <code class="language-json">data: {"id":"59ba5389d2b2a85ed4ebdafa","text":"tweet1","createdAt":1505383305602} data: {"id":"59ba5587d2b2a85f93b8ece7","text":"tweet2","createdAt":1505383814847}</code></pre> <p style="margin-left:0px; margin-right:0px; text-align:start">现在我们正在讨论事件流,您可能会问以下端点是否也返回一个Stream?</p> <pre> <code class="language-java">@GetMapping("/tweets") public Flux<Tweet> getAllTweets() { return tweetRepository.findAll(); }</code></pre> <p style="margin-left:0px; margin-right:0px; text-align:start"><span style="color:rgba(0, 0, 0, 0.87)"><span style="font-family:"Open Sans","Helvetica Neue",Helvetica,Arial,sans-serif"><span style="background-color:#ffffff">答案是肯定的。<code>Flux<Tweet></code>代表推文流。但是,默认情况下,它将生成一个JSON数组,因为如果将单个JSON对象流发送给浏览器,那么它将不会是一个有效的JSON文档。除了使用Server-Sent-Events或WebSocket之外,浏览器客户端无法使用流。</span></span></span></p> <p style="margin-left:0px; margin-right:0px; text-align:start"><span style="color:rgba(0, 0, 0, 0.87)"><span style="font-family:"Open Sans","Helvetica Neue",Helvetica,Arial,sans-serif"><span style="background-color:#ffffff">但是,非浏览器客户端可以通过设置<code>Accept</code>标头来请求JSON流<code>application/stream+json</code>,并且响应将是类似于Server-Sent-Events的JSON流,但不需要额外的格式:</span></span></span></p> <pre> <code class="language-json">{"id":"59ba5389d2b2a85ed4ebdafa","text":"tweet1","createdAt":1505383305602} {"id":"59ba5587d2b2a85f93b8ece7","text":"tweet2","createdAt":1505383814847}</code></pre> <h2 style="margin-left:0px; margin-right:0px; text-align:start">使用WebTestClient进行集成测试</h2> <p style="margin-left:0px; margin-right:0px; text-align:start"><span style="color:rgba(0, 0, 0, 0.87)"><span style="font-family:"Open Sans","Helvetica Neue",Helvetica,Arial,sans-serif"><span style="background-color:#ffffff">Spring 5还提供了一个异步和被动的http客户端,<code>WebClient</code>用于处理异步和流式API。这是一个被动的选择<code>RestTemplate</code>。</span></span></span></p> <p style="margin-left:0px; margin-right:0px; text-align:start"><span style="color:rgba(0, 0, 0, 0.87)"><span style="font-family:"Open Sans","Helvetica Neue",Helvetica,Arial,sans-serif"><span style="background-color:#ffffff">此外,你还可以得到一个<code>WebTestClient</code>写作集成测试。测试客户端可以运行在实时服务器上,也可以用于模拟请求和响应。</span></span></span></p> <p style="margin-left:0px; margin-right:0px; text-align:start"><span style="color:rgba(0, 0, 0, 0.87)"><span style="font-family:"Open Sans","Helvetica Neue",Helvetica,Arial,sans-serif"><span style="background-color:#ffffff">我们将使用WebTestClient为我们的REST API编写集成测试。打开<code>WebfluxDemoApplicationTests.java</code>文件并将以下测试添加到它 -</span></span></span></p> <pre> <code class="language-java">import com.example.webfluxdemo.model.Tweet; import com.example.webfluxdemo.repository.TweetRepository; import org.assertj.core.api.Assertions; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.http.MediaType; import org.springframework.test.context.junit4.SpringRunner; import org.springframework.test.web.reactive.server.WebTestClient; import reactor.core.publisher.Mono; import java.util.Collections; @RunWith(SpringRunner.class) @SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) public class WebfluxDemoApplicationTests { @Autowired private WebTestClient webTestClient; @Autowired TweetRepository tweetRepository; @Test public void testCreateTweet() { Tweet tweet = new Tweet("This is a Test Tweet"); webTestClient.post().uri("/tweets") .contentType(MediaType.APPLICATION_JSON_UTF8) .accept(MediaType.APPLICATION_JSON_UTF8) .body(Mono.just(tweet), Tweet.class) .exchange() .expectStatus().isOk() .expectHeader().contentType(MediaType.APPLICATION_JSON_UTF8) .expectBody() .jsonPath("$.id").isNotEmpty() .jsonPath("$.text").isEqualTo("This is a Test Tweet"); } @Test public void testGetAllTweets() { webTestClient.get().uri("/tweets") .accept(MediaType.APPLICATION_JSON_UTF8) .exchange() .expectStatus().isOk() .expectHeader().contentType(MediaType.APPLICATION_JSON_UTF8) .expectBodyList(Tweet.class); } @Test public void testGetSingleTweet() { Tweet tweet = tweetRepository.save(new Tweet("Hello, World!")).block(); webTestClient.get() .uri("/tweets/{id}", Collections.singletonMap("id", tweet.getId())) .exchange() .expectStatus().isOk() .expectBody() .consumeWith(response -> Assertions.assertThat(response.getResponseBody()).isNotNull()); } @Test public void testUpdateTweet() { Tweet tweet = tweetRepository.save(new Tweet("Initial Tweet")).block(); Tweet newTweetData = new Tweet("Updated Tweet"); webTestClient.put() .uri("/tweets/{id}", Collections.singletonMap("id", tweet.getId())) .contentType(MediaType.APPLICATION_JSON_UTF8) .accept(MediaType.APPLICATION_JSON_UTF8) .body(Mono.just(newTweetData), Tweet.class) .exchange() .expectStatus().isOk() .expectHeader().contentType(MediaType.APPLICATION_JSON_UTF8) .expectBody() .jsonPath("$.text").isEqualTo("Updated Tweet"); } @Test public void testDeleteTweet() { Tweet tweet = tweetRepository.save(new Tweet("To be deleted")).block(); webTestClient.delete() .uri("/tweets/{id}", Collections.singletonMap("id", tweet.getId())) .exchange() .expectStatus().isOk(); } }</code></pre> <p style="margin-left:0px; margin-right:0px; text-align:start">在上面的例子中,我为所有的CRUD API编写了测试。您可以通过转到项目的根目录并键入来运行测试<code>mvn test</code>。<br />  </p> <h2 style="margin-left:0px; margin-right:0px; text-align:start">6.总结</h2> <p style="margin-left:0px; margin-right:0px; text-align:start"><span style="color:rgba(0, 0, 0, 0.87)"><span style="font-family:"Open Sans","Helvetica Neue",Helvetica,Arial,sans-serif"><span style="background-color:#ffffff">在本文中,我们学习了使用Spring进行反应式编程的基础知识,并使用Spring WebFlux框架提供的反应式支持构建了一个简单的Restful服务。我们还使用WebTestClient测试了所有Rest API。</span></span></span></p> <blockquote> <p style="margin-left:40px; margin-right:0px; text-align:start"><span style="color:rgba(0, 0, 0, 0.87)"><span style="font-family:"Open Sans","Helvetica Neue",Helvetica,Arial,sans-serif"><span style="background-color:#ffffff">提示:<a href="http://www.leftso.com/resource/1012.html" target="_blank" >项目源码下载</a></span></span></span></p> </blockquote>
  • Spring Boot Security Swagger2整合生成安全的在线REST API文档 SpringMVC也可参考

    创建REST API时,良好的文档是有帮助的。而且,API中的每一个变化都应该在参考文档中同时描述。手动完成这是一个乏味的操作,因此这个过程的自动化是不可避免的。<h2 style="margin-left:0px; margin-right:0px; text-align:start"><strong>1.概述</strong></h2> <p style="margin-left:0px; margin-right:0px; text-align:start"><span style="color:#535353"><span style="font-family:raleway"><span style="background-color:#ffffff">创建REST API时,良好的文档是有帮助的。</span></span></span><span style="color:#535353"><span style="font-family:raleway"><span style="background-color:#ffffff">而且,API中的每一个变化都应该在参考文档中同时描述。手动完成这是一个乏味的操作,因此这个过程的自动化是不可避免的。</span></span></span></p> <p style="margin-left:0px; margin-right:0px; text-align:start"><span style="color:#535353"><span style="font-family:raleway"><span style="background-color:#ffffff">在本教程中,我们将看看  <strong>Swagger 2的Spring Boot REST Web服务。</strong>对于本文,我们将使用Swagger 2规范的  <strong>Springfox </strong>实现。如果您对Swagger不熟悉,则应 在继续阅读本文之前访问<a href="http://swagger.io/" rel="external nofollow" style="box-sizing:border-box; color:#63b175; text-decoration:none" target="_blank">swagger官网</a>以了解更多信息。</span></span></span><br />  </p> <h2 style="margin-left:0px; margin-right:0px; text-align:start"><strong>2.项目说明</strong></h2> <p style="margin-left:0px; margin-right:0px; text-align:start"><span style="color:#535353"><span style="font-family:raleway"><span style="background-color:#ffffff">我们在示例中使用的REST服务的创建不在本文的讨论范围之内。如果您已经有合适的项目,请使用它。如果没有,下面的链接是一个很好的开始:</span></span></span></p> <ul style="list-style-type:disc"> <li>使用Spring 4和Java Config文章构建REST API</li> <li>构建一个RESTful Web服务</li> </ul> <p style="margin-left:0px; margin-right:0px; text-align:start"><strong>提示:创建Spring Boot REST项目可以参考之前写的<a rel="" target="_blank"href="http://www.leftso.com/blog/223.html" rel="" target="_blank">Swagger2入门篇</a></strong></p> <h2 style="margin-left:0px; margin-right:0px; text-align:start"><br /> <strong>3.添加Maven依赖项</strong></h2> 这里我们只说需要额外添加的maven依赖。Spring Boot REST项目本身的依赖决定于您自身的项目,暂时不在讨论之内。上一篇我们使用的swagger 版本为2.6.2这里我们将会使用比较新的2.7.0 <pre> <code class="language-xml"><dependency> <groupId>io.springfox</groupId> <artifactId>springfox-swagger2</artifactId> <version>2.7.0</version> </dependency></code></pre> <blockquote> <p>提示:现在已经有2.8.0版本。目前存在一些无法汉化的问题。暂不推荐正式项目升级。学习研究即可。</p> </blockquote> <h2 style="margin-left:0px; margin-right:0px; text-align:start"><strong>4.将Swagger 2集成到项目中</strong></h2> <h3 style="margin-left:0px; margin-right:0px; text-align:start"><strong>4.1。Java配置</strong></h3> <p style="margin-left:0px; margin-right:0px; text-align:start"><span style="color:#535353"><span style="font-family:raleway"><span style="background-color:#ffffff">Swagger的配置主要围绕着  <em><strong>Docket</strong></em> bean。</span></span></span></p> <pre> <code class="language-java">@Configuration @EnableSwagger2 public class SwaggerConfig { @Bean public Docket api() { return new Docket(DocumentationType.SWAGGER_2) .select() .apis(RequestHandlerSelectors.any()) .paths(PathSelectors.any()) .build(); } }</code></pre> <p style="margin-left:0px; margin-right:0px; text-align:start"><span style="color:#535353"><span style="font-family:raleway"><span style="background-color:#ffffff">Swagger 2通过<strong>@ EnableSwagger2</strong><em> </em>批注启用 。</span></span></span><br /> <br /> <span style="color:#535353"><span style="font-family:raleway"><span style="background-color:#ffffff">在定义了<em>Docket</em> bean 之后,其  <strong>select()</strong>  方法返回一个<strong>ApiSelectorBuilder</strong>实例,该实例提供了一种控制Swagger公开的端点的方法。</span></span></span></p> <p style="margin-left:0px; margin-right:0px; text-align:start"><span style="color:#535353"><span style="font-family:raleway"><span style="background-color:#ffffff">可以使用<strong>RequestHandlerSelectors</strong>和<strong>PathSelectors</strong>来配置<em>RequestHandler</em>选择的谓词。使用  <em>任何()</em>  都可以通过Swagger为整个API提供文档。</span></span></span></p> <p style="margin-left:0px; margin-right:0px; text-align:start"><span style="color:#535353"><span style="font-family:raleway"><span style="background-color:#ffffff">这种配置足以将Swagger 2集成到现有的Spring Boot项目中。对于其他Spring项目,需要进行一些额外的调整。</span></span></span></p> <h3 style="margin-left:0px; margin-right:0px; text-align:start"><strong>4.2。项目不是Spring Boot的配置</strong></h3> <p style="margin-left:0px; margin-right:0px; text-align:start"><span style="color:#535353"><span style="font-family:raleway"><span style="background-color:#ffffff">如果没有Spring Boot,你无法自动配置资源处理程序。Swagger UI添加了一组资源,您必须将其配置为扩展<em>WebMvcConfigurerAdapter</em>的类的一部分  <em>,</em>  并使用<em>@EnableWebMvc进行</em>注释  <em>。</em></span></span></span></p> <pre> <code class="language-java">@Override public void addResourceHandlers(ResourceHandlerRegistry registry) { registry.addResourceHandler("swagger-ui.html") .addResourceLocations("classpath:/META-INF/resources/"); registry.addResourceHandler("/webjars/**") .addResourceLocations("classpath:/META-INF/resources/webjars/"); }</code></pre>   <h3 style="margin-left:0px; margin-right:0px; text-align:start"><strong>4.3。验证Springfox是否正常工作</strong></h3> <p style="margin-left:0px; margin-right:0px; text-align:start"><span style="color:#535353"><span style="font-family:raleway"><span style="background-color:#ffffff">要验证Springfox是否正常工作,可以在浏览器中访问以下URL:</span></span></span></p> <pre> <code class="language-html">​​​​​​​http://localhost:8080/spring-security-rest/api/v2/api-docs</code></pre> <p style="margin-left:0px; margin-right:0px; text-align:start"><span style="color:#535353"><span style="font-family:raleway"><span style="background-color:#ffffff">结果是带有大量键值对的JSON响应,这不是非常容易理解的。幸运的是,Swagger 为此提供了<strong>Swagger UI</strong>。</span></span></span></p> <h2 style="margin-left:0px; margin-right:0px; text-align:start"><strong>5. Swagger UI</strong></h2> <p style="margin-left:0px; margin-right:0px; text-align:start"><span style="color:#535353"><span style="font-family:raleway"><span style="background-color:#ffffff">Swagger UI是一个内置的解决方案,使得用户可以更轻松地与Swagger生成的API文档进行交互。</span></span></span></p> <h3 style="margin-left:0px; margin-right:0px; text-align:start"><strong>5.1。启用S​​pringfox的Swagger UI</strong></h3> <p style="margin-left:0px; margin-right:0px; text-align:start"><span style="color:#535353"><span style="font-family:raleway"><span style="background-color:#ffffff">要使用Swagger UI,需要另外一个Maven依赖项:</span></span></span></p> <pre> <code class="language-xml"><dependency> <groupId>io.springfox</groupId> <artifactId>springfox-swagger-ui</artifactId> <version>2.7.0</version> </dependency></code></pre> 现在,您可以通过访问<em>http://localhost:8080/your-app-root/swagger-ui.html</em><span style="color:#535353; font-family:raleway">在浏览器中对其进行测试</span> <p style="margin-left:0px; margin-right:0px; text-align:start"><span style="color:#535353"><span style="font-family:raleway"><span style="background-color:#ffffff"><strong>在我们的例子中,顺便说一下,确切的URL是</strong></span></span></span><em>http://localhost:8080/spring-security-rest/api/swagger-ui.html</em></p> 结果应该如下所示:<br /> <img alt="swagger2 UI结果展示" class="img-thumbnail" src="/resources/assist/images/blog/87611d78ac3145fbacc0fbe040cd8b8b.png" /><br />   <h3 style="margin-left:0px; margin-right:0px; text-align:start"><strong>5.2。学习Swagger文档</strong></h3> <p style="margin-left:0px; margin-right:0px; text-align:start"><span style="color:#535353"><span style="font-family:raleway"><span style="background-color:#ffffff">Swagger的回复中<strong>列出了</strong>您的应用程序中定义<strong>的所有控制器</strong>。点击其中的任何一个将列出有效的HTTP方法(<em>DELETE</em>,<em>GET</em>,<em>HEAD</em>,<em>OPTIONS</em>,<em>PATCH</em>,<em>POST</em>,<em>PUT</em>)。</span></span></span></p> <p style="margin-left:0px; margin-right:0px; text-align:start"><span style="color:#535353"><span style="font-family:raleway"><span style="background-color:#ffffff">展开每种方法都会提供其他有用的数据,例如响应状态,内容类型和参数列表。也可以使用UI来尝试每种方法。</span></span></span></p> <p style="margin-left:0px; margin-right:0px; text-align:start"><span style="color:#535353"><span style="font-family:raleway"><span style="background-color:#ffffff">Swagger与您的代码库同步的能力至关重要。为了演示这一点,你可以添加一个新的控制器到你的应用程序。</span></span></span></p> <pre> <code class="language-java">@RestController public class CustomController { @RequestMapping(value = "/custom", method = RequestMethod.POST) public String custom() { return "custom"; } }</code></pre> 现在,如果刷新Swagger文档,您将在控制器列表中看到<strong>自定义</strong>控制器。如您所知,Swagger的回复中只显示一种方法(<em>POST</em>)。 <h2 style="margin-left:0px; margin-right:0px; text-align:start"><strong>6.高级配置</strong></h2> <p style="margin-left:0px; margin-right:0px; text-align:start"><span style="color:#535353"><span style="font-family:raleway"><span style="background-color:#ffffff">应用程序的<em>Docket</em> bean可以配置为让您更多地控制API文档生成过程。</span></span></span></p> <h3 style="margin-left:0px; margin-right:0px; text-align:start"><strong>6.1。过滤Swagger响应的API</strong></h3> <p style="margin-left:0px; margin-right:0px; text-align:start"><span style="color:#535353"><span style="font-family:raleway"><span style="background-color:#ffffff">公开整个API的文档并不总是可取的。您可以通过将参数传递给<em>Docket</em>类的<em><strong>apis()</strong></em>和<em><strong>paths()</strong></em>方法来限制Swagger的响应  。</span></span></span></p> <p style="margin-left:0px; margin-right:0px; text-align:start"><span style="color:#535353"><span style="font-family:raleway"><span style="background-color:#ffffff">如上所示,<em>RequestHandlerSelectors</em>允许使用  <em>any</em>或<em>none</em>谓词,但也可用于根据基本包,类注释和方法注释过滤API。</span></span></span></p> <p style="margin-left:0px; margin-right:0px; text-align:start"><span style="color:#535353"><span style="font-family:raleway"><span style="background-color:#ffffff"><em><strong>PathSelectors</strong></em>提供了额外的过滤功能,并使用谓词来扫描应用程序的请求路径。你可以使用 <em> any()</em>,<em>none(),  </em><em>regex()</em>或 <em> ant()</em>。</span></span></span></p> <p style="margin-left:0px; margin-right:0px; text-align:start"><span style="color:#535353"><span style="font-family:raleway"><span style="background-color:#ffffff">在下面的示例中,我们将指示Swagger使用<em>ant()</em>  谓词仅包含特定包中的控制器,并使用特定的路径。</span></span></span><br />  </p> <pre> <code class="language-java">@Bean public Docket api() { return new Docket(DocumentationType.SWAGGER_2) .select() .apis(RequestHandlerSelectors.basePackage("org.baeldung.web.controller")) .paths(PathSelectors.ant("/foos/*")) .build(); }</code></pre> <h3 style="margin-left:0px; margin-right:0px; text-align:start"><strong>6.2。自定义信息</strong></h3> <p style="margin-left:0px; margin-right:0px; text-align:start"><span style="color:#535353"><span style="font-family:raleway"><span style="background-color:#ffffff">Swagger还在您的自定义响应中提供了一些默认值,例如“Api文档”,“由联系人电子邮件创建”,“Apache 2.0”。</span></span></span></p> <p style="margin-left:0px; margin-right:0px; text-align:start"><span style="color:#535353"><span style="font-family:raleway"><span style="background-color:#ffffff">要更改这些值,可以使用  <strong><em>apiInfo(ApiInfo apiInfo) </em></strong>方法。包含有关API的自定义信息的  <strong><em>ApiInfo</em></strong>类。</span></span></span></p> <pre> <code class="language-java">@Bean public Docket api() { return new Docket(DocumentationType.SWAGGER_2) .select() .apis(RequestHandlerSelectors.basePackage("com.example.controller")) .paths(PathSelectors.ant("/foos/*")) .build() .apiInfo(apiInfo()); } private ApiInfo apiInfo() { return new ApiInfo( "My REST API", "Some custom description of API.", "API TOS", "Terms of service", new Contact("John Doe", "www.example.com", "myeaddress@company.com"), "License of API", "API license URL", Collections.emptyList()); }</code></pre> <h3 style="margin-left:0px; margin-right:0px; text-align:start"><strong>6.3。自定义方法响应消息</strong></h3> <p style="margin-left:0px; margin-right:0px; text-align:start"><span style="color:#535353"><span style="font-family:raleway"><span style="background-color:#ffffff">Swagger允许通过  <em>Docket</em>的  <strong><em>globalResponseMessage()</em></strong>方法<strong>全局覆盖HTTP方法的响应消息</strong>。首先,您必须指示Swagger不要使用默认响应消息。<strong> </strong></span></span></span></p> <p style="margin-left:0px; margin-right:0px; text-align:start"><span style="color:#535353"><span style="font-family:raleway"><span style="background-color:#ffffff">假设您希望覆盖  所有<em>GET</em>方法的<strong>500</strong>和<strong>403</strong>响应消息。为了达到这个目的,一些代码必须被添加到<em>Docket</em>的初始化块(为了清楚起见,原始冗余代码被排除):</span></span></span></p> <pre> <code class="language-java">.useDefaultResponseMessages(false) .globalResponseMessage(RequestMethod.GET, newArrayList(new ResponseMessageBuilder() .code(500) .message("500 message") .responseModel(new ModelRef("Error")) .build(), new ResponseMessageBuilder() .code(403) .message("Forbidden!") .build()));</code></pre> <img alt="swagger UI 2" class="img-thumbnail" src="/resources/assist/images/blog/62c77efb299f44e3a2c90d0326531f2f.png" /> <h2 style="margin-left:0px; margin-right:0px; text-align:start"><strong>7.具有OAuth安全API的Swagger UI</strong></h2> <p style="margin-left:0px; margin-right:0px; text-align:start"><span style="color:#535353"><span style="font-family:raleway"><span style="background-color:#ffffff">Swagger UI提供了许多非常有用的功能 - 目前为止我们已经介绍了这些功能。但是如果我们的API是安全的并且不可访问的话,我们不能真正使用其中的大部分。</span></span></span></p> <p style="margin-left:0px; margin-right:0px; text-align:start"><span style="color:#535353"><span style="font-family:raleway"><span style="background-color:#ffffff">让我们看看我们如何允许Swagger访问OAuth安全的API--在本例中使用授权代码授权类型。</span></span></span></p> <p style="margin-left:0px; margin-right:0px; text-align:start"><span style="color:#535353"><span style="font-family:raleway"><span style="background-color:#ffffff">我们将使用<em>SecurityScheme</em>和<em>SecurityContext</em>支持配置Swagger以访问我们的安全API  :</span></span></span></p> <pre> <code class="language-java">@Bean public Docket api() { return new Docket(DocumentationType.SWAGGER_2).select() .apis(RequestHandlerSelectors.any()) .paths(PathSelectors.any()) .build() .securitySchemes(Arrays.asList(securityScheme())) .securityContexts(Arrays.asList(securityContext())); }</code></pre> <h3 style="margin-left:0px; margin-right:0px; text-align:start"><strong>7.1。安全配置</strong></h3> <p style="margin-left:0px; margin-right:0px; text-align:start"><span style="color:#535353"><span style="font-family:raleway"><span style="background-color:#ffffff">我们将在Swagger配置中定义一个  <em>SecurityConfiguration</em> bean - 并设置一些默认值:</span></span></span></p> <pre> <code class="language-java">@Bean public SecurityConfiguration security() { return SecurityConfigurationBuilder.builder() .clientId(CLIENT_ID) .clientSecret(CLIENT_SECRET) .scopeSeparator(" ") .useBasicAuthenticationWithAccessCodeGrant(true) .build(); }</code></pre>   <h3 style="margin-left:0px; margin-right:0px; text-align:start"><strong>7.2。<em>SecurityScheme</em></strong></h3> <p style="margin-left:0px; margin-right:0px; text-align:start"><span style="color:#535353"><span style="font-family:raleway"><span style="background-color:#ffffff">接下来,我们将定义我们的<em>SecurityScheme</em> ; 这用于描述我们的API如何保护(基本认证,OAuth2,...)。</span></span></span></p> <p style="margin-left:0px; margin-right:0px; text-align:start"><span style="color:#535353"><span style="font-family:raleway"><span style="background-color:#ffffff">在我们的例子中,我们将定义一个用于保护我们的资源服务器的OAuth方案:</span></span></span></p> <pre> <code class="language-java">private SecurityScheme securityScheme() { GrantType grantType = new AuthorizationCodeGrantBuilder() .tokenEndpoint(new TokenEndpoint(AUTH_SERVER + "/token", "oauthtoken")) .tokenRequestEndpoint( new TokenRequestEndpoint(AUTH_SERVER + "/authorize", CLIENT_ID, CLIENT_ID)) .build(); SecurityScheme oauth = new OAuthBuilder().name("spring_oauth") .grantTypes(Arrays.asList(grantType)) .scopes(Arrays.asList(scopes())) .build(); return oauth; }</code></pre> <p style="margin-left:0px; margin-right:0px; text-align:start"><span style="color:#535353"><span style="font-family:raleway"><span style="background-color:#ffffff">请注意,我们使用了授权码授权类型 - 我们需要为其提供令牌端点以及我们的OAuth2授权服务器的授权URL。</span></span></span></p> <p style="margin-left:0px; margin-right:0px; text-align:start"><span style="color:#535353"><span style="font-family:raleway"><span style="background-color:#ffffff">以下是我们需要定义的范围:</span></span></span></p> <pre> <code class="language-java">private AuthorizationScope[] scopes() { AuthorizationScope[] scopes = { new AuthorizationScope("read", "for read operations"), new AuthorizationScope("write", "for write operations"), new AuthorizationScope("foo", "Access foo API") }; return scopes; }</code></pre> <p style="margin-left:0px; margin-right:0px; text-align:start"><span style="color:#535353"><span style="font-family:raleway"><span style="background-color:#ffffff">这些与我们在应用中实际定义的范围同步<em>/ foos</em> API。</span></span></span></p> <h3 style="margin-left:0px; margin-right:0px; text-align:start"><strong>7.3。Security Context</strong></h3> <p style="margin-left:0px; margin-right:0px; text-align:start"><span style="color:#535353"><span style="font-family:raleway"><span style="background-color:#ffffff">最后,我们需要为我们的示例API定义一个安全上下文:</span></span></span></p> <pre> <code class="language-java">private SecurityContext securityContext() { return SecurityContext.builder() .securityReferences(Arrays.asList(new SecurityReference("spring_oauth", scopes()))) .forPaths(PathSelectors.regex("/foos.*")) .build(); }</code></pre> <p style="margin-left:0px; margin-right:0px; text-align:start"><span style="color:#535353"><span style="font-family:raleway"><span style="background-color:#ffffff">请注意,我们在此处使用的名称(参考 -  <em>spring_oauth)</em>与我们之前使用的名称在<em>SecurityScheme中</em>同步。</span></span></span></p> <h3 style="margin-left:0px; margin-right:0px; text-align:start"><strong>7.4。测试</strong></h3> <p style="margin-left:0px; margin-right:0px; text-align:start"><span style="color:#535353"><span style="font-family:raleway"><span style="background-color:#ffffff">好吧,现在我们已经准备好了所有的东西,让我们来看看我们的Swagger UI并尝试访问Foo API:</span></span></span></p> <p style="margin-left:0px; margin-right:0px; text-align:start"><span style="color:#535353"><span style="font-family:raleway"><span style="background-color:#ffffff">我们可以在本地访问Swagger UI:</span></span></span></p> <div style="text-align:start"> <div style="margin-left:0px; margin-right:0px"> <table cellspacing="0" class="table table-bordered table-hover" style="background:0px 0px !important; border-collapse:collapse; border-radius:0px !important; border-spacing:0px; border:0px !important; bottom:auto !important; box-sizing:content-box !important; float:none !important; font-family:"source code pro",consolas,"bitstream vera sans mono","courier new",Courier,monospace !important; font-size:14px !important; font-style:normal !important; font-weight:300 !important; height:auto !important; left:auto !important; line-height:1.43 !important; margin:0px !important; min-height:auto !important; outline:0px !important; overflow:visible !important; padding:0px !important; position:static !important; right:auto !important; text-align:left !important; top:auto !important; vertical-align:baseline !important; width:871px"> <tbody> <tr> <td style="width:auto !important"> <div style="text-align:left !important"><span style="color:#333333"><span style="font-family:raleway"><span style="background-color:#ffffff"><span style="background-color:#ffffff !important"><span style="font-family:"source code pro",consolas,"bitstream vera sans mono","courier new",Courier,monospace !important"><span style="font-family:"source code pro",consolas,"bitstream vera sans mono","courier new",Courier,monospace !important"><span style="color:#afafaf !important">1</span></span></span></span></span></span></span></div> </td> <td style="width:838px"> <div style="text-align:left !important"> <div style="text-align:left !important"><span style="color:#333333"><span style="font-family:raleway"><span style="background-color:#ffffff"><span style="background-color:#ffffff !important"><span style="font-family:"source code pro",consolas,"bitstream vera sans mono","courier new",Courier,monospace !important"><span style="font-family:"source code pro",consolas,"bitstream vera sans mono","courier new",Courier,monospace !important"><span style="font-family:"source code pro",consolas,"bitstream vera sans mono","courier new",Courier,monospace !important"><code>http:</code><code>//localhost</code><code>:8082</code><code>/spring-security-oauth-resource/swagger-ui</code><code>.html</code></span></span></span></span></span></span></span></div> </div> </td> </tr> </tbody> </table> </div> </div> <p style="margin-left:0px; margin-right:0px; text-align:start"><span style="color:#535353"><span style="font-family:raleway"><span style="background-color:#ffffff">由于我们的安全配置,我们可以看到现在存在一个新的“授权”按钮:</span></span></span></p> <img alt="swagger ui security" class="img-thumbnail" src="/resources/assist/images/blog/55027912a7f74e8f9c167b2fced6d4d8.png" /><br /> <br /> 当我们点击“授权”按钮时,我们可以看到以下弹出窗口 - 授权我们的Swagger UI访问安全的API:<br /> <img alt="swagger ui security 2" class="img-thumbnail" src="/resources/assist/images/blog/30d144368b694459bfcb3f386633e1fa.png" /> <p style="margin-left:0px; margin-right:0px; text-align:start"><span style="color:#535353"><span style="font-family:raleway"><span style="background-color:#ffffff">注意:</span></span></span></p> <ul style="list-style-type:disc"> <li>我们已经可以看到CLIENT_ID和CLIENT_SECRET了 - 因为我们已经预先配置了它们(但我们仍然可以更改它们)</li> <li>我们现在可以选择我们需要的范围</li> </ul> <p style="margin-left:0px; margin-right:0px; text-align:start"><span style="color:#535353"><span style="font-family:raleway"><span style="background-color:#ffffff">以下是安全API的标记方式:<br /> <img alt="swagger ui security" class="img-thumbnail" src="/resources/assist/images/blog/b1e5e9e7b818409486ad8b3e78b98583.png" /></span></span></span><br />  </p> <p style="margin-left:0px; margin-right:0px; text-align:start"><span style="color:#535353"><span style="font-family:raleway"><span style="background-color:#ffffff">现在,最后,我们可以打我们的API!</span></span></span><br /> <span style="color:#535353"><span style="font-family:raleway"><span style="background-color:#ffffff">当然,不言而喻,我们需要小心如何在外部公开Swagger UI,因为此安全配置处于活动状态。</span></span></span></p> <h2 style="margin-left:0px; margin-right:0px; text-align:start"><strong>8.总结</strong></h2> <p style="margin-left:0px; margin-right:0px; text-align:start"><span style="color:#535353"><span style="font-family:raleway"><span style="background-color:#ffffff">在本教程中,我们设置了Swagger 2来为Spring REST API生成文档。我们还探索了可视化和自定义Swagger输出的方法。最后,我们查看了Swagger的一个简单的OAuth配置。</span></span></span></p>
  • Spring boot JPA MySQL整合实现CRUD REST接口(基础篇)

    Spring boot JPA MySQL整合实现CRUD REST接口,在这篇文章中,我们将为简单的笔记应用程序构建一个Restful CRUD API。注释可以有标题和一些内容。我们将首先构建apis来创建,检索,更新和删除一个Note,然后使用POSTMAN测试它们。<h2 style="margin-left:0px; margin-right:0px; text-align:start"><span style="color:rgba(0, 0, 0, 0.87)"><span style="font-family:"Open Sans","Helvetica Neue",Helvetica,Arial,sans-serif"><span style="background-color:#ffffff">1.前言</span></span></span></h2> <p style="margin-left:0px; margin-right:0px; text-align:start"><span style="color:rgba(0, 0, 0, 0.87)"><span style="font-family:"Open Sans","Helvetica Neue",Helvetica,Arial,sans-serif"><span style="background-color:#ffffff">Spring Boot将Spring框架提升到了一个新的水平。它极大地缩短了Spring项目所需的配置和设置时间。</span></span></span></p> <p style="margin-left:0px; margin-right:0px; text-align:start"><span style="color:rgba(0, 0, 0, 0.87)"><span style="font-family:"Open Sans","Helvetica Neue",Helvetica,Arial,sans-serif"><span style="background-color:#ffffff">您可以设置几乎为零配置的项目,并开始构建对您的应用程序而言非常重要的事物。</span></span></span></p> <p style="margin-left:0px; margin-right:0px; text-align:start"><span style="color:rgba(0, 0, 0, 0.87)"><span style="font-family:"Open Sans","Helvetica Neue",Helvetica,Arial,sans-serif"><span style="background-color:#ffffff">如果你是Spring的新手,想快速入门,那么这篇博文就是为你准备的。</span></span></span></p> <p style="margin-left:0px; margin-right:0px; text-align:start"><span style="color:rgba(0, 0, 0, 0.87)"><span style="font-family:"Open Sans","Helvetica Neue",Helvetica,Arial,sans-serif"><span style="background-color:#ffffff">在这篇文章中,我们将为简单的笔记应用程序构建一个Restful CRUD API。注释可以有标题和一些内容。我们将首先构建apis来创建,检索,更新和删除一个Note,然后使用POSTMAN测试它们。</span></span></span></p> <h2 style="margin-left:0px; margin-right:0px; text-align:start">2.创建项目</h2> <p style="margin-left:0px; margin-right:0px; text-align:start"><span style="color:rgba(0, 0, 0, 0.87)"><span style="font-family:"Open Sans","Helvetica Neue",Helvetica,Arial,sans-serif"><span style="background-color:#ffffff">Spring Boot提供了一个名为<a href="http://start.spring.io/" rel="external nofollow" style="box-sizing:border-box; color:#419be8; text-decoration:none; word-wrap:break-word" target="_blank">Spring Initializer</a>的Web工具来快速引导应用程序。只需访问<a href="http://start.spring.io/" rel="external nofollow" style="box-sizing:border-box; color:#419be8; text-decoration:none; word-wrap:break-word" target="_blank">http://start.spring.io</a>并按照以下步骤生成一个新项目。</span></span></span></p> <p style="margin-left:0px; margin-right:0px; text-align:start"><span style="color:rgba(0, 0, 0, 0.87)"><span style="font-family:"Open Sans","Helvetica Neue",Helvetica,Arial,sans-serif"><span style="background-color:#ffffff"><strong>第1步</strong>:在<a href="http://start.spring.io/" rel="external nofollow" style="box-sizing:border-box; color:#419be8; text-decoration:none; word-wrap:break-word" target="_blank">http://start.spring.io</a>页面上单击<strong>切换到完整版本</strong>。</span></span></span></p> <p style="margin-left:0px; margin-right:0px; text-align:start"><span style="color:rgba(0, 0, 0, 0.87)"><span style="font-family:"Open Sans","Helvetica Neue",Helvetica,Arial,sans-serif"><span style="background-color:#ffffff"><strong>第2步</strong>:输入详细信息如下 -</span></span></span></p> <ul> <li>Group : com.example</li> <li>Artifact : easy-notes</li> <li>Name : easy-notes</li> <li>Description : Rest API for a Simple Note Taking Application</li> <li>Package Name : com.example.easynotes</li> <li>Packaging : jar (This is the default value)</li> <li>Java Version : 1.8 (Default)</li> <li>Dependencies : Web, JPA, MySQL, DevTools</li> </ul> <p style="margin-left:0px; margin-right:0px; text-align:start"><img alt="图1" class="img-thumbnail" src="/resources/assist/images/blog/d608951119e348f9b793ff4381442142.png" /><br /> <span style="color:rgba(0, 0, 0, 0.87)"><span style="font-family:"Open Sans","Helvetica Neue",Helvetica,Arial,sans-serif"><span style="background-color:#ffffff">输入完所有细节后,单击<strong>生成项目</strong>以生成并下载项目。Spring初始化程序将生成具有您输入的详细信息的项目并下载包含所有项目文件夹的zip文件。</span></span></span></p> <p style="margin-left:0px; margin-right:0px; text-align:start"><span style="color:rgba(0, 0, 0, 0.87)"><span style="font-family:"Open Sans","Helvetica Neue",Helvetica,Arial,sans-serif"><span style="background-color:#ffffff">接下来,解压下载的zip文件并将其导入到您最喜欢的IDE中。</span></span></span><br /> <br />  </p> <h2 style="margin-left:0px; margin-right:0px; text-align:start">3.查看目录结构</h2> <p style="margin-left:0px; margin-right:0px; text-align:start"><span style="color:rgba(0, 0, 0, 0.87)"><span style="font-family:"Open Sans","Helvetica Neue",Helvetica,Arial,sans-serif"><span style="background-color:#ffffff">以下是我们的笔记记录应用程序的目录结构 -</span></span></span></p> <p style="margin-left:0px; margin-right:0px; text-align:start"><span style="color:rgba(0, 0, 0, 0.87)"><span style="font-family:"Open Sans","Helvetica Neue",Helvetica,Arial,sans-serif"><span style="background-color:#ffffff"><img alt="查看目录结构" class="img-thumbnail" src="/resources/assist/images/blog/cc41763f46504dea9b627c77cad4a9db.png" /><br /> 让我们了解一些重要文件和目录的细节 -</span></span></span></p> <p style="margin-left:0px; margin-right:0px; text-align:start"><span style="color:rgba(0, 0, 0, 0.87)"><span style="font-family:"Open Sans","Helvetica Neue",Helvetica,Arial,sans-serif"><span style="background-color:#ffffff"><strong>1. EasyNotesApplication</strong></span></span></span></p> <p style="margin-left:0px; margin-right:0px; text-align:start"><span style="color:rgba(0, 0, 0, 0.87)"><span style="font-family:"Open Sans","Helvetica Neue",Helvetica,Arial,sans-serif"><span style="background-color:#ffffff">这是我们的Spring Boot应用程序的主要入口点。</span></span></span></p> <pre> <code class="language-java">package com.example.easynotes; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; @SpringBootApplication public class EasyNotesApplication { public static void main(String[] args) { SpringApplication.run(EasyNotesApplication.class, args); } }</code></pre> <p style="margin-left:0px; margin-right:0px; text-align:start"><span style="color:rgba(0, 0, 0, 0.87)"><span style="font-family:"Open Sans","Helvetica Neue",Helvetica,Arial,sans-serif"><span style="background-color:#ffffff">它包含一个简单的注释<code>@SpringBootApplication</code>,它是以下更具体的Spring注释的组合 -</span></span></span></p> <ul> <li><a href="https://docs.spring.io/spring/docs/current/javadoc-api/org/springframework/context/annotation/Configuration.html" rel="external nofollow" style="box-sizing:border-box; color:#419be8; text-decoration:none; word-wrap:break-word" target="_blank">@Configuration</a>:用<code>@Configuration</code>Annotation注解的任何类由Spring引导,并且也被认为是其他bean定义的来源。</li> <li><a href="https://docs.spring.io/spring-boot/docs/1.2.1.RELEASE/api/org/springframework/boot/autoconfigure/EnableAutoConfiguration.html" rel="external nofollow" style="box-sizing:border-box; color:#419be8; text-decoration:none; word-wrap:break-word" target="_blank">@EnableAutoConfiguration</a>:这个注解告诉Spring根据你添加到<code>pom.xml</code>文件中的依赖来自动配置你的应用程序。</li> <li>例如,如果<code>spring-data-jpa</code>在类路径中,则它会自动尝试<code>DataSource</code>通过从<code>application.properties</code>文件中读取数据库属性来配置一个。</li> <li><a href="https://docs.spring.io/spring/docs/current/javadoc-api/org/springframework/context/annotation/ComponentScan.html" rel="external nofollow" style="box-sizing:border-box; color:#419be8; text-decoration:none; word-wrap:break-word" target="_blank">@ComponentScan</a>:它告诉Spring扫描并引导当前包(com.example.easynotes)和所有子包中定义的其他组件。</li> </ul> <p style="margin-left:0px; margin-right:0px; text-align:start"><span style="color:rgba(0, 0, 0, 0.87)"><span style="font-family:"Open Sans","Helvetica Neue",Helvetica,Arial,sans-serif"><span style="background-color:#ffffff">该<code>main()</code>方法调用Spring Boot的<code>SpringApplication.run()</code>方法来启动应用程序。</span></span></span></p> <p style="margin-left:0px; margin-right:0px; text-align:start"><span style="color:rgba(0, 0, 0, 0.87)"><span style="font-family:"Open Sans","Helvetica Neue",Helvetica,Arial,sans-serif"><span style="background-color:#ffffff"><strong>2.资源/</strong></span></span></span></p> <p style="margin-left:0px; margin-right:0px; text-align:start"><span style="color:rgba(0, 0, 0, 0.87)"><span style="font-family:"Open Sans","Helvetica Neue",Helvetica,Arial,sans-serif"><span style="background-color:#ffffff">顾名思义,该目录专用于所有静态资源,模板和属性文件。</span></span></span></p> <ul style="margin-left:30px; margin-right:0px"> <li> <p style="margin-left:0px; margin-right:0px"><strong>资源/静态</strong> - 包含静态资源,如css,js和图像。</p> </li> <li> <p style="margin-left:0px; margin-right:0px"><strong>资源/模板</strong> - 包含由Spring呈现的服务器端模板。</p> </li> <li> <p style="margin-left:0px; margin-right:0px"><strong>resources / application.properties</strong> - 这个文件非常重要。它包含应用程序范围内的属性。Spring读取这个文件中定义的属性来配置你的应用程序。您可以在此文件中定义服务器的默认端口,服务器的上下文路径,数据库URL等。</p> <p style="margin-left:0px; margin-right:0px">您可以参考<a href="https://docs.spring.io/spring-boot/docs/current/reference/html/common-application-properties.html" rel="external nofollow" style="box-sizing:border-box; color:#419be8; text-decoration:none; word-wrap:break-word" target="_blank">此页面</a>来了解Spring Boot中使用的常见应用程序属性。</p> </li> </ul> <p style="margin-left:0px; margin-right:0px; text-align:start"><span style="color:rgba(0, 0, 0, 0.87)"><span style="font-family:"Open Sans","Helvetica Neue",Helvetica,Arial,sans-serif"><span style="background-color:#ffffff"><strong>3. EasyNotesApplicationTests</strong> - 在这里定义单元和集成测试。</span></span></span></p> <p style="margin-left:0px; margin-right:0px; text-align:start"><span style="color:rgba(0, 0, 0, 0.87)"><span style="font-family:"Open Sans","Helvetica Neue",Helvetica,Arial,sans-serif"><span style="background-color:#ffffff"><strong>4. pom.xml</strong> - 包含所有的项目依赖关系</span></span></span></p> <h2 style="margin-left:0px; margin-right:0px; text-align:start">4.配置MySQL数据库</h2> <p style="margin-left:0px; margin-right:0px; text-align:start"><span style="color:rgba(0, 0, 0, 0.87)"><span style="font-family:"Open Sans","Helvetica Neue",Helvetica,Arial,sans-serif"><span style="background-color:#ffffff">正如我前面指出的那样,Spring Boot 通过从文件中读取数据库配置,尝试在类路径中自动配置<code>DataSource</code>if 。<code>spring-data-jpa</code><code>application.properties</code></span></span></span></p> <p style="margin-left:0px; margin-right:0px; text-align:start"><span style="color:rgba(0, 0, 0, 0.87)"><span style="font-family:"Open Sans","Helvetica Neue",Helvetica,Arial,sans-serif"><span style="background-color:#ffffff">所以,我们只需要添加配置,而Spring Boot将负责其余部分。</span></span></span></p> <p style="margin-left:0px; margin-right:0px; text-align:start"><span style="color:rgba(0, 0, 0, 0.87)"><span style="font-family:"Open Sans","Helvetica Neue",Helvetica,Arial,sans-serif"><span style="background-color:#ffffff">打开<code>application.properties</code>文件并向其中添加以下属性。</span></span></span></p> <pre> <code class="language-html">## Spring DATASOURCE (DataSourceAutoConfiguration & DataSourceProperties) spring.datasource.url = jdbc:mysql://localhost:3306/notes_app?useSSL=false spring.datasource.username = root spring.datasource.password = root ## Hibernate Properties # The SQL dialect makes Hibernate generate better SQL for the chosen database spring.jpa.properties.hibernate.dialect = org.hibernate.dialect.MySQL5InnoDBDialect # Hibernate ddl auto (create, create-drop, validate, update) spring.jpa.hibernate.ddl-auto = update</code></pre> <p style="margin-left:0px; margin-right:0px; text-align:start"><span style="color:rgba(0, 0, 0, 0.87)"><span style="font-family:"Open Sans","Helvetica Neue",Helvetica,Arial,sans-serif"><span style="background-color:#ffffff">您需要在MySQL中创建一个名为<strong>notes_app</strong>的数据库,并根据您的MySQL安装更改<code>spring.datasource.username</code>&<code>spring.datasource.password</code>属性。</span></span></span></p> <p style="margin-left:0px; margin-right:0px; text-align:start"><span style="color:rgba(0, 0, 0, 0.87)"><span style="font-family:"Open Sans","Helvetica Neue",Helvetica,Arial,sans-serif"><span style="background-color:#ffffff">在上面的属性文件中,最后两个属性用于休眠。Spring Boot使用Hibernate作为默认的JPA实现。</span></span></span></p> <p style="margin-left:0px; margin-right:0px; text-align:start"><span style="color:rgba(0, 0, 0, 0.87)"><span style="font-family:"Open Sans","Helvetica Neue",Helvetica,Arial,sans-serif"><span style="background-color:#ffffff">该属性<code>spring.jpa.hibernate.ddl-auto</code>用于数据库初始化。我已经使用这个属性的值<strong>“update”</strong>。</span></span></span></p> <p style="margin-left:0px; margin-right:0px; text-align:start"><span style="color:rgba(0, 0, 0, 0.87)"><span style="font-family:"Open Sans","Helvetica Neue",Helvetica,Arial,sans-serif"><span style="background-color:#ffffff">它做了两件事 -</span></span></span></p> <ul> <li>定义域模型时,将自动在数据库中创建一个表,并将域模型的字段映射到表中的相应列。</li> <li>对域模型的任何更改也会触发表的更新。例如,如果您更改字段的名称或类型,或将其他字段添加到模型中,则所有这些更改也会反映在映射表中。</li> </ul> <p style="margin-left:0px; margin-right:0px; text-align:start"><span style="color:rgba(0, 0, 0, 0.87)"><span style="font-family:"Open Sans","Helvetica Neue",Helvetica,Arial,sans-serif"><span style="background-color:#ffffff">使用<em>更新</em>的<code>spring.jpa.hibernate.ddl-auto</code>财产是好的发展。但是,对于生产,您应该保留此属性的值以<strong>“验证”</strong>,并使用像<a href="https://www.callicoder.com/spring-boot-flyway-database-migration-example/" rel="external nofollow" style="box-sizing:border-box; color:#419be8; text-decoration:none; word-wrap:break-word" target="_blank">Flyway</a>这样的数据库迁移工具来管理数据库模式中的更改。</span></span></span></p> <h2 style="margin-left:0px; margin-right:0px; text-align:start">6.创建Note模型</h2> <p style="margin-left:0px; margin-right:0px; text-align:start"><span style="color:rgba(0, 0, 0, 0.87)"><span style="font-family:"Open Sans","Helvetica Neue",Helvetica,Arial,sans-serif"><span style="background-color:#ffffff">好吧!现在让我们来创建<code>Note</code>模型。我们的<code>Note</code>模型有以下领域 -</span></span></span></p> <ul> <li><strong><code>id</code></strong>:带自动递增的主键。</li> <li><strong><code>title</code></strong>:备注的标题。(NOT NULL字段)</li> <li><strong><code>content</code></strong>:笔记的内容。(NOT NULL字段)</li> <li><strong><code>createdAt</code></strong>:<code>Note</code>创建时间。</li> <li><strong><code>updatedAt</code></strong>:<code>Note</code>更新时间。</li> </ul> <br /> 现在,让我们看看我们如何在Spring中对它进行建模。创建一个名为<code>model</code>inside 的新包,<code>com.example.easynotes</code>并添加一个名为<code>Note.java</code>以下内容的类- <pre> <code class="language-java">package com.example.easynotes.model; import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import org.springframework.data.annotation.CreatedDate; import org.springframework.data.annotation.LastModifiedDate; import org.springframework.data.jpa.domain.support.AuditingEntityListener; import javax.persistence.*; import javax.validation.constraints.NotBlank; import java.util.Date; @Entity @Table(name = "notes") @EntityListeners(AuditingEntityListener.class) @JsonIgnoreProperties(value = {"createdAt", "updatedAt"}, allowGetters = true) public class Note implements Serializable { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; @NotBlank private String title; @NotBlank private String content; @Column(nullable = false, updatable = false) @Temporal(TemporalType.TIMESTAMP) @CreatedDate private Date createdAt; @Column(nullable = false) @Temporal(TemporalType.TIMESTAMP) @LastModifiedDate private Date updatedAt; // Getters and Setters ... (Omitted for brevity) }</code></pre> <ul> <li>您的所有域模型必须使用注释进行<strong><code>@Entity</code></strong>注释。它用于将该类标记为持久的Java类。</li> <li><strong><code>@Table</code></strong> 注释用于提供此实体将映射到的表的详细信息。</li> <li><strong><code>@Id</code></strong> 注释用于定义主键。</li> <li><strong><code>@GeneratedValue</code></strong>注释用于定义主键生成策略。在上述情况下,我们宣布主键是一个<code>Auto Increment</code>字段。</li> <li><strong><code>@NotBlank</code></strong>注释用于验证注释字段是否为<code>not null</code>空。</li> <li><strong><code>@Column</code></strong>注释用于定义将映射到注释字段的列的属性。您可以定义多个属性,如名称,长度,可为空,可更新等。</li> </ul> <p style="margin-left:0px; margin-right:0px">默认情况下,名为的字段将<code>createdAt</code>映射到<code>created_at</code>数据库表中指定的列。即所有的骆驼案件都被下划线取代。</p> <p style="margin-left:0px; margin-right:0px">如果您想将字段映射到不同的列,可以使用以下命令指定它 -</p> <pre> <code class="language-java">@Column(name = "created_on") private String createdAt;</code></pre> <ul> <li><strong><code>@Temporal</code></strong>注释<code>java.util.Date</code>和<code>java.util.Calendar</code>类一起使用。它将Java Object中的日期和时间值转换为兼容的数据库类型,反之亦然。</li> <li><strong><code>@JsonIgnoreProperties</code></strong>注释是杰克逊的注释。Spring Boot使用Jackson将JSON序列化和反序列化Java对象。</li> <li>使用这个注解是因为我们不希望剩下的api的客户端提供<code>createdAt</code>和<code>updatedAt</code>值。如果他们提供这些值,那么我们会简单地忽略它们。但是,我们将在JSON响应中包含这些值。</li> </ul> <h2 style="margin-left:0px; margin-right:0px; text-align:start">7.启用JPA审核</h2> <p style="margin-left:0px; margin-right:0px; text-align:start"><span style="color:rgba(0, 0, 0, 0.87)"><span style="font-family:"Open Sans","Helvetica Neue",Helvetica,Arial,sans-serif"><span style="background-color:#ffffff">在我们的<code>Note</code>模型中,我们已经注解<code>createdAt</code>,并<code>updatedAt</code>与领域<code>@CreatedDate</code>,并<code>@LastModifiedDate</code>分别标注。</span></span></span></p> <p style="margin-left:0px; margin-right:0px; text-align:start"><span style="color:rgba(0, 0, 0, 0.87)"><span style="font-family:"Open Sans","Helvetica Neue",Helvetica,Arial,sans-serif"><span style="background-color:#ffffff">现在,我们想要的是,只要我们创建或更新实体,这些字段就会自动填充。</span></span></span></p> <p style="margin-left:0px; margin-right:0px; text-align:start"><span style="color:rgba(0, 0, 0, 0.87)"><span style="font-family:"Open Sans","Helvetica Neue",Helvetica,Arial,sans-serif"><span style="background-color:#ffffff">要做到这一点,我们需要做两件事 -</span></span></span></p> <p style="margin-left:0px; margin-right:0px; text-align:start"><span style="color:rgba(0, 0, 0, 0.87)"><span style="font-family:"Open Sans","Helvetica Neue",Helvetica,Arial,sans-serif"><span style="background-color:#ffffff"><strong>1.将Spring Data JPA添加<code>AuditingEntityListener</code>到域模型中。</strong></span></span></span></p> <p style="margin-left:0px; margin-right:0px; text-align:start"><span style="color:rgba(0, 0, 0, 0.87)"><span style="font-family:"Open Sans","Helvetica Neue",Helvetica,Arial,sans-serif"><span style="background-color:#ffffff">我们已经在我们的<code>Note</code>模型中使用了注释<code>@EntityListeners(AuditingEntityListener.class)</code>。</span></span></span></p> <p style="margin-left:0px; margin-right:0px; text-align:start"><span style="color:rgba(0, 0, 0, 0.87)"><span style="font-family:"Open Sans","Helvetica Neue",Helvetica,Arial,sans-serif"><span style="background-color:#ffffff"><strong>2.在主应用程序中启用JPA审核。</strong></span></span></span></p> <p style="margin-left:0px; margin-right:0px; text-align:start"><span style="color:rgba(0, 0, 0, 0.87)"><span style="font-family:"Open Sans","Helvetica Neue",Helvetica,Arial,sans-serif"><span style="background-color:#ffffff">打开<code>EasyNotesApplication.java</code>并添加<code>@EnableJpaAuditing</code>注释。</span></span></span></p> <pre> <code class="language-java">@SpringBootApplication @EnableJpaAuditing public class EasyNotesApplication { public static void main(String[] args) { SpringApplication.run(EasyNotesApplication.class, args); } }</code></pre> <h2 style="margin-left:0px; margin-right:0px; text-align:start">8.创建NoteRepository以访问数据库中的数据</h2> <p style="margin-left:0px; margin-right:0px; text-align:start"><span style="color:rgba(0, 0, 0, 0.87)"><span style="font-family:"Open Sans","Helvetica Neue",Helvetica,Arial,sans-serif"><span style="background-color:#ffffff">接下来我们要做的是创建一个存储库来访问数据库中的Note数据。</span></span></span></p> <p style="margin-left:0px; margin-right:0px; text-align:start"><span style="color:rgba(0, 0, 0, 0.87)"><span style="font-family:"Open Sans","Helvetica Neue",Helvetica,Arial,sans-serif"><span style="background-color:#ffffff">那么,Spring Data JPA已经在这里介绍了我们。它带有一个<a href="https://docs.spring.io/autorepo/docs/spring-data-jpa/current/api/org/springframework/data/jpa/repository/JpaRepository.html" rel="external nofollow" style="box-sizing:border-box; color:#419be8; text-decoration:none; word-wrap:break-word" target="_blank"><code>JpaRepository</code></a>接口,该接口定义实体上所有CRUD操作的方法,以及一个默认的<code>JpaRepository</code>调用实现<a href="http://docs.spring.io/autorepo/docs/spring-data-jpa/current/api/org/springframework/data/jpa/repository/support/SimpleJpaRepository.html" rel="external nofollow" style="box-sizing:border-box; color:#419be8; text-decoration:none; word-wrap:break-word" target="_blank"><code>SimpleJpaRepository</code></a>。</span></span></span></p> <p style="margin-left:0px; margin-right:0px; text-align:start"><span style="color:rgba(0, 0, 0, 0.87)"><span style="font-family:"Open Sans","Helvetica Neue",Helvetica,Arial,sans-serif"><span style="background-color:#ffffff">现在创建存储库。首先,创建一个<code>repository</code>在基础包内调用的新包<code>com.example.easynotes</code>。然后,创建一个接口<code>NoteRepository</code>并将其从<code>JpaRepository</code>-</span></span></span></p> <pre> <code class="language-java">package com.example.easynotes.repository; import com.example.easynotes.model.Note; import org.springframework.data.jpa.repository.JpaRepository; @Repository public interface NoteRepository extends JpaRepository<Note, Long> { }</code></pre> <p style="margin-left:0px; margin-right:0px; text-align:start"><span style="color:rgba(0, 0, 0, 0.87)"><span style="font-family:"Open Sans","Helvetica Neue",Helvetica,Arial,sans-serif"><span style="background-color:#ffffff">请注意,我们使用注释标注了界面<code>@Repository</code>。这告诉Spring在组件扫描期间引导存储库。</span></span></span></p> <p style="margin-left:0px; margin-right:0px; text-align:start"><span style="color:rgba(0, 0, 0, 0.87)"><span style="font-family:"Open Sans","Helvetica Neue",Helvetica,Arial,sans-serif"><span style="background-color:#ffffff">大!这就是您在存储库层中必须做的所有事情。现在,您将能够使用JpaRepository的方法,如<code>save()</code>,<code>findOne()</code>,<code>findAll()</code>,<code>count()</code>,<code>delete()</code>等。</span></span></span></p> <p style="margin-left:0px; margin-right:0px; text-align:start"><span style="color:rgba(0, 0, 0, 0.87)"><span style="font-family:"Open Sans","Helvetica Neue",Helvetica,Arial,sans-serif"><span style="background-color:#ffffff">你不需要实现这些方法。Spring Data JPA已经实现了它们<code>SimpleJpaRepository</code>。这个实现在运行时被Spring自动插入。</span></span></span></p> <p style="margin-left:0px; margin-right:0px; text-align:start"><span style="color:rgba(0, 0, 0, 0.87)"><span style="font-family:"Open Sans","Helvetica Neue",Helvetica,Arial,sans-serif"><span style="background-color:#ffffff">检查<a href="http://docs.spring.io/autorepo/docs/spring-data-jpa/current/api/org/springframework/data/jpa/repository/support/SimpleJpaRepository.html" rel="external nofollow" style="box-sizing:border-box; color:#419be8; text-decoration:none; word-wrap:break-word" target="_blank">SimpleJpaRepository文档中</a>提供的所有方法。</span></span></span><br /> <br />  </p> <h2 style="margin-left:0px; margin-right:0px; text-align:start">9.创建自定义业务例外</h2> <p style="margin-left:0px; margin-right:0px; text-align:start"><span style="color:rgba(0, 0, 0, 0.87)"><span style="font-family:"Open Sans","Helvetica Neue",Helvetica,Arial,sans-serif"><span style="background-color:#ffffff">我们将<code>Note</code>在下一节中定义用于创建,检索,更新和删除a的Rest API 。</span></span></span></p> <p style="margin-left:0px; margin-right:0px; text-align:start"><span style="color:rgba(0, 0, 0, 0.87)"><span style="font-family:"Open Sans","Helvetica Neue",Helvetica,Arial,sans-serif"><span style="background-color:#ffffff"><code>ResourceNotFoundException</code>只要在数据库中找不到<code>Note</code>给定的API,API就会抛出<code>id</code>。</span></span></span></p> <p style="margin-left:0px; margin-right:0px; text-align:start"><span style="color:rgba(0, 0, 0, 0.87)"><span style="font-family:"Open Sans","Helvetica Neue",Helvetica,Arial,sans-serif"><span style="background-color:#ffffff">以下是定义<code>ResourceNotFoundException</code>。(我创建了一个名为<code>exception</code>inside 的包<code>com.example.easynotes</code>来存储这个异常类) -</span></span></span></p> <pre> <code class="language-java">package com.example.easynotes.exception; import org.springframework.http.HttpStatus; import org.springframework.web.bind.annotation.ResponseStatus; @ResponseStatus(value = HttpStatus.NOT_FOUND) public class ResourceNotFoundException extends RuntimeException { private String resourceName; private String fieldName; private Object fieldValue; public ResourceNotFoundException( String resourceName, String fieldName, Object fieldValue) { super(String.format("%s not found with %s : '%s'", resourceName, fieldName, fieldValue)); this.resourceName = resourceName; this.fieldName = fieldName; this.fieldValue = fieldValue; } public String getResourceName() { return resourceName; } public String getFieldName() { return fieldName; } public Object getFieldValue() { return fieldValue; } }</code></pre> <p style="margin-left:0px; margin-right:0px; text-align:start"><span style="color:rgba(0, 0, 0, 0.87)"><span style="font-family:"Open Sans","Helvetica Neue",Helvetica,Arial,sans-serif"><span style="background-color:#ffffff">注意<code>@ResponseStatus</code>在上述异常类中使用注释。这将导致Spring引导以指定的HTTP状态码进行响应,无论何时从您的控制器抛出此异常。</span></span></span></p> <h2 style="margin-left:0px; margin-right:0px; text-align:start">10.创建NoteController</h2> <p style="margin-left:0px; margin-right:0px; text-align:start"><span style="color:rgba(0, 0, 0, 0.87)"><span style="font-family:"Open Sans","Helvetica Neue",Helvetica,Arial,sans-serif"><span style="background-color:#ffffff"><strong>最后一步</strong> - 我们现在将创建REST API来创建,检索,更新和删除注释。</span></span></span></p> <p style="margin-left:0px; margin-right:0px; text-align:start"><span style="color:rgba(0, 0, 0, 0.87)"><span style="font-family:"Open Sans","Helvetica Neue",Helvetica,Arial,sans-serif"><span style="background-color:#ffffff">首先,<code>controller</code>在里面创建一个新的包<code>com.example.easynotes</code>。然后,创建一个<code>NoteController.java</code>包含以下内容的新课程-</span></span></span></p> <pre> <code class="language-java">package com.example.easynotes.controller; import com.example.easynotes.exception.ResourceNotFoundException; import com.example.easynotes.model.Note; import com.example.easynotes.repository.NoteRepository; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.*; import javax.validation.Valid; import java.util.List; @RestController @RequestMapping("/api") public class NoteController { @Autowired NoteRepository noteRepository; // Get All Notes // Create a new Note // Get a Single Note // Update a Note // Delete a Note }</code></pre> <p style="margin-left:0px; margin-right:0px; text-align:start"><span style="color:rgba(0, 0, 0, 0.87)"><span style="font-family:"Open Sans","Helvetica Neue",Helvetica,Arial,sans-serif"><span style="background-color:#ffffff"><strong><code>@RestController</code></strong>注释是Spring <code>@Controller</code>和<code>@ResponseBody</code>注释的组合。</span></span></span></p> <p style="margin-left:0px; margin-right:0px; text-align:start"><span style="color:rgba(0, 0, 0, 0.87)"><span style="font-family:"Open Sans","Helvetica Neue",Helvetica,Arial,sans-serif"><span style="background-color:#ffffff">该<code>@Controller</code>注释被用来定义一个控制器和所述<code>@ResponseBody</code>注释被用于指示一个方法的返回值应作为所述请求的响应主体。</span></span></span></p> <p style="margin-left:0px; margin-right:0px; text-align:start"><span style="color:rgba(0, 0, 0, 0.87)"><span style="font-family:"Open Sans","Helvetica Neue",Helvetica,Arial,sans-serif"><span style="background-color:#ffffff"><strong><code>@RequestMapping("/api")</code></strong>声明此控制器中所有apis的url将以<code>/api</code>。</span></span></span></p> <p style="margin-left:0px; margin-right:0px; text-align:start"><span style="color:rgba(0, 0, 0, 0.87)"><span style="font-family:"Open Sans","Helvetica Neue",Helvetica,Arial,sans-serif"><span style="background-color:#ffffff">现在我们来看看所有apis的执行情况。</span></span></span></p> <h4 style="margin-left:0px; margin-right:0px; text-align:start">1.获取所有Notes(GET / api / notes)</h4> <pre> <code class="language-java">// Get All Notes @GetMapping("/notes") public List<Note> getAllNotes() { return noteRepository.findAll(); }</code></pre> <p style="margin-left:0px; margin-right:0px; text-align:start"><span style="color:rgba(0, 0, 0, 0.87)"><span style="font-family:"Open Sans","Helvetica Neue",Helvetica,Arial,sans-serif"><span style="background-color:#ffffff">上述方法非常简单。它调用JpaRepository的<code>findAll()</code>方法从数据库中检索所有注释并返回整个列表。</span></span></span></p> <p style="margin-left:0px; margin-right:0px; text-align:start"><span style="color:rgba(0, 0, 0, 0.87)"><span style="font-family:"Open Sans","Helvetica Neue",Helvetica,Arial,sans-serif"><span style="background-color:#ffffff">此外,<code>@GetMapping("/notes")</code>注释是一种简短形式<code>@RequestMapping(value="/notes", method=RequestMethod.GET)</code>。</span></span></span></p> <h4 style="margin-left:0px; margin-right:0px; text-align:start">2.创建一个新的Note(POST / api / notes)</h4> <pre> <code class="language-java">// Create a new Note @PostMapping("/notes") public Note createNote(@Valid @RequestBody Note note) { return noteRepository.save(note); }</code></pre> <p style="margin-left:0px; margin-right:0px; text-align:start"><span style="color:rgba(0, 0, 0, 0.87)"><span style="font-family:"Open Sans","Helvetica Neue",Helvetica,Arial,sans-serif"><span style="background-color:#ffffff">该<code>@RequestBody</code>注释用于将请求主体与方法参数绑定。</span></span></span></p> <p style="margin-left:0px; margin-right:0px; text-align:start"><span style="color:rgba(0, 0, 0, 0.87)"><span style="font-family:"Open Sans","Helvetica Neue",Helvetica,Arial,sans-serif"><span style="background-color:#ffffff">该<code>@Valid</code>注释可以确保请求主体是有效的。请记住,我们在模型中标注了注释的标题和<code>@NotBlank</code>注释内容<code>Note</code>?</span></span></span></p> <p style="margin-left:0px; margin-right:0px; text-align:start"><span style="color:rgba(0, 0, 0, 0.87)"><span style="font-family:"Open Sans","Helvetica Neue",Helvetica,Arial,sans-serif"><span style="background-color:#ffffff">如果请求主体没有标题或内容,那么spring会<code>400 BadRequest</code>向客户端返回错误。</span></span></span></p> <h4 style="margin-left:0px; margin-right:0px; text-align:start">3.获取单个Note(Get / api / notes / {noteId})</h4> <pre> <code class="language-java">// Get a Single Note @GetMapping("/notes/{id}") public Note getNoteById(@PathVariable(value = "id") Long noteId) { return noteRepository.findById(noteId) .orElseThrow(() -> new ResourceNotFoundException("Note", "id", noteId)); }</code></pre> <p style="margin-left:0px; margin-right:0px; text-align:start"><span style="color:rgba(0, 0, 0, 0.87)"><span style="font-family:"Open Sans","Helvetica Neue",Helvetica,Arial,sans-serif"><span style="background-color:#ffffff">的<code>@PathVariable</code>注释,顾名思义,是用来与方法参数路径变量绑定。</span></span></span></p> <p style="margin-left:0px; margin-right:0px; text-align:start"><span style="color:rgba(0, 0, 0, 0.87)"><span style="font-family:"Open Sans","Helvetica Neue",Helvetica,Arial,sans-serif"><span style="background-color:#ffffff">在上面的方法中,我们抛出一个没有找到给定id的<code>ResourceNotFoundException</code>a <code>Note</code>。</span></span></span></p> <p style="margin-left:0px; margin-right:0px; text-align:start"><span style="color:rgba(0, 0, 0, 0.87)"><span style="font-family:"Open Sans","Helvetica Neue",Helvetica,Arial,sans-serif"><span style="background-color:#ffffff">这将导致Spring Boot向客户端返回一个404 Not Found错误(请记住,我们已经<code>@ResponseStatus(value = HttpStatus.NOT_FOUND)</code>为<code>ResourceNotFoundException</code>该类添加了注释)。</span></span></span></p> <h4 style="margin-left:0px; margin-right:0px; text-align:start">4.更新Note(PUT / api / notes / {noteId})</h4> <pre> <code class="language-java">// Update a Note @PutMapping("/notes/{id}") public Note updateNote(@PathVariable(value = "id") Long noteId, @Valid @RequestBody Note noteDetails) { Note note = noteRepository.findById(noteId) .orElseThrow(() -> new ResourceNotFoundException("Note", "id", noteId)); note.setTitle(noteDetails.getTitle()); note.setContent(noteDetails.getContent()); Note updatedNote = noteRepository.save(note); return updatedNote; }</code></pre> <h4 style="margin-left:0px; margin-right:0px; text-align:start">5.删除一个Note(DELETE / api / notes / {noteId})</h4> <pre> <code class="language-java">// Delete a Note @DeleteMapping("/notes/{id}") public ResponseEntity<?> deleteNote(@PathVariable(value = "id") Long noteId) { Note note = noteRepository.findById(noteId) .orElseThrow(() -> new ResourceNotFoundException("Note", "id", noteId)); noteRepository.delete(note); return ResponseEntity.ok().build(); }</code></pre> <h2 style="margin-left:0px; margin-right:0px; text-align:start">运行应用程序</h2> <p style="margin-left:0px; margin-right:0px; text-align:start"><span style="color:rgba(0, 0, 0, 0.87)"><span style="font-family:"Open Sans","Helvetica Neue",Helvetica,Arial,sans-serif"><span style="background-color:#ffffff">我们已经成功为我们的应用程序构建了所有apis。现在运行该应用并测试apis。</span></span></span></p> <p style="margin-left:0px; margin-right:0px; text-align:start"><span style="color:rgba(0, 0, 0, 0.87)"><span style="font-family:"Open Sans","Helvetica Neue",Helvetica,Arial,sans-serif"><span style="background-color:#ffffff">只需转到应用程序的根目录并输入以下命令即可运行它 -</span></span></span></p> <pre style="margin-left:0px; margin-right:0px; text-align:left"> <span style="background-color:#f6f8fa"><span style="font-family:monospace,monospace"><span style="color:rgba(0, 0, 0, 0.9)"><code class="language-bash">$ mvn spring-boot:run </code></span></span></span></pre> <p style="margin-left:0px; margin-right:0px; text-align:start"><span style="color:rgba(0, 0, 0, 0.87)"><span style="font-family:"Open Sans","Helvetica Neue",Helvetica,Arial,sans-serif"><span style="background-color:#ffffff">该应用程序将从Spring Boot的默认tomcat端口8080开始。</span></span></span></p> <p style="margin-left:0px; margin-right:0px; text-align:start"><span style="color:rgba(0, 0, 0, 0.87)"><span style="font-family:"Open Sans","Helvetica Neue",Helvetica,Arial,sans-serif"><span style="background-color:#ffffff">大!现在,是时候用邮差测试我们的apis了。</span></span></span></p> <h2 style="margin-left:0px; margin-right:0px; text-align:start">测试API</h2> <h4 style="margin-left:0px; margin-right:0px; text-align:start"><strong>使用<code>POST /api/notes</code>API 创建新的note</strong></h4> <img alt="使用POST /api/notesAPI 创建新的note" class="img-thumbnail" src="/resources/assist/images/blog/cb5cfce6fa5a4e48a68fbef46dccf83b.png" /> <h4 style="margin-left:0px; margin-right:0px; text-align:start"><strong>使用<code>GET /api/notes</code>API 检索所有Notes</strong></h4> <img alt="使用GET /api/notesAPI 检索所有Notes" class="img-thumbnail" src="/resources/assist/images/blog/f04e78160b664266b9ae53ac746f0b16.png" /> <h4 style="margin-left:0px; margin-right:0px; text-align:start"><strong>使用<code>GET /api/notes/{noteId}</code>API 检索单个Note</strong></h4> <img alt="使用GET /api/notes/{noteId}API 检索单个Note" class="img-thumbnail" src="/resources/assist/images/blog/aad1aebfe57b49429a81b7f00bae2e8d.png" /> <h4 style="margin-left:0px; margin-right:0px; text-align:start"><strong>使用<code>PUT /api/notes/{noteId}</code>API 更新note</strong></h4> <img alt="使用PUT /api/notes/{noteId}API 更新note" class="img-thumbnail" src="/resources/assist/images/blog/b9926529dfaf4b6694043cc11afb2363.png" /><br />   <h4 style="margin-left:0px; margin-right:0px; text-align:start"><strong>使用<code>DELETE /api/notes/{noteId}</code>API 删除note</strong></h4> <img alt="使用DELETE /api/notes/{noteId}API 删除note" class="img-thumbnail" src="/resources/assist/images/blog/1e7f7a50e90b4889aa499b48ffbc3754.png" /><br />