restful和rest_HATEOAS的RESTful服务:JVM上的REST API和超媒体

restful和rest

1.简介

到目前为止,我们已经花了很多时间谈论了相当数量的关于角色的的超媒体和HATEOAS在REST风格的 Web服务和API,扫视不同规格和可用性方面。 听起来好像支持超媒体和HATEOAS并不难,只需选择您的收藏夹就可以了! 您可能会猜到,现实是“好,取决于情况”,在本教程的这一部分中,我们将理解“为什么”。 困难的学习方法是从头开始设计和实现它们。 这正是我们将要忙的事情,主要在Java中将RESTful Web API和客户端引入JVM平台。

我们将为其设计RESTful Web API的应用程序是我们之前涉及的案例研究的扩展版本。 从业务角度来看,我们将实施一个可以让客户租车的租车平台。 我们的目标是根据REST架构风格的原则和约束来构建该平台。

目录

1.简介 2.从CRUD到工作流程 3.在服务器上
3.1。 JAX-RS 3.2。 Quarkus,Micronaut,Helidon…… 3.3。 Crnk 3.4。 Spring帽子 3.5。 九头蛇
4.在客户端上
4.1。 JAX-RS 4.2。 Crnk 4.3。 Spring帽子
5。结论 6.接下来 7.下载源代码

2.从CRUD到工作流程

超媒体作为驱动力的存在极大地改变了设计过程。 不幸的事实是,目前大多数基于HTTP的Web服务和API基本上都是CRUD代理,用于其后面的数据存储。 仅仅公开数据模型并不能帮助客户理解他们可以做什么,他们注定要在许多地方复制服务器的业务逻辑(至少是业务逻辑的大部分)。 这不是经过深思熟虑的REST应用程序应该做的。

REST API应该花费几乎所有的描述性精力来定义用于表示资源和驱动应用程序状态的媒体类型,或者为现有标准媒体类型定义扩展关系名称和/或启用超文本的标记。 花费所有精力描述应该在媒体类型的处理规则范围内(并且在大多数情况下已由现有媒体类型定义)完全定义对感兴趣的URI使用哪种方法。

https://roy.gbiv.com/untangled/2008/rest-apis-must-be-超文本驱动

代替CRUD ,我们应该将RESTful Web API视为工作流。 通过就适当的媒体类型达成共识,服务器和客户端可以就如何解释它们达成共识。 链接,关系和操作(支付能力)的存在会引导客户采取可能的后续步骤。 换句话说,服务定义并共享供客户遵循的工作流。

3.在服务器上

在深入研究库和框架之前,最好先了解我们正在尝试作为应用程序一部分构建的RESTful Web API的工作流程。 下图是这样做的尝试。

JVM上的REST API-租车API工作流程
租车API工作流程

诚然,工作流远非详尽无遗,但足以说明现实世界中RESTful Web API的复杂性,挑战和好处。

最后但并非最不重要的一点是,我们尚未决定要使用哪种超媒体规范 。 HAL的简单性,轻量级结构和广泛采用(以及HAL-FORMS )使其成为大多数情况下的不错选择,这就是我们将用于RESTful Web API的方式。

制定战略决策后,就该讨论技术细节了。 首先,我们正在寻找对我们有帮助的库或框架。

JAX-RS

Jakarta RESTful Web服务规范(更好地称为JAX-RS 2.1 ( JSR-370 ))是在JVM平台上实现RESTful Web服务和API的最受欢迎选择之一。

JAX-RS : RESTful Web服务的Java API JAX-RS 是一种Java编程语言API规范,它提供了根据代表性状态转移(REST)架构模式创建Web服务的支持。

https://projects.eclipse.org/projects/ee4j.jaxrs

尽管它包括全面的服务器端和客户端支持,但除了引入非常基本的Link表示之外,它几乎没有解决任何超媒体功能。

一些JAX-RS参考实现(例如Jersey )进行了努力,并捆绑了自己的专有扩展以促进对超媒体的支持,但是正如您所期望的那样,这些都不是规范的一部分。 这当然是有帮助的,但仍然需要开发人员完成大量工作。

Quarkus,Micronaut,Helidon……

向微服务架构和云计算的加速转变导致了新一代的云原生框架的泛滥。 对于JVM平台尤其如此,在JVM平台上,著名的领导者正受到Quarkus , Micronaut和Helidon等人的挑战。

