几乎所有的编程语言都支持REST服务的开发。其中,Java一直跟进最新的企业应用开发的规范的支持。在REST开发领域,Java用于开发REST服务的规范,主要是JAX-RS规范。该规范使得Java程序员可以使用一套固定、统一的接口来开发REST应用,从而避免了依赖于第三方框架。同时,JAX-RS使用POJO编程模型和基于注解的配置,并集成了JAXB,从而可以有效缩短REST应用的开发周期。Java EE 6引入了对JSR-311的支持,Java EE 7支持JSR-339规范。
JAX-RS定义的API位于javax.ws.rs包中。
伴随着JSR 311规范的发布,Sun同步发布了该规范的参考实现Jersey。JAX-RS的具体实现第三方还包括Apache的CXF以及JBoss的RESTEasy等。未实现该规范的其他REST框架还包括Spring Web MVC等。
随着REST的流行,Java开始着手制订REST开发规范,并在Java EE6引入了对JSR-311的支持。JAX-RS是一个社区驱动的标准,用于使用Java构建RESTful Web服务。它不仅定义了一套用于构建RESTful网络服务的API,同时也通过增强客户端API功能简化了REST客户端的构建过程。从2007年诞生至今,JAX-RS经过了多次重大版本的发布,也经过多次的提案与审查,终于在2017年8月22日发布了2.1版本(JSR-370)。
JAX-RS规范制订了以下目标。
·基于POJO的API。该规范所定义的API将提供一组注解和相关的类、接口,可用于POJO以将它们公开为Web资源。同时,该规范将定义对象生命周期和范围。·以HTTP为中心。该规范将假定HTTP是底层网络协议,并将提供HTTP和URI元素以及相应的API类和注解之间的明确映射。API将为常见的HTTP使用模式提供高级别的支持,并且将具有足够的灵活性来支持各种HTTP应用程序,包括WebDAV和Atom发布协议。
·格式独立性。该规范所定义的API将适用于各种各样的HTTP实体主体内容类型。它将提供必要的可插入性,从而允许应用程序以标准方式添加其他类型。
·容器独立性。使用该规范所定义的API的部件可以部署在各种Web容器中。该规范定义了如何在Servlet容器和JAX-WS提供程序中部署部件。
·包含在Java EE中。规范将定义托管在Java EE容器中的Web资源类的环境,并将指定如何在Web资源类中使用Java EE功能和组件。
JAX-RS规范包含了以下核心概念。
根资源类(Root Resource Classes)是带有@PATH注解的,包含至少一个@PATH注解的方法或者方法带有@GET、@PUT、@POST、@DELETE资源方法指示器的POJO。资源方法是带有资源方法指示器(Resource Method Designator)注解的方法。
下面这段代码就是一个带有JAX-RS注解的简单示例。
import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
@Path("helloworld")
public class HelloWorldResource {
public static final String CLICHED_MESSAGE = "Hello World!";
@GET
@Produces("text/plain")
public String getHello() {
return CLICHED_MESSAGE;
}
}
其中,@Path是一个URI的相对路径,在上面的例子中,设置的是本地的URI的/helloworld。这是一个非常简单的关于@Path的例子,有时,我们也会嵌入变量到URI里面。URI的路径模板是由URI和嵌入URI的变量所组成的。变量在运行时将会被匹配到的URI的那部分所代替。
例如下面的@Path注解。
@Path("/users/{username}")
按照这种类型的例子,一个用户可以方便地填写他的名字,同时服务器也会按照这个URI路径模板响应这个请求。例如用户输入了名字“Wa”,那么服务器就会响应
http://example.com/users/Way。
为了接收到用户名变量,@PathParam用在接收请求的方法的参数上,例如:
@Path("/users/{username}")
public class UserResource {
@GET
@Produces("text/xml")
public String getUser(@PathParam("username") String userName) {
...
}
}
@GET、@PUT、@POST、@DELETE、@HEAD是JAX-RS定义的注解,它非常类似于HTTP的方法名。在上面的例子中,这些注解是通过HTTP的GET方法实现的。资源的响应就是HTTP的响应。
资源方法中,带有基于参数注解的参数可以从请求中获取信息。前面的一个例子就是在匹配了@Path之后,通过@PathParam来获取URL请求中的路径参数。
@QueryParam用于从请求URL的查询组件中提取查询参数。观察下面的例子。
@Path("smooth")
@GET
public Response smooth(
@DefaultValue("2") @QueryParam("step") int step,
@DefaultValue("true") @QueryParam("min-m") boolean hasMin,
@DefaultValue("true") @QueryParam("max-m") boolean hasMax,
@DefaultValue("true") @QueryParam("last-m") boolean hasLast,
@DefaultValue("blue") @QueryParam("min-color") ColorParam minColor,
@DefaultValue("green") @QueryParam("max-color") ColorParam maxColor,
@DefaultValue("red") @QueryParam("last-color") ColorParam lastColor)
{
...
}
如果step的参数存在,那么赋值给它,否则默认是@DefaultValue定义的值2。如果step的内容不是32位的整型,那么会返回404错误。
@PathParam和其他参数注解@MatrixParam、@HeaderParam、@CookieParam以及@FormParam遵循与@QueryParam一样的规则。
@MatrixParam从URL路径提取信息。@HeaderParam从HTTP头部提取信息,@CookieParam从关联在HTTP头部的cookies里提取信息。
@FormParam稍有特殊,因为它提取信息,要求MIME媒体类型必须为“application/x-www-form urlencoded”,并且符合指定的HTML编码的形式。此参数提取对于HTML表单请求是非常有用的,例如从发布的表单数据中提取名称是name的参数信息。
@POST
@Consumes("application/x-www-form-urlencoded")
public void post(@FormParam("name") String name) {
// ...
}
另一种注解是@BeanParam允许注入一个bean到参数中。
@BeanParam可以用于注入这种bean到资源或资源的方法。以下是@BeanParam的用法。
public class MyBeanParam {
@PathParam("p")
private String pathParam;
@MatrixParam("m")
@Encoded
@DefaultValue("default")
private String matrixParam;
@HeaderParam("header")
private String headerParam;
private String queryParam;
public MyBeanParam(@QueryParam("q") String queryParam) {
this.queryParam = queryParam;
}
public String getPathParam() {
return pathParam;
}
...
}
将MyBeanParam以参数形式注入。
@POST
public void post(@BeanParam MyBeanParam beanParam, String entity) {
final String pathParam = beanParam.getPathParam(); // contains injected path
parameter "p"
// ...
}
@Path可以用在类上,这样的类称为根资源类,它也可以被用在根资源类的方法上。这使得许多资源的方法被组合在一起,能够被重用。
@Path是用在资源的方法上,这类方法被称为子资源方法(Sub-Resource Method)。
以下是一个完整的根资源类和子资源方法的示例。
@Singleton
@Path("/printers")
public class PrintersResource {
@GET
@Produces({
"application/json", "application/xml"})
public WebResourceList getMyResources() {
... }
@GET @Path("/list")
@Produces({
"application/json", "application/xml"})
public WebResourceList getListOfPrinters() {
... }
@GET @Path("/jMakiTable")@Produces("application/json")
public PrinterTableModel getTable() {
... }
@GET @Path("/jMakiTree")
@Produces("application/json")
public TreeModel getTree() {
... }
@GET @Path("/ids/{printerid}")
@Produces({
"application/json", "application/xml"})
public Printer getPrinter(
@PathParam("printerid") String printerId) {
... }
@PUT @Path("/ids/{printerid}")
@Consumes({
"application/json", "application/xml"})
public void putPrinter(@PathParam("printerid") String printerId,
Printer printer) {
... }
@DELETE @Path("/ids/{printerid}")
public void deletePrinter(
@PathParam("printerid") String printerId) {
... }
}
默认情况下,根资源类的生命周期是每个请求,即根资源类的新实例在每次请求的URI路径匹配根资源时创建。利用构造函数和范围可以构造一个很自然的编程模型,而无须关心对同一资源的多个并发请求。
总的来说这不太可能成为导致性能问题的原因。近年来,类的构造以及JVM的GC已大大改善,在服务和处理HTTP请求并返回HTTP响应中,许多对象将被创建和丢弃。
单个的根资源类的实例可以通过一个应用实例声明。
使用Jersey特定注解让Jersey拥有更多生命周期管理。下表所示为根资源类生命周期。
注入可以用在属性、构造方法参数、资源/子资源/子资源定位方法的参数和bean setter方法上。以上介绍的这些注入的情况具体如下。
@Path("{id:\\d+}")
public class InjectedResource {
// 注入属性
@DefaultValue("q") @QueryParam("p")
private String p;
// 注入构造函数参数
public InjectedResource(@PathParam("id") int id) {
... }
// 注入资源参数
@GET
public String get(@Context UriInfo ui) {
... }
// 注入子资源方法参数
@Path("sub-id")
@GET
public String get(@PathParam("sub-id") String id) {
... }
// 注入子资源方法参数定位器方法参数
@Path("sub-id")
public SubResource getSubResource(
@PathParam("sub-id") String id) {
... }
// 注入 bean setter 方法
@HeaderParam("X-header")
public void setHeader(String header) {
... }
}
有一些限制,当注入一个生命周期为单例的资源类时,类的属性或构造函数的参数不能被注入请求特定的参数。例如,以下是不允许的。
@Path("resource")@Singleton
public static class MySingletonResource {
@QueryParam("query")
String param; //错误:不能将特定参数注入单例资源,否则会使程序初始化失败
@GET
public String get() {
return "query param: " + param;
}
}
上面的例子验证了应用程序不能为单例资源注入请求特定的参数,否则验证失败。同样的例子,如果查询的参数将被注入一个单例构造函数参数则失败。换句话说,如果你希望一个资源实例的服务被很多次请求,则资源实例不能绑定到一个特定的请求参数上。
存在一个例外,特定请求对象可以注入构造函数或类属性。这些对象的运行时注入的代理可以同时服务多个请求。这些请求的对象是HttpHeaders、Request、UriInfo、SecurityContext。这些代理可以使用@Context注解进行注入。下面的示例展示将代理注入单例资源类。
@Path("resource")
@Singleton
public static class MySingletonResource {
@Context
Request request; // 这个是允许的:
//请求的代理将会被注入单例
public MySingletonResource(
@Context SecurityContext securityContext) {
// 这个也是允许的:
// SecurityContext的代理将会被注入单例
}
@GET
public String get() {
return "query param: " + param;
}
}
Jersey是官方JAX-RS规范的参考实现,可以说是全面地实现了JAX-RS规范所定义的内容。Jersey框架是开源的。Jersey框架不仅是JAX-RS参考实现,还提供了自己的API,它扩展了JAX-RS工具包的附加功能和实用程序,以进一步简化RESTful服务和客户端开发。
Jersey也公开了大量的扩展SPI,以便开发人员可以扩展Jersey以满足他们的需求。
Jersey项目的目标可以归纳为以下3点。
跟踪JAX-RS API,并定期发布GlassFish所定义的参考实现。
提供API来扩展Jersey,不断构建用户和开发人员的社区。
使用Java和Java虚拟机可以轻松构建RESTful Web服务。
Apache CXF是另外一款支持JAX-RS的框架。除了支持JAX-RS外,Apache CXF还支持传统的JAX-WS协议。Apache CXF可以使用各种协议,如SOAP、XML/HTTP、RESTful HTTP或CORBA,并可用于各种传输,如HTTP、JMS或JBI。Apache CXF支持JAX-RS 2.0(JSR-339)以及JAX-RS 1.1(JSR-311)。
同样Apache CXF也是开源的,具有高性能、可扩展、易于使用等特点。
Spring Web MVC框架,也简称为“Spring MVC”,实现了Web开发中的经典的MVC(Model-View-Controller)模式。MVC由以下3个部分组成。
如果你觉得自己学习效率低,缺乏正确的指导,可以加入资源丰富,学习氛围浓厚的技术圈一起学习交流吧!
[Java架构群]
群内有许多来自一线的技术大牛,也有在小厂或外包公司奋斗的码农,我们致力打造一个平等,高质量的JAVA交流圈子,不一定能短期就让每个人的技术突飞猛进,但从长远来说,眼光,格局,长远发展的方向才是最重要的。
模型(Model):应用程序的核心功能,管理这个模块中用到的数据和值。
视图(View):视图提供模型的展示,管理模型如何显示给用户,它是应用程序的外观。
控制器(Controller):对用户的输入做出反应,管理用户和视图的交互,是连接模型和视图的枢纽。
Spring Web MVC是基于Servlet API来构建的,自Spring框架诞生之日起,它就包含在Spring里面了。严格意义上来说,Spring Web MVC并没有遵守JAX-RS规范,所以也称不上是REST框架。但是,Spring WebMVC所暴露的接口,可以是REST风格的API,所以自然也能拿来开发REST服务。
给大家分享一篇一线开发大牛整理的java高并发核心编程神仙文档,里面主要包含的知识点有:多线程、线程池、内置锁、JMM、CAS、JUC、高并发设计模式、Java异步回调、CompletableFuture类等。
文档地址:一篇神文就把java多线程,锁,JMM,JUC和高并发设计模式讲明白了
码字不易,如果觉得本篇文章对你有用的话,请给我一键三连!关注作者,后续会有更多的干货分享,请持续关注!