本文是《轻量级 Java Web 框架架构设计》的系列博文。
用过 Struct 的同学们应该都知道 Action 的概念吧?在 Spring MVC 中对应的是 Controller。此外,Spring MVC 也提供了基于注解的配置方式,例如,@Controller、@RequestMapping 等。这些注解给了我们很多帮助,让我们不再编写繁重的 XML 配置了。现在我想做的是一个结合了 Struct 与 Spring MVC 以及现今较为流行的 REST(JAX-RS),打造一个更加简化又更加实用的 Action。由于本框架不打算使用 MVC 架构,所以没有用 Controller 这个术语,而改用更加通俗易懂的 Action。
下面以 ProductAction 为例,展示了如何编写 Action 类,代码如下:
@Action
public class ProductAction {
@Inject
private ProductService productService;
@Request(url = "/product/{id}", method = RequestMethod.GET)
@Response(ResponseType.JSON)
public Result getProductById(@PathParam("id") long productId) {
if (productId == 0) {
return new Result(ResultError.ERROR_PARAM_INVALID);
}
Product product = productService.getProduct(productId);
if (product != null) {
return new Result(ResultError.OK, product);
} else {
return new Result(ResultError.ERROR_DATA_NULL);
}
}
}
想必用过 Spring MVC 的同学都能看得懂,不过还是有必要稍微解释一下:
第1行:使用 @Action 注解用于表明该类是一个 Action,框架会自动加载它。
第3行:使用 @Inject 注解用于注入一个对象,类似于 Spring 中的 @Autowired。
第6行:使用 @Request 注解用于配置请求与 Action 方法之间的映射关系,类似于 Spring 中的 @RequestMapping。
第7行:使用 @Response 注解用于定义该方法的返回值(也就是 Result 对象)将被序列化为哪种类型的数据,可选择 JSON 或 XML,借鉴于 JAX-RS 的 @Produces。
第8行:使用 @PathParam 注解来映射 @Request 注解 url 属性中的路径参数,类似于 Spring 中的 @PathParam,同样 JAX-RS 中也有 @PathParam。此外,还会提供一系列的 @XxxPram 注解,例如,@QueryParam、@FormParam、@HeaderParam等。
第9~11行:验证请求参数,若参数无效,则返回 ResultError.ERROR_PARAM_INVALID。在常量接口 ResultError 中定义了一些列的错误代码,例如:OK 表示正确,值为1;ERROR_PARAM_INVALID 表示参数无效,值为100;ERROR_DATA_NULL 表示数据为空,值为200。这些错误代码可在服务端事先定义好,并直接返回到客户端,而客户端通过错误代码来给出错误提示信息。
第12行:调用服务接口中的方法,并获取返回值。
第13~17行:判断服务接口的返回值是否为空,若非空,则返回正确结果,否则返回错误结果。在正确结果对象 Result 中,包含一个 Object 类型的属性,用于存放返回到客户端的返回值,即 product 对象,而错误结果中无需给定这个属性,该属性的值默认为 null。
请大家对以上构思,给一些评价吧!
补充(2013-09-03)
方案二:
@Request(url = "/product", method = "GET")
public Result getProductById(HttpServletRequest request) {
long productId = Integer.parseLong(request.getParameter("id"));
if (productId == 0) {
return new Result(ResultError.ERROR_PARAM_INVALID);
}
Product product = productService.getProduct(productId);
if (product != null) {
return new Result(ResultError.OK, product);
} else {
return new Result(ResultError.ERROR_DATA_NULL);
}
}
此方案实现起来最为简单,但需要与 Servlet API 依赖,所有的参数必须通过 Request 对象获取,而且不支持 REST 风格的 URL(只能是 /product?id=1 这样的)。此外,单元测试也比较痛苦,需要 mock 出 Request 对象。
方案三:
@Request("GET:/product/{id}")
public Result getProductById(long productId) {
if (productId == 0) {
return new Result(ERROR_PARAM);
}
Product product = productService.getProduct(productId);
if (product != null) {
return new Result(OK, product);
} else {
return new Result(ERROR_DATA);
}
}
此方案是对方案一的改进,@Request 注解中是提供一个 URL 字符串即可,同时支持占位符。方法参数中,将 URL 的 {id} 匹配 productId。注意:这里是 Spring MVC 注解的简化,在 Spring MVC 中,如果 URL 中的占位符与方法参数命名不一致,则必须使用 @PathParam 注解进行标示,否则无法映射。这里是根据 URL 中占位符出现的顺序自动自动匹配方法参数,若 URL 占位符与方法参数不匹配,则不会执行方法体。此外,简化了常量的使用方法,可定义一个 Action 父类,其中定义一些列的常量,包含:OK、ERROR_PARAM、ERROR_DATA 等,可在应用开发过程中进行扩展。