实际上,对于大多数媒体 而言 , 超媒体和HATEOAS并不是首要任务。 Micronaut是异常值的一个示例,该异常值至少包含基本的超媒体元素 ,但总的来说,您是一个人。

Crnk

如果您碰巧选择了JSON:API规范来支持RESTful Web服务和API,那么Crnk框架当然值得一看。

Crnk是JSON API规范和Java中建议的实现,以促进构建RESTful应用程序。 它提供了许多可以使应用程序受益的约定和构造块。 这包括诸如排序,过滤,分页,请求复杂对象图,稀疏字段集,将链接附加到数据或自动执行多项操作等功能。 与框架和库(例如Spring,CDI,JPA,Bean验证,Dropwizard,Servlet API,Zipkin等)的进一步集成可确保JSON API与Jav​​a生态系统完美地结合在一起。

https://github.com/crnk-project/crnk-framework

Crnk采用以资源为中心的API建模方法,基本上可以实现非常干净且可维护的实现。 引用文档, 资源,关系存储库是Crnk的核心构建块。 下面的代码段很好地说明了这些概念。

@JsonApiResource(type = "customer", resourcePath = "customers")
public class Customer {@JsonApiId private String id;@NotNull @NotBlank private String firstName;@NotNull @NotBlank private String lastName;@JsonApiRelation(mappedBy = "customer")private Collection<Reservation> reservations;// Getters and setters here...
}@JsonApiResource(type = "reservation", resourcePath = "reservations")
public class Reservation {@JsonApiId private String id;private String vehicle;@NotNull @FutureOrPresent private LocalDate from;@NotNull @FutureOrPresent private LocalDate to;@JsonApiRelation private Customer customer;// Getters and setters here...
}@Repository
public class CustomerRepository implements ResourceRepository<Customer, String> {// Implementation of the repository methods here...
}@Repository
public class ReservationRepository implements ResourceRepository<Reservation, String>  {// Implementation of the repository methods here...
}

基本上就是这样,根据集成( Vert.X , JAX-RS和Spring Boot ), Crnk框架将为您完成其余工作。 不幸的是, Crnk不支持ALPS或JSON Hyper-Schema (至少是开箱即用的)。

但是由于我们决定使用HAL而不是JSON:API ,所以我们必须继续搜索。

Spring帽子

构成Spring产品组合的项目数量确实令人印象深刻。 我们特别感兴趣的是Spring HATEOAS ,它是一个库,用于支持实现超媒体驱动的RESTful Web服务和API的表示形式。 它实现了HAL , HAL-FORMS , Collection + JSON和UBER规范,并且加分了一点,它带有ALPS支持,非常适合我们完成已设定的目标。

如人们所料, Spring HATEOAS自然地与典型的Spring Boot Web应用程序集成,包括传统的Spring MVC和响应式Spring WebFlux堆栈。 @EnableHypermediaSupport批注以及Spring Boot自动配置功能会根据您选择的一个(或多个)规范来激活超媒体支持。

@SpringBootConfiguration
@EnableHypermediaSupport(type = HypermediaType.HAL_FORMS)
public class ReservationServiceConfig {@BeanHalFormsConfiguration halFormsConfiguration() {final HalFormsConfiguration configuration = new HalFormsConfiguration();configuration.registerPattern(LocalDate.class, "yyyy-MM-dd");return configuration;}
}

遵循非正式的命名约定, RootController我们的RESTful Web API提供RootController

@RestController
public class RootController {@GetMapping("/")public ResponseEntity<RepresentationModel<?>> root() {final RepresentationModel<?> model = new RepresentationModel<>();model.add(linkTo(methodOn(RootController.class).root()).withSelfRel());model.add(templated(linkTo(methodOn(ReservationController.class).findAll(null)), "reservations").withProfile(linkTo(methodOn(RootController.class).reservations()).withSelfRel().getHref()));model.add(linkTo(methodOn(CustomerController.class).findAll()).withRel("customers").withProfile(linkTo(methodOn(RootController.class).customers()).withSelfRel().getHref()));return ResponseEntity.ok(model);}
}

控制器返回的HAL文档提示了下一个可用的导航方向。

{                                                                         "_links": {                                                            "self": {                                                            "href": "https://rentals.jcg.com"                                   },                                                                    "reservations": {                                                    "href": "https://rentals.jcg.com/reservations{?page,size,sort}",     "profile": "https://rentals.jcg.com/alps/reservations",              "templated" : true                                                  },                                                                    "customers": {                                                       "href": "https://rentals.jcg.com/customers",                         "profile": "https://rentals.jcg.com/alps/customers"                  }                                                                     }                                                                       
}

