到第七部分为止,整个服务端的设计基本上就结束了,还剩下具体的和业务相关的Servlet编写,涉及数据库访问层的设计请参考第二部分,这里对Module模块再简单介绍下。
因为所有的基础设施已经准备完毕,剩余的开发就变得异常的简单,按之前的约定(约定大于配置,这是很重要、很实用的开发经验)我们编写一个Servlet,它派生自IServlet:
package com.bubbling.servlet;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import com.bubbling.bean.Module;
import com.bubbling.common.Constant;
import com.bubbling.common.ServiceException;
import com.bubbling.service.ModuleService;
import com.bubbling.servlet.base.IServlet;
/**
* 处理所有和模块相关的请求,派生自IServlet,需实现getMethodMap()抽象方法
* getMethodMap()方法返回处理方法的键值对,其Value值为处理方法名
* 所有请求处理方法的访问修饰符为public,返回值类型为void,其参数列表为空
*
* @author lovel
*
*/
public class ModuleServlet extends IServlet
{
private static final long serialVersionUID = 1L;
private static ModuleService service = ModuleService.getService();
@Override
protected Map<String, String> getMethodMap()
{
return new HashMap<String, String>()
{
private static final long serialVersionUID = 1L;
{
put("add_module", "addModule");
put("get_module", "getModule");
put("get_module_list", "getModuleList");
}
};
}
public void addModule()
{
try
{
boolean ret = service.addModule(getParam("name"), getParam("description"));
if (ret)
{
setWebResponse(true);
}
else
{
setWebResponse(Constant.STR_ERROR_CODE_MODULE_CREATE_FAILURE);
}
}
catch (ServiceException e)
{
setWebResponse(e.getCode());
}
}
/**
* 获取模块,/module
*
* action:get_module
*
* param:module_uuid(必填)
*/
public void getModule()
{
try
{
Module module = service.getModuleByUuid(getParam("module_uuid"));
setWebResponse(module);
}
catch (ServiceException e)
{
setWebResponse(e.getCode());
}
}
/**
* 获取模块列表,/module
*
* action:get_module_list
*
* param:
*/
public void getModuleList()
{
try
{
List<Module> modules = service.getModuleList();
setWebResponse(modules);
}
catch (ServiceException e)
{
setWebResponse(e.getCode());
}
}
}
IServlet类的设计可以参考前几部分,IServlet类要求派生类实现一个抽象方法:
package com.bubbling.servlet.base;
import java.io.IOException;
import java.io.PrintWriter;
import java.lang.invoke.MethodHandles;
import java.lang.invoke.MethodType;
import java.util.Map;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import com.bubbling.common.Constant;
import com.bubbling.util.GsonUtil;
import com.google.gson.Gson;
/**
* @author 胡楠
*
* 所有Servlet均需要自该类派生,并且需实现处理请求映射的获取方法,派生类的所有方法访问类型按规范必须声明为public,
* 且返回值为void
*
*/
public abstract class IServlet extends HttpServlet
{
private static final long serialVersionUID = 1L;
private HttpServletRequest request;
private HttpServletResponse response;
private HttpSession session;
private IResponse result = new IResponse();
……
/**
* @return kEY值为请求参数action值,VALUE值为处理该请求的方法名
*/
protected abstract Map<String, String> getMethodMap();
……
}
该方法返回了具体Servlet处理类针对某种类型的所有请求的实现方法名,IServlet的processAction()方法会通过方法句柄的方式来调用具体方法,以实现请求的分发及处理:
package com.bubbling.servlet.base;
import java.io.IOException;
import java.io.PrintWriter;
import java.lang.invoke.MethodHandles;
import java.lang.invoke.MethodType;
import java.util.Map;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import com.bubbling.common.Constant;
import com.bubbling.util.GsonUtil;
import com.google.gson.Gson;
/**
* @author 胡楠
*
* 所有Servlet均需要自该类派生,并且需实现处理请求映射的获取方法,派生类的所有方法访问类型按规范必须声明为public,
* 且返回值为void
*
*/
public abstract class IServlet extends HttpServlet
{
……
private void processAction() throws Throwable
{
Map<String, String> methodMap = getMethodMap();
String action = request.getParameter("action");
if (!methodMap.containsKey(action))
{
setWebResponseInvalidAction();
}
else
{
MethodHandles.lookup().findVirtual(getClass(), methodMap.get(action), MethodType.methodType(void.class))
.invoke(this);
}
}
……
}
所以我们在编写Servlet的时候,仅仅需要派生自IServlet,并实现getMethodMap()方法,再依次实现各处理方法即可,可以说Servlet的实现变得极为简单,整个调度过程都在IServlet中实现了,开发者仅面向业务逻辑进行设计。
注意,ModuleServlet持有一个ModuleService对象,它归属业务层,真正用于处理和模块相关的请求的业务逻辑:
package com.bubbling.service;
import java.util.List;
import com.bubbling.bean.Module;
import com.bubbling.common.Constant;
import com.bubbling.common.ServiceException;
import com.bubbling.dao.ModuleDao;
import com.bubbling.dao.impl.mysql.ModuleDaoMysqlImpl;
import com.bubbling.util.StringUtil;
/**
* 处理模块相关请求的业务层处理逻辑
*
* @author 胡楠
*
*/
public class ModuleService
{
private static ModuleDao dao;
private static ModuleService service;
static
{
if (Constant.B_IS_MYSQL)
{
dao = new ModuleDaoMysqlImpl();
}
}
private ModuleService()
{
}
public static ModuleService getService()
{
if (service == null)
{
service = new ModuleService();
}
return service;
}
public boolean addModule(String name, String description) throws ServiceException
{
if (StringUtil.isEmpty(name))
{
throw new ServiceException(Constant.STR_ERROR_CODE_NAME_EMPTY);
}
if (StringUtil.isEmpty(description))
{
throw new ServiceException(Constant.STR_ERROR_CODE_DESCRIPTION_EMPTY);
}
return dao.addModule(name, description);
}
public Module getModuleByUuid(String uuid) throws ServiceException
{
if (StringUtil.isEmpty(uuid))
{
throw new ServiceException(Constant.STR_ERROR_CODE_UUID_EMPTY);
}
return dao.getModuleByUuid(uuid);
}
public List<Module> getModuleList() throws ServiceException
{
return dao.getModuleList();
}
}
ModuleService又持有一个ModuleDao,它归属于数据访问层,用于和数据源进行交互。
这是一个完整的MVC结构,正是因为结构清晰,所以开发起来更为容易,定位问题也及其简单。
将上例中的ModuleServlet配置到web.xml,然后我们启动Tomcat进行部署,测试一下运行结果,因为涉及到访问数据库,先贴一下module表数据:
模块相关的请求都归属于module,具体的请求参数action则以getMethodMap()方法为准,这里以获取module列表为例,其请求为:
http://localhost:8080/JianZi/module?action=get_module_list
启动Tomcat,运行结果如下:
从第一到第七部分,我都在做基础设施的开发准备工作,包括:
直到基础设施准备完毕,再开始动手写具体的业务实现逻辑,越到后期设计越快,因为准备工作做的充分,剩余的工作就变得越来越简单,这是一个很常规、很普通但是很有代表性的设计流程。
因为项目体量很小,并没有面面俱到的介绍,我的风格相信大家也能感受到一些,我对设计思路更为看重,实现上反而没花那么多精力,对于一名开发者而言,我个人觉得思路应该占工作比重的70%,实现上占用15-20%,剩余的比重留给自测,更为完备的测试应该交付测试人员。
如果我们的设计方案是合理的,那么会大大的减少测试工作,更多的问题排查在方案层面就能够得到解决。
自此,服务端设计告一段落,我说过了,后续的服务端设计都是重复性工作,根据不同的请求类型设计不同的Servlet,然后逐一按业务需求进行设计即可,后面的部分我会介绍前端设计,包括:
在做这部分设计的时候,如果涉及到有趣的服务端逻辑,我会再侧重的介绍,总的目标依然是给读者一个完整的设计思路,给自己一个温习web项目开发流程的机会。