领先一步
VMware 提供培训和认证,助您快速发展。
了解更多最近我得知我们用于某个指南的公共 API 包含了令人反感的内容。在确认此事后,我立即回复说我们将选择另一个源。为了将来避免此类问题,我认为最好的解决方案是构建我们自己的 RESTful 语录服务。所以我决定使用最好的工具来实现这一目标,即 Spring 技术栈,并能够在第二天就完成了迁移。
为了开始这项工作,我列出了我认为对于创建 RESTful Web 服务来说是正确的工具的清单。
我很快放弃了通过网页添加、删除、管理或查看数据的想法。取而代之的是,我的重点是提供一套固定的内容,其结构与指南预期消费的内容完全相同。
指南的原始内容是一系列“Chunk Norris”笑话。我喜欢好笑话。但当我重新查看公共 API 时,我发现其中几个笑话有点低俗。与同事简短讨论后,提出了引用历史名言的想法。我采纳了这个想法并做了一点修改。最近我出于个人原因收集了一些开发者关于 Spring Boot 的语录,所以我决定用它们作为精选内容。
为了开始,我访问了 http://start.spring.io。这个 Spring Boot 应用允许您输入新项目的详细信息、选择 Java 版本,并选择所需的 Spring Boot 启动器。我使用了上面的清单,创建了一个新的基于 gradle 的项目。
解压项目并将其导入到我的 IDE 后,我做的第一件事就是复制了 Reactor 指南中显示的域对象。这样,我就可以确保我的 REST 服务发送出去的数据是正确的。由于我的 Quoters Incorporated 应用中的 POJO 几乎完全相同,我在这里就不发布它们了。
然后我创建了一个 Spring Data Repository。
public interface QuoteRepository extends CrudRepository<Quote, Long> {}
这个空的接口定义处理内部主键类型为 Long
的 Quote
对象。通过扩展 Spring Data Commons 的 CrudRepository
,它继承了一系列数据库操作,我们稍后会用到它们。
下一步是什么?初始化一些数据。我创建了一个 DatabaseLoader
,代码如下
@Service
public class DatabaseLoader {
private final QuoteRepository repository;
@Autowired
public DatabaseLoader(QuoteRepository repository) {
this.repository = repository;
}
@PostConstruct
void init() {
repository.save(new Quote("Working with Spring Boot is like pair-programming with the Spring developers."));
// more quotes...
}
}
@Service
,因此当应用启动时,@ComponentScan
会自动检测到它。QuoteRepository
的副本可用。@PostConstruct
告诉 Spring MVC 在所有 bean 创建后运行数据加载方法。init()
方法使用 Spring Data JPA 创建了一整批语录。因为我在 build.gradle
中选择了 H2 作为我的数据库 (com.h2database:h2),所以根本不需要进行数据库设置(这要感谢 Spring Boot)。
构建完数据库层后,我继续创建 API。使用 Spring MVC,这一点都不难。
@RestController
public class QuoteController {
private final QuoteRepository repository;
private final static Quote NONE = new Quote("None");
private final static Random RANDOMIZER = new Random();
@Autowired
public QuoteController(QuoteRepository repository) {
this.repository = repository;
}
@RequestMapping(value = "/api", method = RequestMethod.GET)
public List<QuoteResource> getAll() {
return StreamSupport.stream(repository.findAll().spliterator(), false)
.map(q -> new QuoteResource(q, "success"))
.collect(Collectors.toList());
}
@RequestMapping(value = "/api/{id}", method = RequestMethod.GET)
public QuoteResource getOne(@PathVariable Long id) {
if (repository.exists(id)) {
return new QuoteResource(repository.findOne(id), "success");
} else {
return new QuoteResource(NONE, "Quote " + id + " does not exist");
}
}
@RequestMapping(value = "/api/random", method = RequestMethod.GET)
public QuoteResource getRandomOne() {
return getOne(nextLong(1, repository.count() + 1));
}
private long nextLong(long lowerRange, long upperRange) {
return (long)(RANDOMIZER.nextDouble() * (upperRange - lowerRange)) + lowerRange;
}
}
让我们分解一下
@RestController
。这意味着所有路由都返回对象而不是视图。NONE
语录和一个用于随机选择语录的 Java 8 Random
。QuoteRepository
。API | 描述 |
---|---|
/api | 获取所有语录 |
/api/{id} | 获取语录 id |
/api/random | 获取一个随机语录 |
要获取所有语录,我使用 Java 8 stream 来包装 Spring Data 的 findAll()
,然后将每个结果包装到 QuoteResource
中。结果被转换为 List
。
要获取单个语录,它首先检查给定的 id 是否存在。如果不存在,则返回 NONE
。否则,返回包装好的语录。
最后,要获取一个随机语录,我在 nextLong()
工具方法内部使用了 Java 8 的 Random
工具来获取一个包含 lowerRange
和 upperRange
的 Long
。
问题:为什么我使用
QuoteResource
?Quote
是由QuoteRepository
返回的核心域对象。为了匹配之前的公共 API,我将每个实例包装在QuoteResource
中,该资源包含一个 status 代码。
设置完成后,由 http://start.spring.io 创建的默认 Application
类就可以运行了。
$ curl localhost:8080/api/random
{
type: "success",
value: {
id: 1,
quote: "Working with Spring Boot is like pair-programming with the Spring developers."
}
}
```
Ta dah!
To wrap things up, I built the JAR file and pushed it up to [Pivotal Web Services](https://run.pivotal.io/). You can view the site yourself at http://gturnquist-quoters.cfapps.io/api/random.
Suffice it to say, I was able to tweak the [Reactor guide](https://springjava.cn/guides/gs/messaging-reactor/) by altering [ONE LINE OF CODE](https://github.com/spring-guides/gs-messaging-reactor/blob/master/complete/src/main/java/hello/Receiver.java#L21). With that in place, I did some other clean up of the content and was done!
To see the code, please visit https://github.com/gregturn/quoters.
### Outstanding issues
* This RESTful service satisfies [Level 2 - HTTP Verbs](https://martinfowler.com.cn/articles/richardsonMaturityModel.html#level2) of the Richardson Maturity Model. While good, it's best to shoot for [Level 3 - Hypermedia](https://martinfowler.com.cn/articles/richardsonMaturityModel.html#level3). With [Spring HATEOAS](http://projects.spring.io/spring-hateoas), it's easier than ever to add hypermedia links. Stay tuned.
* There is no friendly web page. This would be nice, but it isn't required.
* Content is fixed and defined inside the app. To make content flexible, we would need to open the door to POSTs and PUTs. This would introduce the desire to also secure things properly.
These are some outstanding things that didn't fit inside the time budget and weren't required to solve the original problem involving the Reactor guide. But they are good exercises you can explore! You can clone the project in github and take a shot at it yourself!
### SpringOne 2GX 2014
Book your place at [SpringOne](https://2014.event.springone2gx.com/register) in Dallas, TX for Sept 8-11 soon. It's simply the best opportunity to find out first hand all that's going on and to provide direct feedback. You can see myself and Roy Clarkson talk about [Spring Data REST - Data Meets Hypermedia](https://2014.event.springone2gx.com/schedule/sessions/spring_data_rest_data_meets_hypermedia.html) to see how to merge Spring Data and RESTful services.