有些事情可能引起您的注意。 第一个是reservations关系的链接,该链接作为模板返回。 第二个是每个链接关系的profile属性的存在,指向各自的ALPS配置文件。 下面的代码段说明了用于构建reservations收集资源的ALPS配置文件的Spring HATEOAS API。

@GetMapping(value = "/alps/reservations", produces = MediaTypes.ALPS_JSON_VALUE)
public ResponseEntity<Alps> reservations() {return ResponseEntity.ok(Alps.alps().doc(doc().href("https://rentals.jcg.com/documentation.html").build()).descriptor(List.of(descriptor().id("reservations").type(Type.SEMANTIC).rt("#reservation").descriptor(Arrays.asList(descriptor().id("book").name("reservations").type(Type.UNSAFE).rt("#reservation").build(),descriptor().id("list").name("reservations").type(Type.SAFE).rt("#reservation").build())).build(),descriptor().id("reservation").type(Type.SEMANTIC).descriptor(Stream.concat(PropertyUtils.getExposedProperties(Reservation.class).stream().map(property -> descriptor().id(property.getName()).href(href(property)) .type(Type.SEMANTIC).build()),Stream.of(descriptor().id("customer").type(Type.SAFE).rt("#customer").build(),descriptor().id("update").name("reservation").type(Type.IDEMPOTENT).rt("#reservation").build(),descriptor().id("cancel").name("reservation").type(Type.IDEMPOTENT).rt("#reservation").build())).collect(Collectors.toList())).build())).build());
}

分别是ALPS reservations集合资源概要文件的JSON表示,这基本上是客户端要处理的内容。

{"version": "1.0","doc": {"href": "https://rentals.jcg.com/documentation.html"},"descriptor": [ {"id": "reservations","type": "SEMANTIC","descriptor": [ {"id": "book","name": "reservations","type": "UNSAFE","rt": "#reservation"}, {"id": "list","name": "reservations","type": "SAFE","rt": "#reservation"} ],"rt": "#reservation"}, {"id": "reservation","type": "SEMANTIC","descriptor": [ {"id": "from","href" : "https://schema.org/Date","type": "SEMANTIC"}, {"id": "id","href" : "https://schema.org/Thing#identifier","type": "SEMANTIC"}, {"id": "to","href" : "https://schema.org/Date","type": "SEMANTIC"}, {"id": "vehicle","href" : "https://schema.org/Vehicle#name","type": "SEMANTIC"}, {"id": "customer","type": "SAFE","rt": "#customer"}, {"id": "update","name": "reservation","type": "IDEMPOTENT","rt": "#reservation"}, {"id": "cancel","name": "reservation","type": "IDEMPOTENT","rt": "#reservation"} ]} ]
}

本着超媒体和HATEOAS的精神, Spring HATEOAS方法也是面向资源的(或者更确切地说 ,面向资源表示的)。 基本上,您必须实现许多RepresentationModelAssembler (例如ReservationResourceAssembler )和控制器端点,这些端点依赖于各自的汇编器来构造单个资源表示或资源集合表示。

@Component
public class ReservationResourceAssembler implements SimpleRepresentationModelAssembler<Reservation> {@Overridepublic void addLinks(EntityModel<Reservation> resource) {resource.add(linkTo(methodOn(CustomerController.class).findOne(resource.getContent().getCustomerId())).withRel("customer").withType(linkTo(methodOn(RootController.class).customers()).slash("#customer").toString()));resource.add(linkTo(methodOn(ReservationController.class).findOne(resource.getContent().getId())).withSelfRel().withType(linkTo(methodOn(RootController.class).reservations()).slash("#reservation").toString()).andAffordance(afford(methodOn(ReservationController.class).modify(resource.getContent().getId(), null))).andAffordance(afford(methodOn(ReservationController.class).cancel(resource.getContent().getId()))));}
}

除了与customer的链接关系外,还有许多用于更改预留资源状态( modifycancel预留状态)的优惠(动作)。 另外,由于reservations收集资源正在使用分页(和排序),因此其表示的构造稍微复杂一点,并且涉及两个汇编程序,因此让我们看一下该示例。

