最近我在将APDPlat升级到Java8,由于之前有很多的同学希望我把APDPlat的struts2替换为spring mvc,所以我就决定试试看。
本次我把APDPlat的struts2改造为spring mvc的目标是:99.99%不改动JS、HTML、JSP等前端代码,只改JAVA代码!所以大家要先理解我的目标,然后再来看我的做法。
本文我们看两个转换前后的例子:
1、下拉列表服务,此类比较简单,只涉及一个方法store:
使用struts2:
@Scope("prototype") @Controller @Namespace("/dictionary") public class DicAction extends ExtJSSimpleAction<Dic> { @Resource private DicService dicService; private String dic; private String tree; private boolean justCode; /** * * 此类用来提供下拉列表服务,主要有两种下列类型: * 1、普通下拉选项 * 2、树形下拉选项 * @return 不需要返回值,直接给客户端写数据 * */ public String store(){ Dic dictionary=dicService.getDic(dic); if(dictionary==null){ LOG.info("没有找到数据词典 "+dic); return null; } if("true".equals(tree)){ String json = dicService.toStoreJson(dictionary); Struts2Utils.renderJson(json); }else{ List<Map<String,String>> data=new ArrayList<>(); for(DicItem item : dictionary.getDicItems()){ Map<String,String> map=new HashMap<>(); if(justCode){ map.put("value", item.getCode()); }else{ map.put("value", item.getId().toString()); } map.put("text", item.getName()); data.add(map); } Struts2Utils.renderJson(data); } return null; } public void setJustCode(boolean justCode) { this.justCode = justCode; } public void setTree(String tree) { this.tree = tree; } public void setDic(String dic) { this.dic = dic; } }
使用spring mvc:
@Scope("prototype") @Controller @RequestMapping("/dictionary") public class DicAction extends ExtJSSimpleAction<Dic> { @Resource private DicService dicService; /** * * 此类用来提供下拉列表服务,主要有两种下拉类型: * 1、普通下拉选项 * 2、树形下拉选项 * @param dic * @param tree * @param justCode * @return 返回值直接给客户端 */ @ResponseBody @RequestMapping("/dic!store.action") public String store(@RequestParam(required=false) String dic, @RequestParam(required=false) String tree, @RequestParam(required=false) String justCode){ Dic dictionary=dicService.getDic(dic); if(dictionary==null){ LOG.info("没有找到数据词典 "+dic); return ""; } if("true".equals(tree)){ String json = dicService.toStoreJson(dictionary); return json; }else{ List<Map<String,String>> data=new ArrayList<>(); dictionary.getDicItems().forEach(item -> { Map<String,String> itemMap=new HashMap<>(); if("true".equals(justCode)){ itemMap.put("value", item.getCode()); }else{ itemMap.put("value", item.getId().toString()); } itemMap.put("text", item.getName()); data.add(itemMap); }); String json = JSONArray.fromObject(data).toString(); return json; } } }
从上面我们可以看到,struts2和spring mvc的区别非常明显,struts2使用原型,spring mvc使用单例。
单例一定比原型快吗?创建一个对象的开销可以忽略吗?这个问题需要在自己的场景中考虑,不过大多时候我们是可以忽略的。
APDPlat之前使用struts2,每一个请求都会对应一个全新的Action,所以请求的参数就可以作为Action的字段来自动注入,言下之意就是Action中的所有方法都可以共用字段,而现在换成spring mvc了,不同的方法需要各自获取请求中的参数。
对比以上代码,我个人还是认为spring mvc的方式更好一些,对于Action(spring mvc叫Controller)来说,单例、无状态是比较理想的。
2、数据字典服务,此类比较复杂,涉及的方法有create、delete、updatePart、retrieve、query、store:
使用struts2:
@Scope("prototype") @Controller @Namespace("/dictionary") public class DicItemAction extends ExtJSSimpleAction<DicItem> { @Resource private DicService dicService; private String node; /** * 返回数据字典目录树 * @return */ public String store() { if (node == null) { return null; } Dic dic=null; if(node.trim().startsWith("root")){ dic = dicService.getRootDic(); }else{ int id=Integer.parseInt(node); dic = dicService.getDic(id); } if (dic != null) { String json = dicService.toJson(dic); Struts2Utils.renderJson(json); } return null; } public void setNode(String node) { this.node = node; } }
使用spring mvc:
@Scope("prototype") @Controller @RequestMapping("/dictionary") public class DicItemAction extends ExtJSSimpleAction<DicItem> { @Resource private DicService dicService; /** * 返回数据字典目录树 * @param node * @return */ @ResponseBody @RequestMapping("/dic-item!store.action") public String store(@RequestParam(required=false) String node) { if (node == null) { return "[]"; } Dic dic=null; if(node.trim().startsWith("root")){ dic = dicService.getRootDic(); }else{ int id=Integer.parseInt(node); dic = dicService.getDic(id); } if (dic != null) { String json = dicService.toJson(dic); return json; } return "[]"; } @ResponseBody @RequestMapping("/dic-item!query.action") public String query(@RequestParam(required=false) Integer start, @RequestParam(required=false) Integer limit, @RequestParam(required=false) String propertyCriteria, @RequestParam(required=false) String orderCriteria, @RequestParam(required=false) String queryString, @RequestParam(required=false) String search){ super.setStart(start); super.setLimit(limit); super.setPropertyCriteria(propertyCriteria); super.setOrderCriteria(orderCriteria); super.setQueryString(queryString); super.setSearch("true".equals(search)); return super.query(); } @ResponseBody @RequestMapping("/dic-item!retrieve.action") public String retrieve(@ModelAttribute DicItem model) { super.model = model; return super.retrieve(); } @ResponseBody @RequestMapping("/dic-item!delete.action") public String delete(@RequestParam String ids) { super.setIds(ids); return super.delete(); } @ResponseBody @RequestMapping("/dic-item!create.action") public String create(@ModelAttribute DicItem model) { super.model = model; return super.create(); } @ResponseBody @RequestMapping("/dic-item!updatePart.action") public String updatePart(@ModelAttribute DicItem model) { super.model = model; return super.updatePart(); } }
从上面可以看到,从struts2转换为spring mvc之后,代码一下子就增加了,父类的create、delete、updatePart、retrieve、query这5个方法对于spring mvc就无效了,而且模型注入的方式也不起作用了,下面我们要解决这两个问题。
要解决第一个问题,我们首先要改变struts2的URL调用方式,在struts2中,我们是这么调用Action的方法的,!后面是Action的方法名称:
http://localhost:8080/APDPlat_Web-2.6/dictionary/dic-item!query.action
如果我们不改变调用方式,上面刚说的那5个方法就无法抽象到父类中了,改变方式也挺简单,只需要把!改成/就可以了,在父类中增加如下代码并在前端JS中将!改成/:
@ResponseBody @RequestMapping("query.action") public String query(@RequestParam(required=false) Integer start, @RequestParam(required=false) Integer limit, @RequestParam(required=false) String propertyCriteria, @RequestParam(required=false) String orderCriteria, @RequestParam(required=false) String queryString, @RequestParam(required=false) String search){ super.setStart(start); super.setLimit(limit); super.setPropertyCriteria(propertyCriteria); super.setOrderCriteria(orderCriteria); super.setQueryString(queryString); setSearch("true".equals(search)); return query(); } @ResponseBody @RequestMapping("retrieve.action") public String retrieve(@ModelAttribute T model) { this.model = model; return retrieve(); } @ResponseBody @RequestMapping("delete.action") public String delete(@RequestParam String ids) { super.setIds(ids); return delete(); } @ResponseBody @RequestMapping("create.action") public String create(@ModelAttribute T model) { this.model = model; return create(); } @ResponseBody @RequestMapping("updatePart.action") public String updatePart(@ModelAttribute T model) { this.model = model; return updatePart(); }
关于第二个问题,在struts2中,注入Action的参数,要使用model.id这样的方式,model是Action的一个字段,而在spring mvc中,这样是不行的,需要做一个转换,在父类中增加如下代码以使spring mvc能适应struts2参数注入方式:
/** * 前端向后端传递模型参数的时候都有model.前缀 * @param binder */ @InitBinder public void initBinder(WebDataBinder binder) { binder.setFieldDefaultPrefix("model."); }
经过上面的改进,数据字典服务 使用spring mvc的代码升级为:
@Scope("prototype") @Controller @RequestMapping("/dictionary/dic-item/") public class DicItemAction extends ExtJSSimpleAction<DicItem> { @Resource private DicService dicService; /** * 返回数据字典目录树 * @param node * @return */ @ResponseBody @RequestMapping("store.action") public String store(@RequestParam(required=false) String node) { if (node == null) { return "[]"; } Dic dic=null; if(node.trim().startsWith("root")){ dic = dicService.getRootDic(); }else{ int id=Integer.parseInt(node); dic = dicService.getDic(id); } if (dic != null) { String json = dicService.toJson(dic); return json; } return "[]"; } }