Java 服务层和控制器在实践中的区别

Java 服务层和控制器在实践中的区别,java,spring-mvc,model-view-controller,Java,Spring Mvc,Model View Controller,我已经阅读了很多关于服务层和控制器之间差异的理论,我对如何在实践中实现这一点有一些疑问。一个答案是: 我试图限制控制器执行与验证http相关的工作 参数,决定使用什么参数调用什么服务方法, 在httpsession或请求中放置什么,重定向或请求什么视图 转发到,或类似的网络相关的东西 和来自: 危险信号:如果: 控制器向服务层发出太多请求。这个 控制器向服务层发出许多不需要的请求 返回数据。控制器向服务层发出请求 没有传递论点 目前我正在用Spring MVC开发一个web应用程序,我有这样的方

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

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

和来自:

危险信号:如果:

控制器向服务层发出太多请求。这个 控制器向服务层发出许多不需要的请求 返回数据。控制器向服务层发出请求 没有传递论点

目前我正在用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;
    }
方法convertAvaForRendering(clientFromDB.getAvatar())被放置在该控制器的超类中,这是该方法的正确位置,或者他必须被放置在服务层


请帮助,这对我来说真的很重要。

Spring
控制器
通常与Spring API(使用
Model
ModelAndView等类)或Servlet API(
HttpServletRequest
HttpServletResponse
…)相关联。方法可以返回解析为模板名称(JSP…)的
String
结果<代码>控制器
无疑偏向于Web GUI,对Web技术有着强烈的依赖性

另一方面,
服务
s的设计应考虑到业务逻辑,而不是对客户机的假设。我们可以远程服务,将其公开为Web服务,实现Web前端或Swing客户端。
服务
不应该依赖于SpringMVC、ServletAPI等。这样,如果需要重新确定应用程序的目标,可以重用大部分业务逻辑


至于关于从控制器层调用服务层的次数过多的注意事项,主要是性能问题,IMHO则有所不同。如果对服务层的每次调用都查询数据库,则可能会遇到性能问题。如果服务层和控制器层不在同一个JVM中运行,则可能会遇到性能问题。这是设计应用程序的另一个非常重要的方面,但它表明您应该对服务调用进行修饰,以向控制器层提供更粗粒度的操作。

在这两个示例中,为什么需要强制转换
客户机
?那是一种代码气味

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

这就是为什么不鼓励多打电话的原因之一@阿瑟诺塞达在书中提到了其他很好的理由

在第一种情况下,应该对服务层进行一次调用,例如:

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相关的操作保留在控制器中它们所属的位置。

您应该忘记“控制器向服务层发出的请求太多。控制器向服务层发出大量不返回数据的请求。控制器向服务层发出请求,而不传入参数。“听起来好像没有意义”请显示此方法的更好版本?“,请尝试codereview.stackexchange.comThank(非常感谢)(例如),我已将其转换为客户端,因为在我的web应用程序中,我有类User、两个子类Client和Translator,还有抽象类UserService,它们对用户和Translator都有一些常用方法,并且因为此方法位于ClientController中(但不是在TranslatorController中)我可以向客户端强制转换。如果不好,请告诉我更好的方法:)@Yuriy服务如何知道是否为给定用户返回
客户端
翻译器
?您应该有两个服务方法,每种类型一个。因此,我将创建公共接口UserService和两个子类ClientService和TranslatorService。谢谢:)还有一个问题。我可以有一个方法吗?因为控制器从主体对象获取电子邮件,强制转换取决于控制器类型,我为此方法编写了适当的文档?编辑我的第一条评论:我有一个抽象类UserService,它对客户端和翻译程序都有一些常用方法。这是因为在这两个类中都存在很多相似的方法,并且为每一个这样的方法创建了两个不同的实现——这是一个复杂的代码
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");