文章目录

  • RestEasy的入门与使用
    • 背景
      • 传统Servlet
    • JAX-RS和JSR
      • 为什么要看规范?
      • 简单解读JSR370
        • Applications
        • Resources
        • Providers
    • RestEasy的基本使用
      • 建立Restful风格的JavaEE应用
        • RestEasy是如何接管了应用呢?
      • 接受Restful风格的HTTP请求
        • 基本使用
        • 进阶使用
      • 全局异常的处理
    • 备注
    • 更新日志

RestEasy的入门与使用

对于Restful风格应用的框架,平时用的最多的应该就是SpringBoot,SpringMVC了。但是也有另外一派,就是使用完全实现JavaEE标准的框架。所以本文会在完全实现JavaEE(相关JSR标准)的框架上进行讨论,不引入Spring。

背景

以前在JavaEE(Java Enterprise Edition)中如何处理HTTP请求呢?

是使用Servlet,具体来说在所谓的“控制层”通过继承HttpServlet来进行处理.例如

传统Servlet

public class indexServlet extends HttpServlet {@Overrideprotected void doGet(final HttpServletRequest req, final HttpServletResponse resp) throws ServletException, IOException {// handle request  req.getParameter("parameterName");......// return responseresp.getWriter().write("something");}
}

web.xml配置

<?xml version="1.0" encoding="UTF-8" ?>
<web-appxmlns="http://xmlns.jcp.org/xml/ns/javaee"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd"version="4.0"><servlet><servlet-name>indexServlet</servlet-name><servlet-class>IndexServlet</servlet-class></servlet><servlet-mapping><servlet-name>indexServlet</servlet-name><url-pattern>index/*</url-pattern></servlet-mapping>
</web-app>

到后来可以支持@WebServlet达到和上面一样的效果

但是随着RESTFUl架构风格越来越成为主流,Java业界并没有对JavaEE中如何使用Restful风格进行一个标准的定义

JAX-RS和JSR

JAX-RS是JavaEE规范的一部分,对应的JSR(Java Specification Request)-Java规范提案最新的是JSR370,对应JAX-RS版本2.1

在线查看JSR370地址

为什么要看规范?

  • 在我们使用一个具体实现了某个JSR的框架时,提前了解规范,能够帮助我们更好得理解具体框架的原理以及思路。
  • 一般JSR规范都是以接口形式定义。了解相关接口,有助于我们快速找到具体实现框架的对应位置。例如,我们知道JSR的某个接口A是处理Servlet初始化相关的,那么我们可以根据这个接口查找其具体实现,可以快速定位到我们想要或者应该使用的具体框架的类上。同时。了解某个JSR规范的接口,可以更好的帮助我们定位什么时候该用什么接口及其实现,而不是去网上各种百度,XXX要怎么做

简单解读JSR370

在JSR370目录中,我们可以看到整体的内容大概有12章。作为入门,我们简单了解第2、3、4章即可

Applications

A JAX-RS application consists of one or more resources (see Chapter 3) and zero or more providers (see Chapter 4). This chapter describes aspects of JAX-RS that apply to an application as a whole, subsequent chapters describe particular aspects of a JAX-RS application and requirements on JAX-RS implementations

可以看到,JAX-RS对于Application的定位是主要有Resource和Provider这两个内容组成。可以将它视为一个全局的servlet,处理HTTP的入口点。

使用方式