@RestController
@RequestMapping(path = "/reservations")
public class ReservationController {@Autowired private ReservationRepository repository;@Autowired private ReservationResourceAssembler reservationResourceAssembler;@Autowired private PagedResourcesAssembler>Reservation< assembler;@GetMappingpublic ResponseEntity>PagedModel>EntityModel>Reservation<<< findAll(@PageableDefault Pageable pageable) {return ResponseEntity.ok(assembler.toModel(repository.findAll(pageable), reservationResourceAssembler));}
}

为了演示分页的效果,仅用说2个元素的页面大小来获取reservations集合就足够了。

{                                                                                        "_embedded": {                                                                        "reservations": [ {                                                                 "id": "13e1892765c5",                                                             "vehicle": "Honda Civic 2020",                                                    "from": "2020-01-01",                                                             "to": "2020-01-05",                                                               "_links": {                                                                       "customer": {                                                                   "href": "https://rentals.jcg.com/customers/fed195a03e9d",                       "type": "https://rentals.jcg.com/alps/customers#customer"                       },                                                                               "self": {                                                                       "href": "https://rentals.jcg.com/reservations/13e1892765c5",                    "type": "https://rentals.jcg.com/alps/reservations#reservation"                 }                                                                                },                                                                                 "_templates": {                                                                   "cancel": {                                                                     "method": "delete",                                                           "properties": [ ]                                                             },                                                                               "default": {                                                                    "method": "put",                                                              "properties": [ {                                                             "name": "from",                                                             "regex": "yyyy-MM-dd",                                                      "required": true                                                            }, {                                                                           "name": "to",                                                               "regex": "yyyy-MM-dd",                                                      "required": true                                                            }, {                                                                           "name": "vehicle",                                                          "required": true                                                            } ]                                                                            }                                                                                }                                                                                  }, {                                                                                 "id": "fc14e8ef90f5",                                                             "vehicle": "BMW 325i",                                                            "from": "2020-01-10",                                                             "to": "2020-01-12",                                                               "_links": {                                                                       "customer": {                                                                   "href": "https://rentals.jcg.com/customers/fed195a03e9d",                       "type": "https://rentals.jcg.com/alps/customers#customer"                       },                                                                               "self": {                                                                       "href": "https://rentals.jcg.com/reservations/fc14e8ef90f5",                    "type": "https://rentals.jcg.com/alps/reservations#reservation"                 }                                                                                },                                                                                 "_templates": {                                                                   "cancel": {                                                                     "method": "delete",                                                           "properties": [ ]                                                             },                                                                               "default": {                                                                    "method": "put",                                                              "properties": [ {                                                             "name": "from",                                                             "regex": "yyyy-MM-dd",                                                      "required": true                                                            }, {                                                                           "name": "to",                                                               "regex": "yyyy-MM-dd",                                                      "required": true                                                            }, {                                                                           "name": "vehicle",                                                          "required": true                                                            } ]                                                                            }                                                                                }                                                                                  } ]                                                                                  },                                                                                     "_links": {                                                                           "first": {                                                                          "href": "https://rentals.jcg.com/reservations?page=0&size=2"                        },                                                                                   "self": {                                                                           "href": "https://rentals.jcg.com/reservations?page=0&size=2"                        },                                                                                   "next": {                                                                           "href": "https://rentals.jcg.com/reservations?page=1&size=2"                        },                                                                                   "last": {                                                                           "href": "https://rentals.jcg.com/reservations?page=1&size=2"                        }                                                                                    },                                                                                     "page": {                                                                             "size": 2,                                                                          "totalElements": 3,                                                                 "totalPages": 2,                                                                    "number": 0                                                                         }                                                                                      
}

很容易发现附加的导航链接firstnextlast ,它们实际上是上下文相关的(例如,由于我们要求第一页,因此不存在prev链接关系)。

可以肯定地说Spring HATEOAS为JVM平台提供了最全面的超媒体和HATEOAS支持。 尽管它没有立即实现某些规范,但它允许通过一组SPI 插入自定义媒体类型 。

九头蛇

决定采用JSON-LD和Hydra的RESTful Web服务和API可能会受益于使用hydra-java库。 Spring HATEAOS扩展的出现非常令人鼓舞,但不幸的是,由于它不能与最新的Spring HATEOAS版本一起使用而被阴影掩盖 。

有了这个,我们对RESTful Web服务和API的服务器端实现有了一个很好的主意,现在该切换主题并讨论客户端。

