← 返回文章列表

oa-backend-framework项目架构设计 - Web API

分类:计算机/架构/oa-backend-framework项目架构设计

实现本地运行

OA系统遵循Java EE的JAX-RS规范来实现Web API,我们当然也必须遵循之。

JAX-RS本身只是一种规范,运行它还需要实现了这种规范的框架(通常为Jersey),以及Web容器(Jersey在Web容器上注册Servlet来接收请求)。OA系统使用的JAX-RS框架是Jersey,使用的Web容器是Resin。

要在本地运行Web API,只需要让我们的框架在本地具备(<scope>provided</scope>)JAX-RS规范(javax.ws.rs:javax.ws.rs-api)和Jersey框架(com.sun.jersey:jersey-bundle),并运行在本地的Web容器(Tomcat)中即可。

为了让Jersey能扫描到我们的控制器类,需要在web.xml中(本地和线上)修改<servlet-class>com.sun.jersey.spi.container.servlet.ServletContainer</servlet-class><param-value>值,将我们应用的包名附加到最后。

控制器类

JAX-RS使用@Path注解标记一个方法是一个资源(即一个API),使用@GET/@POST注解标记资源对应的HTTP方法,使用@QueryParam@PathParam等注解获取请求中的参数,使用@Produces注解设置响应的内容类型(Content-Type)。@Path写在类上可表达公共路径前缀,@Produces写在类上可统一设置响应的内容类型。

可见,JAX-RS的基本使用跟Spring Boot的控制器是类似的,因此我们仍然称有@Path注解的类为控制器类,但它们的运行机制不太相同。在Spring Boot中,一个控制器类是一个Bean,全局只有一个实例。但在JAX-RS中,对于一个@Path类,每当一个HTTP请求映射到它时,它都会创建一个新的实例来执行。因此,我们的控制器类不能作为Bean管理。

为了简化控制器类的代码,我们编写所有控制器的抽象基类BaseController

// 所有API均返回JSON响应
@Produces(MediaType.APPLICATION_JSON)
public abstract class BaseController {
    public BaseController() {
        // 注入依赖
        IoCContainer.injectDependencies(this);
    }
}

业务的控制器类继承自BaseController

public EntityController extends BaseController {
    @Resource
    private EntityService entityService;

    @GET @Path("/getEntity")
    public Entity get() {
        ...
    }
}

自动数据库事务管理

我们希望执行一个API请求时,如果正常返回则自动提交数据库事务,如果发生异常则自动回滚数据库事务。

因为JAX-RS最终也是基于Servlet实现的,因此我们可以使用Servlet中的过滤器(Filter)机制,实现请求前、后的拦截,并在其中实现统一异常处理和自动数据库事务管理。

// 只拦截我们应用的API
@WebFilter("/my/*")
public class MyFilter implements Filter {
    @Override
    public void init(FilterConfig filterConfig) {}

    @Override
    public void destroy() {}

    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
        try {
            // 当前请求线程获取数据库连接
            ConnectionHolder.getConnection();
            // 对于HTTP响应,做统一异常处理
            if (response instanceof HttpServletResponse) {
                HttpServletResponse httpResponse = (HttpServletResponse) response;
                // HTTP响应包装器,重写方法,避免响应在其他Servlet或Filter中被提交导致无法修改响应体内容
                HttpServletResponseWrapper httpResponseWrapper = new HttpServletResponseWrapper(httpResponse) {
                    @Override
                    public void sendError(int sc, String msg) {}

                    @Override
                    public void sendError(int sc) {}

                    @Override
                    public void sendRedirect(String location) {}
                };
                try {
                    // 执行
                    chain.doFilter(request, httpResponseWrapper);
                    // 执行成功,提交数据库事务
                    ConnectionHolder.commit();
                }
                // 捕获任何抛出,进行统一异常处理
                catch (Throwable e) {
                    // 回滚数据库事务
                    ConnectionHolder.rollback();

                    // 响应结果map
                    Map<String, String> resultMap = new LinkedHashMap<>();
                    resultMap.put("error", e.getClass().getSimpleName());
                    resultMap.put("message", e.getMessage());

                    // 写入HTTP响应
                    httpResponse.setStatus(500);
                    httpResponse.setContentType(MediaType.APPLICATION_JSON);
                    httpResponse.getWriter().write(JSONUtil.toJsonStr(resultMap));
                }
            }
            // 对于非HTTP响应,不做处理
            else {
                chain.doFilter(request, response);
            }
        } finally {
            // 当前请求线程关闭数据库连接
            ConnectionHolder.closeConnection();
        }
    }
}