实现本地运行
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();
}
}
}