不直接编写 Servlet 来创建 REST API 的原因

2022-09-01 18:49:02

在我目前的公司,我们正在启动一个新项目,该项目将是Java中的REST API,部署在像Tomcat这样的servlet容器中。在我之前使用REST框架的经验中,例如JAX-RS与Jersey,JBOSS REST Easy,Spring MVC,我知道使用这样的框架比直接编写Servlet来处理请求有什么优势。

(当然,我们知道上述框架仍然在幕后使用Servlets)

我发现很难说服他们。由于他们建议编写servlet,认为它对性能更好(这可能是这种情况,但我认为使用其中一个框架的开销对于REST API来说应该是微不足道的)。

以下是我的理由:

1)更少的样板和更简洁的代码(更易于维护和测试)。使用 JAX-RS 框架或 SpringMVC,您可以非常轻松地定义 REST 资源,方法是编写带有注释的方法,这些注释指示资源的 PATH、要使用的 http 方法、查询和 url 参数、接受的编码等标头等。

例:

@GET
@Path("/users")
@Produces({MediaType.APPLICATION_JSON}) 
public UserList getUsers(@QueryParam("group") String group) {
    return userService.findUsers(group);
}

使用servlets,你至少需要这样的东西:

映射 Web.xml中的每个 servlet 的 url(这在 Servlet 3.0 及更高版本中不是必需的):

<servlet>
    <servlet-name>UsersServlet</servlet-name>
    <servlet-class>test.UsersServlet</servlet-class>
</servlet>
<servlet-mapping>
    <servlet-name>UsersServlet</servlet-name>
    <url-pattern>/users</url-pattern>
</servlet-mapping>

然后在 servlet 类中:

public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { 
    String group = request.getParameter("group");
    response.setContentType("application/json");
    PrintWriter out = response.getWriter();
    JsonSerializer someJsonSerializer = new JsonSerializer();
    String json = someJsonSerializer.serialize(userService.findUsers(group));      
    out.print(json);
}

2)适应性强。上述框架允许您轻松地向应用程序添加功能,否则您将需要手动执行此操作,例如使用多个媒体类型的输入和输出。例如,根据接受标头使服务返回 xml 或 json 或任何其他服务。像SpringMVC和Jersey这样的框架使得为您的请求和响应配置序列化程序/反序列化程序变得非常容易。

3) REST 最佳实践。通常,这些框架是在对REST API要遵循的最佳实践的深刻理解的基础上构建的,并且基于REST架构的标准进行定义,这使得构建可靠且符合标准的应用程序变得更加容易。另一方面,Servlets在如何处理您的请求/响应方面为您提供了如此高的自由度,以至于很难意识到您根本不是RESTfull。

还有其他的吗?


答案 1

让我用我的答案扮演魔鬼的代言人。

首先,您不需要将 servlet 添加到 web.xml 文件中。Servlets 3.0 允许您使用注释

其次,这些框架确实对性能造成了重大影响。查看这些基准测试

第三,你可以在servlet中使用GSON,这比Jackson更快(在Spring和Jersey中默认使用)。这可以为您提供更高的性能,特别是考虑到性能对您的要求至关重要。

最后,如果您担心样板,请将您在 servlet 中编写的代码放在某个实用程序类中,并从多个 servlet 中使用它。这比承载框架的巨大负载要好,因为您(像大多数人一样)可能会使用其功能的一小部分。


答案 2

几个月前,我发表了一条评论,说我确实支持纯Servlet 3.0解决方案,反对使用REST MVC框架。

经过几个月的使用,我确认了我的选择!

我试图安装Jackson和其他框架,但它需要比编写额外的5行代码更多的工作,而且我不必处理额外的软件组件来设置,学习,更新...

这是我的工作示例:

package example;

import java.io.IOException;
import java.io.PrintWriter;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.google.gson.Gson;

/**@WebServlet(name = "booking", urlPatterns = { "/api/v1/booking" })*/
public class BookingWs extends javax.servlet.http.HttpServlet {

    public static final Logger LOGGER = LoggerFactory.getLogger(BookingWs.class);

    protected void doPost(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {
        
        try {
            // Used for JSON handling
            Gson gson = new Gson(); 
            
            // De-serialize from request
            BookingRequest bRequest = gson.fromJson(request.getReader(), BookingRequest.class);
            
            // Do your business logic.
            BookingResponse bResponse = new BookingResponse();
            bResponse.request = bRequest;
            bResponse.accepted = "bar".equalsIgnoreCase(bRequest.type);
            bResponse.reason = bResponse.accepted ? "Welcome" : "Only bar table booking left";
            
            // Serialize and send response back;
            response.setContentType("application/json");
            PrintWriter pw = response.getWriter();
            gson.toJson(bResponse, pw);
        } catch (Throwable t) {
            response.setStatus(500);
            PrintWriter pw = response.getWriter();
            pw.write("{\"error\":\"" + t.getLocalizedMessage() + "\"}");
        }
    }
}

class BookingRequest{
    String type;
    int seats;
    String name;
    long requiredTimestamp;
}

class BookingResponse{
    BookingRequest request;
    boolean accepted;
    String reason;
}  

也许这些框架有一个你绝对需要的功能,但对我来说,它应该足够果断,值得额外的库的麻烦。

正如法国作家安东尼·德·圣埃克苏佩里所说:

“完美不是在没有什么可添加的时候实现的,而是在没有什么可以带走的时候实现的”。

我带走了杰克逊以更接近它:-)

(是的,我不得不承认,我使用了GSON,但它是一个小罐子,不需要任何配置)。