实践中服务层与控制器的区别

时间:2016-07-30 19:48:26

标签: java spring-mvc model-view-controller

我已经阅读了很多关于服务层和控制器之间差异的理论,我对如何在实践中实现这一点有一些疑问。 Service layer and controller: who takes care of what?的一个答案是:

  

我尝试限制控制器执行与验证http相关的工作   参数,决定用什么参数调用什么服务方法,   什么放在httpsession或请求,什么视图重定向或   转发或类似网络相关的东西。

http://www.bennadel.com/blog/2379-a-better-understanding-of-mvc-model-view-controller-thanks-to-steven-neiland.htm

  

Red Flags:如果出现以下情况,我的控制器架构可能会变坏:

     

Controller向Service层发出太多请求。该   Controller向Service层提出了许多请求   返回数据。 Controller向服务层发出请求   没有传递参数。

目前我正在使用Spring MVC开发一个Web应用程序,我有这样的方法来保存更改后的用户的电子邮件:

/**
     * <p>If no errors exist, current password is right and new email is unique,
     * updates user's email and redirects to {@link #profile(Principal)}
     */
    @RequestMapping(value = "/saveEmail",method = RequestMethod.POST)
    public ModelAndView saveEmail(
            @Valid @ModelAttribute("changeEmailBean") ChangeEmailBean changeEmailBean,
            BindingResult changeEmailResult,
            Principal user,
            HttpServletRequest request){

        if(changeEmailResult.hasErrors()){
            ModelAndView model = new ModelAndView("/client/editEmail");
            return model;
        }
        final String oldEmail = user.getName();
        Client client = (Client) clientService.getUserByEmail(oldEmail);
        if(!clientService.isPasswordRight(changeEmailBean.getCurrentPassword(), 
                                          client.getPassword())){
            ModelAndView model = new ModelAndView("/client/editEmail");
            model.addObject("wrongPassword","Password doesn't match to real");
            return model;
        }
        final String newEmail = changeEmailBean.getNewEmail();
        if(clientService.isEmailChanged(oldEmail, newEmail)){
            if(clientService.isEmailUnique(newEmail)){
                clientService.editUserEmail(oldEmail, newEmail);
                refreshUsername(newEmail);
                ModelAndView profile = new ModelAndView("redirect:/client/profile");
                return profile;
            }else{
                ModelAndView model = new ModelAndView("/client/editEmail");
                model.addObject("email", oldEmail);
                model.addObject("emailExists","Such email is registered in system already");
                return model;
            }
        }
        ModelAndView profile = new ModelAndView("redirect:/client/profile");
        return profile;
    }

你可以看到我对服务层有很多请求,我从控制器重定向 - 这就是业务逻辑。请显示此方法的更好版本。

另一个例子。我有这个方法,它返回用户的个人资料:

/**
     * Returns {@link ModelAndView} client's profile
     * @param user - principal, from whom we get {@code Client}
     * @throws UnsupportedEncodingException
     */
    @RequestMapping(value = "/profile", method = RequestMethod.GET)
    public ModelAndView profile(Principal user) throws UnsupportedEncodingException{
        Client clientFromDB = (Client)clientService.getUserByEmail(user.getName());
        ModelAndView model = new ModelAndView("/client/profile");
        model.addObject("client", clientFromDB);
        if(clientFromDB.getAvatar() != null){
            model.addObject("image", convertAvaForRendering(clientFromDB.getAvatar()));
        }
        return model;
    }

方法convertAvaForRendering(clientFromDB.getAvatar())被放置在这个控制器的超类中,它是正确放置这个方法,还是他必须放在服务层?

请帮助,这对我来说非常重要。

2 个答案:

答案 0 :(得分:11)

Spring Controller通常与Spring API绑定(使用类ModelModelAndView ...)或Servlet API(HttpServletRequest,{{1 }} ...)。方法可以返回已解析为模板名称的HttpServletResponse结果(JSP ...)。 String肯定偏向于Web GUI,并且非常依赖Web技术。

另一方面

Controller 应该设计时考虑到业务逻辑,而不是关于客户端的假设。我们可以远程服务,将其公开为Web服务,实现Web前端或Swing客户端。 Service 应该不依赖于Spring MVC,Servlet API等。这样,如果您需要重新定位应用程序,则可以重用大部分业务逻辑。

关于从控制器层调用服务层的次数过多的说明,主要是性能问题,恕我直言是不同的。如果每次调用服务层都会查询数据库,则可能会遇到性能问题。服务层和控制器层没有在同一个JVM中运行,您也可能遇到性能问题。这是设计应用程序的另一个非常重要的方面,但它表明您应该对服务调用进行外观,以便为控制器层提供更粗粒度的操作。

答案 1 :(得分:7)

在这两个示例中,为什么需要投射Client?那是代码味道。

由于对服务层的调用也是建立数据库事务边界的调用,因此进行多次调用意味着它们在不同的事务中执行,因此不一定彼此一致。

这是不鼓励多次通话的原因之一。 @ArthurNoseda在his answer中提到了其他正当理由。

在您的第一种情况下,应该只对服务层进行一次调用,例如像这样的东西:

if (changeEmailResult.hasErrors()) {
    return new ModelAndView("/client/editEmail");
}
try {
    clientService.updateUserEmail(user.getName(),
                                  changeEmailBean.getCurrentPassword(),
                                  changeEmailBean.getNewEmail());
} catch (InvalidPasswordException unused) {
    ModelAndView model = new ModelAndView("/client/editEmail");
    model.addObject("wrongPassword", "Password doesn't match to real");
    return model;
} catch (DuplicateEmailException unused) {
    ModelAndView model = new ModelAndView("/client/editEmail");
    model.addObject("email", oldEmail);
    model.addObject("emailExists", "Such email is registered in system already");
    return model;
}
refreshUsername(newEmail);
return new ModelAndView("redirect:/client/profile");

您也可以使用返回值而不是例外。

正如您所看到的,这将委托将电子邮件更改为服务层的业务逻辑,同时将所有与UI相关的操作保留在控制器所属的位置。