我已经阅读了很多关于服务层和控制器之间差异的理论,我对如何在实践中实现这一点有一些疑问。 Service layer and controller: who takes care of what?的一个答案是:
我尝试限制控制器执行与验证http相关的工作 参数,决定用什么参数调用什么服务方法, 什么放在httpsession或请求,什么视图重定向或 转发或类似网络相关的东西。
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())被放置在这个控制器的超类中,它是正确放置这个方法,还是他必须放在服务层?
请帮助,这对我来说非常重要。
答案 0 :(得分:11)
Spring Controller
通常与Spring API绑定(使用类Model
,ModelAndView
...)或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相关的操作保留在控制器所属的位置。