4.在客户端上

从客户端角度,区分超媒体 API客户端的两个角度或类是有意义的:

  • (Web)用户界面(前端)上下文中的超媒体 API客户端
  • 业务任务实施(后端)环境中的超媒体 API客户端

JavaScript是Web前端开发的第一选择,而Java(通常是JVM)已经在后端方面占据了主导地位。 尽管我们将进一步讨论后者,但许多概念同样适用于两者。

那么, 超媒体 API客户端的设计和实现背后的原理是什么? 如果要强调的是一件事,那就是专注于针对超媒体规范进行编程,而不是检查服务器的响应。 媒体类型应向客户提供所有必要的详细信息,并作为实施指南。 而且,客户可能只使用一些特定的流程,而无需实现服务必须提供的所有功能。

JAX-RS

JAX-RS 2.1规范包括客户端部分,不幸的是,该客户端部分仅提供了从Link头中提取链接的方法。

final Client client = ClientBuilder.newClient();try (final Response response = client.target("https://rentals.jcg.com/").request().accept("application/prs.hal-forms+json").get()) {final Link customers = response.getLink("customers");if (customers != null) {// follow the link here }
} finally {client.close();
}

基本上,与服务器端一样 ,如果您需要完成某些工作,请准备好卷起袖子。

Crnk

Crnk框架提供了很好的客户端支持,并通过熟悉的构建块实现了这些构建块:资源,关系和存储库。

final CrnkClient client = new CrnkClient("https://rentals.jcg.com/");
client.setHttpAdapter(new OkHttpAdapter());final ResourceRepository>Customer, String< repository = client.getRepositoryForType(Customer.class);final ResourceRepository>Customer, String< repository = client.getRepositoryForType(Customer.class);
final List>Customer< customers = repository.findAll(new QuerySpec(Customer.class).setPaging(new OffsetLimitPagingSpec(0L, 10L)));if (!customers.isEmpty()) {// navigate through customers
}

如果您的RESTful Web服务和API遵循JSON:API规范,则Crnk客户端可以为您节省大量时间和精力。

Spring帽子

令人惊讶的是,直到最近SpringHATEOAS对超媒体客户端的支持还有些不完整,但是最新版本带来了许多改进 。 Traverson是Spring HATEOAS支持的最古老的机制,用于在链接和关系之间导航。

final RestTemplate template = ...;final Map>String, Object< paging = Map.of("page", 0L,"size", 2L);final CollectionModelType>Reservation< resourceType =new TypeReferences.CollectionModelType>Reservation<() {};final Traverson traverson = new Traverson(URI.create("https://rentals.jcg.com/"), MediaTypes.HAL_FORMS_JSON).setLinkDiscoverers(List.of(new HalFormsLinkDiscoverer())).setRestOperations(template);final CollectionModel>Reservation< reservations = traverson.follow(rel("reservations").withParameters(paging)).toObject(resourceType);;if (!reservations.getContent().isEmpty()) {// navigate through reservations
}

像WebClient和RestTemplate这样的更传统的通用API客户端(我们称它们为REST客户端)也已经得到了超媒体支持。

final WebClient client = builder.build();final CollectionModelType>Reservation< resourceType =new TypeReferences.CollectionModelType>Reservation<() {};
final LinkDiscoverer discoverer = new HalFormsLinkDiscoverer();final Optional>Link< link = client.get().uri("https://rentals.jcg.com/").accept(MediaTypes.HAL_FORMS_JSON).retrieve().bodyToMono(String.class).map(r -< discoverer.findLinkWithRel("reservations", r)).block();if (link.isPresent()) {final Map>String, Object< paging = Map.of("page", 0L,"size", 2L);final URI uri = link.get().getTemplate().expand(paging);final CollectionModel>Reservation< reservations = client.get().uri(uri).accept(MediaTypes.HAL_FORMS_JSON).retrieve().bodyToMono(resourceType).block();if (!reservations.getContent().isEmpty()) {// navigate through reservations}
}

您甚至可以选择Traverson , RestTemplate或WebClient ,当然可以实现功能强大的超媒体 API客户端,以完全自动化服务工作流程。

5。结论

在本教程的这一部分中,我们讨论了可帮助您在JVM平台上设计和实现超媒体驱动的RESTful Web API及其客户端的库和框架。 尽管数量不多,但您可以选择。

由于它主要是使用JavaScript在浏览器端完成的,因此我们跳过了在Web前端上下文中有关超媒体 API客户端的讨论。 在这方面,仍然值得一提的是Traverson ,它是Node.js和浏览器的超媒体 API / HATEOAS客户端(是的,这是Spring HATEAOS的灵感来源)。

6.接下来

在接下来的本教程的最后一部分,我们将总结REST体系结构样式中最被遗忘和神秘的约束( 超媒体作为应用程序状态引擎( HATEOAS ))背后的理论和实践。

7.下载源代码

下载
您可以在此处下载此示例的完整源代码: 具有HATEOAS的RESTful服务:JVM上的REST API和超媒体

翻译自: https://www.javacodegeeks.com/restful-services-with-hateoas-rest-apis-and-hypermedia-on-jvm.html

restful和rest

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/331152.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Spring中配置数据源的4种形式

【0】README 0.1&#xff09;以下内容转自&#xff1a;http://blog.csdn.net/orclight/article/details/8616103 不管采用何种持久化技术&#xff0c;都需要定义数据源。Spring中提供了4种不同形式的数据源配置方式&#xff1a; spring自带的数据源(DriverManagerDataSource)&…

MavenSelenium测试自动化教程

在进行自动化测试项目时&#xff0c;您需要与之关联的所有Selenium依赖项。 通常&#xff0c;这些依赖项是在项目生命周期中手动下载和升级的&#xff0c;但是随着项目规模的扩大&#xff0c;管理依赖项可能会非常困难。 这就是为什么需要构建自动化工具&#xff08;例如Maven&…

命令行执行Junit测试

【0】README 0.1&#xff09;本文旨在给出如何在命令行中执行 Junit测试的steps&#xff1a; 【1】在命令行中执行Junit测试 1&#xff09;problemsolution&#xff1a; 1.1&#xff09;problem&#xff1a; 1.2&#xff09;solution&#xff1a;导出 JUnitCore 类并且使用 …

托管 非托管_如何在托管Kubernetes上还原Neo4J备份

托管 非托管在下面的视频中&#xff0c;我将解释如何将Neo4J实例的备份还原到在托管Kubernetes环境中运行的新实例。 备份内容将从先前写入备份的持久卷中获取。 在上一篇文章中&#xff0c;您可以首先了解如何进行备份。 自己尝试 同样&#xff0c;您将在以下GitHub存储库中…

spring(3)高级装配

【0】README0&#xff09;本文部分文字描述转自&#xff1a;“Spring In Action&#xff08;中/英文版&#xff09;”&#xff0c;旨在review spring(3)高级装配 的相关知识&#xff1b;【1】环境与profile&#xff08;考虑数据库配置&#xff09;1&#xff09;使用嵌入式数据…

jmc线程转储_查找线程转储中的异常和错误

jmc线程转储线程转储是解决生产问题/调试生产问题的重要工件。 在像过去我们已经讨论了几种有效的线程转储故障模式&#xff1a; 堵车 &#xff0c; 跑步机 &#xff0c; RSI &#xff0c; 一个 LL条条大路通罗马 .........。 在本文中&#xff0c;我们想介绍另一种线程转储故障…

命令模式的两种不同实现

转载自 命令模式&#xff08;Command&#xff09;的两种不同实现命令模式&#xff08;Command&#xff09;&#xff1a;将一个请求封装成一个对象&#xff0c;使得你用不同的请求把客户端参数化&#xff0c;对请求排队或者记录请求日志&#xff0c;可以提供命令的撤销和恢复功能…

tomcat(18)部署器

【0】README-1&#xff09;先上干货&#xff1a;本文重点分析了tomcat 如何部署WAR文件的项目形式 以及 普通文件夹的项目形式&#xff1b;不管是WAR文件 还是 普通文件夹的项目形式&#xff0c;在tomcat中&#xff0c;它们都是Context容器&#xff1b;&#xff08;Bingo&#…

装饰器模式和代理模式的区别

转载自 装饰器模式和代理模式的区别学习AOP时&#xff0c;教材上面都说使用的是动态代理&#xff0c;可是在印象中代理模式一直都是控制访问什么的&#xff0c;怎么又动态增加行为了&#xff0c;动态增加行为不是装饰器模式吗&#xff1f;于是找了很多资料&#xff0c;想弄清楚…

使用Java 8 Stream像操作SQL一样处理数据(上)

转载自 使用Java 8 Stream像操作SQL一样处理数据&#xff08;上&#xff09; 几乎每个Java应用都要创建和处理集合。集合对于很多编程任务来说是一个很基本的需求。举个例子&#xff0c;在银行交易系统中你需要创建一个集合来存储用户的交易请求&#xff0c;然后你需要遍历整个…

tomcat(19)Manager应用程序的servlet类

【0】README1&#xff09;本文部分文字描述转自&#xff1a;“深入剖析tomcat”&#xff0c;旨在学习“tomcat(19)Manager应用程序的servlet类” 的相关知识&#xff1b;2&#xff09;Manager应用程序用来管理已经部署的web 应用程序&#xff1b;在tomcat7中&#xff0c;manage…

使用Java 8 Stream像操作SQL一样处理数据(下)

转载自 使用Java 8 Stream像操作SQL一样处理数据&#xff08;下&#xff09; 在上一篇文章中&#xff0c;我们介绍了Stream可以像操作数据库一样来操作集合&#xff0c;但是我们没有介绍 flatMap 和 collect 操作。这两种操作对实现复杂的查询是非常有用的。比如你可以结果 fl…

spring(4)面向切面的Spring(AOP)

【0】README1&#xff09;本文部分文字描述转自&#xff1a;“Spring In Action&#xff08;中/英文版&#xff09;”&#xff0c;旨在review “spring(4)面向切面的Spring&#xff08;AOP&#xff09;”的相关知识&#xff1b;2&#xff09;在软件开发中&#xff0c;散布于应…

Mybatis-plus 思维导图,让 Mybatis-plus 不再难懂

转载自 Mybatis-plus 思维导图&#xff0c;让 Mybatis-plus 不再难懂 摘要: Mybatis-Plus&#xff08;简称MP&#xff09;是一个Mybatis的增强工具&#xff0c;在 Mybatis 的基础上只做增强不做改变&#xff0c;为简化开发、提高效率而生。mybatis-plus与mybatis mybatis Mybat…

报错:The type javax.servlet.http.HttpServletRequest cannot be resolved

【0】README 0.1&#xff09;以下内容转自&#xff1a; http://blog.csdn.net/vincent080928/article/details/5392891 problem&#xff09;The type javax.servlet.http.HttpServletRequest cannot be resolved. It is indirectly referenced from required .class files. 这…

MyBatis 思维导图,让 MyBatis 不再难懂(一)

转载自 MyBatis 思维导图&#xff0c;让 MyBatis 不再难懂&#xff08;一&#xff09;写在前面与hibernate相比&#xff0c;我无疑更喜欢mybatis&#xff0c;就因为我觉得它真的好用&#xff0c;哈哈。它简单上手和掌握&#xff1b;sql语句和代码分开&#xff0c;方便统一管理和…

tomcat(20)基于JMX的管理

【0】README1&#xff09;本文部分文字描述转自&#xff1a;“深入剖析tomcat”&#xff0c;旨在学习“tomcat(20)基于JMX的管理” 的相关知识&#xff1b;2&#xff09;晚辈我在tomcat上部署web 项目以测试JMX管理 tomcat 容器bean的效果&#xff0c;结果运行不成功&#xff0…

mybatis思维导图,让mybatis不再难懂(二)

转载自 mybatis思维导图&#xff0c;让mybatis不再难懂&#xff08;二&#xff09; 写在前面 上一篇文章写了mybatis的基本原理和配置文件的基本使用&#xff0c;这一篇写mybatis的使用&#xff0c;主要包括与sping集成、动态sql、还有mapper的xml文件一下复杂配置等。值得注意…

spring(5)构建 spring web 应用程序

【0】README1&#xff09;本文部分文字描述转自&#xff1a;“Spring In Action&#xff08;中/英文版&#xff09;”&#xff0c;旨在review “spring(5)构建 spring web 应用程序” 的相关知识&#xff1b;【1】spring mvc 起步【1.1】跟踪spring mvc的请求1&#xff09;请求…

Spring思维导图,让Spring不再难懂(ioc篇)

转载自 Spring思维导图&#xff0c;让Spring不再难懂&#xff08;ioc篇&#xff09; 写过java的都知道&#xff1a;所有的对象都必须创建&#xff1b;或者说&#xff1a;使用对象之前必须先创建。而使用ioc之后&#xff0c;你就可以不再手动创建对象&#xff0c;而是从ioc容器中…