回调模式——让你的controller不再繁琐

在Java后台编程中,大家一般会使用MVC设计模式,即便使用的具体框架不尽相同。今天,我们来说说MVC中的这个C,也就是Controller。Controller是web程序中最先接触到用户request的地方,当然,前提是该request经过了身份认证和权限检查等重重考验,这一部分建议在框架的Interceptor中进行。详细内容请看笔者之前的博客玩转Spring!从拒绝Filter开始。好了,话不多说,进入正题。


一般来说,Controller的作用是为每个URI提供一个处理方法,比如有一组User的增删改查操作,我们会写一个UserController,然后其中有四个方法,对应User的增删改查操作。好了,这部分没什么不同。我们来看看这四个方法的内部逻辑,比如增加用户addUser方法,我们需要接受一些关于User的元信息,然后检查这些元信息是否非空,是否合法等等,接着调用对应的service方法,然后返回一个执行正确或者执行错误的结果。再比如updateUser方法,我们仍然需要接受一个User的元信息,然后检查这些元信息是否非空,是否合法等等,接着调用对应的service方法,然后返回一个执行正确或者执行错误的结果。


根据Donnot repeat yourself原则,我们自然想到:

1. 可否做一个抽象,将所有的参数合法性检查放到一个地方来做,业务代码仅仅需要指定传入的参数类,框架会帮助检查参数的合法性。如果不合法,直接返回异常。

2. 可否做一个抽象,将所有的返回值放到一个地方来做,业务代码可以将任意的结果类返回,而无需关心该结果类和JsonString的转化。


针对第一个问题,我们设计一种模式,让业务developer每次实现一个URI,就新增一个对应该URI的Param类,然后框架会帮助检查这个Param类。

针对第二个问题,我们设计一个包含泛型结果的类,让业务developer每次仅仅需要将某一个处理结果类放入即可。


至于重用代码的抽象,我们使用回调模式,来看代码:

public interface CommonExecute {
    HttpResult execute(T param);
}

public class CommonExecutor {
    public static String execute(HttpServletRequest request, HttpServletResponse response, Class paramClass, CommonExecute commonExecute) {
        //根据paramClass的原信息检查参数的是否存在以及各式是否正确
        BaseParam baseParam = null;
        try {
            baseParam = paramClass.newInstance();
        } catch (InstantiationException | IllegalAccessException e) {
            throw new SystemException(ErrorEnum.RUNTIME_EXCEPTION);
        }
        //inject the values into the baseParam
        new ServletRequestDataBinder(baseParam).bind(request);
        HttpResult result = commonExecute.execute(baseParam);
        return JSON.toJSONString(result);
    }
}

先来看第一个问题,有了上面的这两个类以后,以后的Controller是这样的:

    @Resource(name = "userService")
    IUserService userService;

    @RequestMapping(value = CommonUrl.UrlConstant.GET_USER_BY_ACCOUNT, produces = CommonUtils.CONTENT_TYPE)
    @ResponseBody
    public String getUserByAccount(HttpServletRequest request, HttpServletResponse response) {
        return CommonExecutor.execute(request, response, CommonUrl.GET_USER_BY_ACCOUNT.getParamClass(), new CommonExecute() {
            @Override
            public HttpResult execute(GetUserByAccountParam param) {
                HttpResult result = userService.getUserByAccount(param.getAccount());
                return result;
            }
        });
    }

public class GetUserByAccountParam extends BaseParam {
    private String account;

    public String getAccount() {
        return account;
    }

    public void setAccount(String account) {
        this.account = account;
    }
}

我们来看上面的代码,当我需要写一个getUserByAccount的API时,仅仅需要在对应的controller中添加一个如下的方法

public String getUserByAccount(HttpServletRequest request, HttpServletResponse response) {}

当我想添加比如名为xxx方法的时候,仅仅需要添加同样的方法:

public String xxx(HttpServletRequest request, HttpServletResponse response) {}

在方法体的内部,直接调用CommonExecutor的静态execute方法,该方法有四个参数,前两个参数是request和response参数,传入皆可,无需感知。第三个方法是该方法对应的URI对应的参数类,可以看到,他是一个继承自BaseParam子类的元类。说白了,就是你新添加的方法,需要传入那些参数,把这些参数写成一个类。


Class


第四个参数是一个实现了CommonExecute接口的匿名类,在这里进行具体的业务处理。读者可以看到,该类的一个传入的参数就是你自定义的传入参数类,只要用户在发送请求的时候传入和该参数类的字段域同名的参数,框架就会自动帮你注入到该传入参数类中。这样业务developer无需感知这个过程,在写CommonExecute接口的匿名类时,直接使用自定义的参数类即可。

对于第二个问题,框架定义了一个HttpResult类:

public class HttpResult implements Serializable {
    private static final long serialVersionUID = -3404886040638951329L;

    protected boolean success;

    protected T model;

    protected String msgCode;

    protected String msgInfo;

    public HttpResult() {

    }

    public HttpResult(boolean success, String msgCode, String msgInfo) {
        this.success = success;
        this.msgCode = msgCode;
        this.msgInfo = msgInfo;
    }

    public static  HttpResult successResult(T t) {
        HttpResult result = new HttpResult();
        result.setSuccess(true);
        result.setModel(t);
        return result;
    }

    public static  HttpResult failedResult(String msgCode, String msgInfo) {
        HttpResult result = new HttpResult();
        result.setSuccess(false);
        result.setMsgCode(msgCode);
        result.setMsgInfo(msgInfo);
        return result;
    }

    public static  HttpResult failedResult(ErrorEnum errorEnum) {
        HttpResult result = new HttpResult();
        result.setSuccess(false);
        if (errorEnum != null) {
            result.setMsgCode(errorEnum.getErrCode());
            result.setMsgInfo(errorEnum.getErrDesc());
        }
        return result;
    }

    public boolean isSuccess() {
        return success;
    }

    public void setSuccess(boolean success) {
        this.success = success;
    }

    public T getModel() {
        return model;
    }

    public void setModel(T model) {
        this.model = model;
    }

    public String getMsgCode() {
        return msgCode;
    }

    public void setMsgCode(String msgCode) {
        this.msgCode = msgCode;
    }

    public String getMsgInfo() {
        return msgInfo;
    }

    public void setMsgInfo(String msgInfo) {
        this.msgInfo = msgInfo;
    }

    @Override
    public String toString() {
        return ToStringBuilder.reflectionToString(this);
    }
}

可以看见,HttpResult类是框架定义好的返回格式,在业务Developer实现CommonExecute接口的匿名类时,仅仅需要返回一个HttpResult类,框架会帮助把该HttpResult类格式化成为Json返回给用户,十分方便。


觉得文章不错的话,别忘了关注我哦!

关于更将详细的代码,请参见笔者的github。点击打开链接

笔者开设了一个知乎live,详细的介绍的JAVA从入门到精通该如何学,学什么?

提供给想深入学习和提高JAVA能力的同学,欢迎收听https://www.zhihu.com/lives/93219220424868249



你可能感兴趣的:(框架探险)