  • 继承Application
  • 在web.xml中声明

可以看到这里只有一个servlet了,而不像传统的servlet项目,有多少个“控制器”,就要在web.xml中配置多少个servlet

Resources

一句话解释什么是resources

A resource class is a Java class that uses JAX-RS annotations to implement a corresponding Web resource. Resource classes are POJOs that have at least one method annotated with @Path or a request method designator

资源类是被@Path注解标注的方法所在的类,有过SpringMVC开发经验的人这里应该很好看懂。使用的注解也和SpringMVC大同小异。

例如 @GET, @POST, @PUT, @DELETE, @PATCH, @HEAD and @OPTIONS 标注HTTP方法类型

还有@QueryParam , @PathParam等接收url参数和路径参数。

@Produces和@Consumes分别代表响应类型、接受的请求类型

入门的话知道这几个注解就可以了。想要了解更多可以查看JSR370在线pdf的第3.2和3.5章

Providers

Providers in JAX-RS are responsible for various cross-cutting concerns such as filtering requests, converting representations into Java objects, mapping exceptions to responses, etc. A provider can be either pre-packaged in the JAX-RS runtime or supplied by an application. All application-supplied providers implement interfaces in the JAX-RS API and MAY be annotated with @Provider for automatic discovery purposes; the integration of pre-packaged providers into the JAX-RS runtime is implementation dependent. This chapter introduces some of the basic JAX-RS providers; other providers are introduced in Chapter 5 and Chapter 6

Provider是程序中公共的部分。比如各种Filter、读取和响应的处理(将JSON转成对应实体类类似这种)-如果在传统的servlet中,处理各种各样的Content-Type我们怎么处理?思路大概就是搞一个filter,拦截Request或者Response,自己再根据实际场景自己手动去进行HttpServletRequest.getParameters处理等等,在JAX-RS中,也提供了最基本的接口。如果我们要自己实现JAX-RS的话,实现这些基本的接口,利用上面说的这种思路,处理各种不同的情形以达到满足我们使用的需求,那么我们也可以称之为实现了JAX-RS标准。

自己也可以实现JAX-RS规范中的接口,并用@Provider注解标注,达到自定义Provider的目的。

RestEasy的基本使用

上面提到过,JAX-RS只提供了最基本的接口,因为这些基本接口只是大家的共识。但是离真正在应用上使用,还需要实现这些接口才可以。如果大家都去各自实现这些接口,那么每个使用Restful风格的应用代码量也很多,例如最最原始的Servlet中,我们能拿到的就是request和response,如果想要处理各种请求和响应,是不是要对request做各种解析,对response的写入又做各种适配?为了帮助开发者专注于业务,降低开发Restful风格应用的难度,RestEasy出现了。它完全实现了JSR370标准,使我们普通的业务开发者,无需关心Request到各种类型的转换以及Response的各种响应处理等这些功能。直接在它之上做我们想要做的业务即可

建立Restful风格的JavaEE应用

本示例将使用Tomcat9作为JavaEE应用的部署容器。Tomcat是一个Servlet容器,并不是一个完全支持JavaEE标准的JavaEE容器。但是可以通过简单改造,可以使得Tomcat也可以部署JavaEE应用。

由于Tomcat9支持Servlet4,而RestEasy在Servlet3以上版本可以很轻松的配置。所以这里选择Tomcat9

@ApplicationPath("/auth")
public class AuthorizationServerApplication extends Application {}

仅仅需要上述一行代码,便可以让RestEasy自动完成Restful应用的启动配置,而传统的Servlet下的应用,还有可能是需要配置web.xml。

ApplicationPath代表这个Web应用的起始路径。

注意:也有一些文章,例如上面介绍Application时文档给的示例那样。在介绍RestEasy入门使用的时候,会告知读者,需要在web.xml中配置带有ApplicationPath的类的完全限定名,但那都是针对Servlet3以前的web应用了。对于支持Servlet3以上的Servlet容器,使用RestEasy的时候是不需要配置的.有关更详细的信息请移步RestEasy官方文档关于Servlet容器配置

RestEasy是如何接管了应用呢?

答案在于RestEasy实现了ServletContainerInitializer这个接口

这个接口是Servlet3.0添加的,可以查看最新的Servlet4.0规范文档的第四章Servlet Context

可以简单理解为在Servlet容器初始化时候做一些工作,可以参考详细的Javadoc

简单放几张截图来说明ResteasyServletInitializer做的事情



可以从这几张图里基本可以看出来,ResteasyServletInitializer做的事情就是注册Resource和Provider,并添加一个Servlet.

读者想了解更多,则直接去看ResteasyServletInitializer类的源码即可,很简洁的代码,不到200行。并且RestEasy是通过SPI的形式,将ResteasyServletInitializer注册进去

这样在容器启动完毕的时候,就注册了Resource、Provider并且添加了一个全局处理HTTP的Servlet

拓展:

  • 其实Spring-web也实现了这个接口,感兴趣的可以去看下SpringServletContainerInitializer这个类里面做的事情,可以结合关键的SpringBootServletInitializer这个类一起来看
  • 关于ServletContainerInitializer的自定义实现可以参考这个示例
  • 关于javax.servlet.Registration#setInitParameter方法的作用

接受Restful风格的HTTP请求

在JAX-RS中, 被@Path注解的方法被定义为Resource.我们平时通过浏览器或其他方式(例如curl)发起的HTTP请求,请求服务器的时候,被视作对一个资源的访问。

接下来就给出几个基本的RestEasy的例子,来说明具体注解的使用。如果有SpringMVC的使用经验,应该很好理解

基本使用

基本使用中给出普通GET、POST、DELETE的使用

/*** basic use for RestEasy*/
@Log
@Path("hello")
public class HelloResource {private static List<UserVO> users = new ArrayList<>();/*** curl http:localhost:8080/auth/hello/jack*/@GET@Path("{name}")@Produces(MediaType.TEXT_PLAIN)public String hello(@PathParam("name") String name) {return "Hello " + name.toUpperCase();}/*** RestEasy提供了高级的@PathParam注解,可以不不用声明path的值,只要变量名字和路径变量一致即可* <a href="https://docs.jboss.org/resteasy/docs/3.8.1.Final/userguide/html/_NewParam.html">使用文档</a>*  搭配Maven compiler插件使用*  curl http:localhost:8080/auth/hello/advanced/jack*/@GET@Path("advanced/{name}")@Produces(MediaType.TEXT_PLAIN)@DenyAllpublic String helloRestEasy(@org.jboss.resteasy.annotations.jaxrs.PathParam String name) {return "Hi,there " + name;}/*** curl -X POST -H 'Content-Type: application/json' -d '{"userName":"jack", "age":18"}' http://localhost:8080/auth/hello/users*/@POST@Path("users")@Consumes(MediaType.APPLICATION_JSON)@Produces(MediaType.APPLICATION_JSON)public Response createUser(@Valid UserVO userVO) {users.add(userVO);return Response.ok().entity(userVO).build();}/*** curl http:localhost:8080/auth/hello/users/0*/@GET@Path("users/{index}")@Produces(MediaType.APPLICATION_JSON)public Response queryUser(@PathParam("index") Integer index) {checkParameter(index);UserVO targetUser = users.get(index);return Response.ok().entity(targetUser).build();}/*** curl -X DELETE http://localhost:8080/auth/hello/users/0*/@DELETE@Path("users/{index}")public Response deleteUser(@PathParam("index") Integer index) {checkParameter(index);users.remove(index);return Response.ok().build();}private void checkParameter(Integer index) {if (users.size() == 0 || Objects.isNull(index) ||  index < 0 || index >= users.size()) {throw new WebApplicationException("user Not Found, please enter valid index", Response.Status.NOT_FOUND);}}
}

进阶使用

进阶使用给出处理表单和文件上传、下载的示例

private final String UPLOADED_FILE_PATH = "D:\\tmp\\";/*** 接收表单类型数据* curl -X POST -d 'username=jack&age=18' http://localhost/auth/hello/users*/@POST@Path("users")@Consumes(MediaType.APPLICATION_FORM_URLENCODED)public Response createFromFrom(@Form UserVO userVO) {if (Objects.isNull(userVO)) {throw new WebApplicationException(Status.BAD_REQUEST);}users.add(userVO);return Response.ok().build();}/*** 接受一个或多个文件.* curl -F 'fileName=@pictureLocation/upload.png' http://localhost:8080/auth/hello/file*/@POST@Path("file")@Consumes(MediaType.MULTIPART_FORM_DATA)public Response uploadImage(MultipartFormDataInput input) {//Get API input dataMap<String, List<InputPart>> uploadForm = input.getFormDataMap();List<InputPart> inputParts = uploadForm.get("fileName");if (Objects.isNull(inputParts)) {throw new WebApplicationException("no upload file, please upload file", Status.BAD_REQUEST);}StringBuilder uploadFileName = new StringBuilder();for (InputPart inputPart : inputParts) {// convert the uploaded file to inputstreamtry(InputStream inputStream = inputPart.getBody(InputStream.class, null)) {//Use this header for extra processing if requiredMultivaluedMap<String, String> header = inputPart.getHeaders();String fileName = getFileName(header);uploadFileName.append(fileName).append(",");// 有内存溢出的危险byte[] bytes = IOUtils.toByteArray(inputStream);// constructs upload file pathfileName = UPLOADED_FILE_PATH + fileName;writeFile(bytes, fileName);log.info("Success !!!!!");} catch (Exception e) {log.log(Level.WARNING, "upload file failed", e);throw new WebApplicationException(e.getMessage(), Status.INTERNAL_SERVER_ERROR);}}return Response.status(200).entity("File uploaded successfully.Uploaded file name : "+ uploadFileName.substring(0, uploadFileName.length()-1)).build();}/*** header sample* {*     Content-Type=[image/png],*     Content-Disposition=[form-data; name="file"; filename="filename.extension"]* }**/private String getFileName(MultivaluedMap<String, String> header) {String[] contentDisposition = header.getFirst("Content-Disposition").split(";");for (String filename : contentDisposition) {if ((filename.trim().startsWith("filename"))) {String[] name = filename.split("=");return name[1].trim().replaceAll("\"", "");}}return "unknown";}private void writeFile(byte[] content, String filename) throws IOException {File file = new File(filename);if (!file.exists()) {file.createNewFile();}FileOutputStream fop = new FileOutputStream(file);fop.write(content);fop.flush();fop.close();}/*** curl -o download.png http://localhost:8080/auth/hello/file/upload.png* @param fileName* @return*/@GET@Path("file/{fileName}")@Produces("image/png")public Response downLoadImage(@org.jboss.resteasy.annotations.jaxrs.PathParam String fileName) {if(fileName == null || fileName.isEmpty()) {ResponseBuilder response = Response.status(Status.BAD_REQUEST);return response.build();}//Prepare a file object with file to returnFile file = new File(UPLOADED_FILE_PATH + fileName);ResponseBuilder response = Response.ok(file);response.header("Content-Disposition", "attachment; filename="+fileName);return response.build();}

相信看过上面的示例,读者可以很轻松的完成基本业务的开发了

全局异常的处理

更进一步,在上面的示例中,面对有问题的请求,我们是直接抛了一个异常,那么异常抛出之后,为了对客户端进行友好的提示,我们需要一个处理这种异常的机制。在SpringBoot中处理全局异常,我们使用@ExceptionHandler和@RestControllerAdvice来处理。JAX-RS也提供了类似的方式来处理,那就是实现ExceptionMapper接口,同时使用@Provider注解。结合这个示例是不是可以更好的理解了Provider在整个JAX-RS中的角色

@Provider
public class GlobalWebExceptionMapper implements ExceptionMapper<WebApplicationException> {@Overridepublic Response toResponse(WebApplicationException e) {return Response.status(e.getResponse().getStatus()).type(MediaType.APPLICATION_JSON).entity(new ExceptionData(e.getMessage())).build();}
}

备注

  • 完整示例代码仓库
  • 在线查看某一个jar包的Javadoc网址.原来我也觉得这种Javadoc看起来好无聊。但是随着编程越来越多,特别是不熟悉的jar包让你使用时,或者你不知道自己要实现的功能需要哪个接口、类时,查找Javadoc是非常快速的解决办法

更新日志

  • 源码的JDK已经升级到JDK11
  • Servlet4升级到Servlet6,包名从javax全部变更为jakarta,web.xml的描述符也已经修改
  • JSTL升级至2.0
  • Tomcat9升级到Tomcat10
  • Hibernate、Jackson均已升级
  • Weld CDI已经升级至最新,并更新了webapp/META-INF/context.xml下的相关配置

RestEasy的入门与使用相关推荐

  1. RESTEasy入门

    RESTEasy入门 博客分类: RESTEasy mavenWebServiceJBossServletWeb  RESTEasy是JBoss的开源项目之一,是一个RESTful Web Servi ...

  2. quarkus_Quarkus入门

    quarkus Quarkus – 一个针对OpenJDK HotSpot和GraalVM量身定制的Kubernetes本机Java堆栈,它是从最佳Java库和标准中精制而成的. –是一个容器优先的框 ...

  3. Java 云原生微服务框架 Quarkus 入门实践

    点击上方"芋道源码",选择"设为星标" 管她前浪,还是后浪? 能浪的浪,才是好浪! 每天 10:33 更新文章,每天掉亿点点头发... 源码精品专栏 原创 | ...

  4. 带有RESTEasy + JAXB + Jettison的JSON示例

    RESTEasy使用Jettison JSON库在JSON之间来回映射JAXB注释对象. 在本教程中,我们向您展示如何将带​​有JAXB注释的对象转换为JSON格式并将其返回给客户端. 杰克逊(Jac ...

  5. 用Construct 2制作入门小游戏~

    今天在软导课上了解到了Construct 2这个神器,本零基础菜鸟决定尝试做一个简单的小游戏(实际上是入门的教程啊= = 首先呢,肯定是到官网下载软件啊,点击我下载~ 等安装完毕后我便按照新手教程开始 ...

  6. Docker入门六部曲——Swarm

    原文链接:http://www.dubby.cn/detail.html?id=8738 准备工作 安装Docker(版本最低1.13). 安装好Docker Compose,上一篇文章介绍过的. 安 ...

  7. Docker入门六部曲——Stack

    原文链接:http://www.dubby.cn/detail.html?id=8739 准备知识 安装Docker(版本最低1.13). 阅读完Docker入门六部曲--Swarm,并且完成其中介绍 ...

  8. Docker入门六部曲——服务

    原文链接:http://www.dubby.cn/detail.html?id=8735 准备 已经安装好Docker 1.13或者以上的版本. 安装好Docker Compose.如果你是用的是Do ...

  9. 【springboot】入门

    简介: springBoot是spring团队为了整合spring全家桶中的系列框架做研究出来的一个轻量级框架.随着spring4.0推出而推出,springBoot可以説是J2SEE的一站式解决方案 ...

最新文章

  1. Jenkins 流水线 获取git 分支列表_基于Jenkins的DevOps流水线实践课程
  2. 中科视拓开源SeetaFace2
  3. 记一次云服务器被入侵
  4. Codeforces Round #601 (Div. 2) E2. Send Boxes to Alice (Hard Version) 思维 + 质因子
  5. 还有人不知道什么是AndroidX的吗?文末领取面试资料
  6. 2018年阿里云NoSQL数据库大事盘点
  7. EularProject 39:给周长推断构成直角三角形个数
  8. leetcode 236. 二叉树的最近公共祖先LCA(后序遍历,回溯)
  9. 苹果 macOS 再曝漏洞,输任意密码可进入 App Store 首选项
  10. 有关top命令中的%st,sar命令中的%steal .
  11. 软件设计师考试大纲2018
  12. 项目经理所应具备的八项素质:
  13. ftp文件服务器怎么迁移,ftp文件服务器迁移
  14. GD32F4(2): 用keil5打开官方评估版demo,编译报错找不到core_cm4.h文件
  15. 面试金典12(Python)—— 删除中间节点(简单)
  16. Word学习笔记分享
  17. 通常人们说i5处理器的计算机其中二五是指,计算机cpu?学会看CPU只要五分钟
  18. 计算机组成原理(8)CPU——基本结构
  19. delphi android 打印机,用delphi控制小票打印机打印图片
  20. 智能小车的超声波避障

热门文章

  1. 目前微型计算机中广泛使用的显示卡是,计算机应用基础题库
  2. redis读写分离 java_Redis主从实现读写分离
  3. 基于Java+SpringBoot+Vue前后端分离学生管理系统设计与实现
  4. ulimit命令的使用
  5. Echarts实现堆叠柱状图+折线图
  6. Java 分布式生成ID—雪花算法
  7. exception in thread “main” org.apache.hadoop.hadooplllcgalargumentException:Ha is not enabled for
  8. codemirror 光标定位_CodeMirror
  9. springboot2+JPA 配置多数据源(不同类型数据库)
  10. Google 与 金山新合作的《谷歌金山词霸》,试用感觉不错