在MVC开发模式中,封装对象应该放在Controller还是Service?Controller与Service应该如何交互?

已经学了两年的MVC了,但是有些概念还是很模糊,希望能在这里寻找到答案。

我曾经看到有人说Controller不负责数据处理,全部交给我们的Service来处理,网页前端传回来是什么数据类型,就直接把数据类型转发到Service,然后由Service来处理;但是又有另外一种声音说有时候会同时调用多个Service,如果在Controller就将对象封装好了,就免于在Service的方法中多次封装。

另外还有一个问题就是关于Controller与Service的交互问题。
我们为了前端的客户交互良好,往往会通过Controller向前端返回一些错误提示,比如用户名已存在,用户名和密码不匹配等等。可是处理业务逻辑我们是放在Service层,那么如果把一个login(String username,String password)方法的返回值设定为boolean就无法返回多种错误,但是如果返回String类型,就需要设定一些基本的字典。
我自己“奇思妙想”,我在Service中通过抛出我自定义的一些RuntimeException,然后在Controller中通过TryCatch来处理不同的错误,但是我自己认为这种抛出异常的方式不妥。最近就陷入了迷茫,马上就要开始做下一个项目了。希望各位能帮我解答一下迷惑。

感谢。

阅读 21.8k
8 个回答

我们项目里分了三层:

  1. 表现层:Spring MVC,负责接收Http请求、展现(返回)结果、简单校验

  2. App层:提供应用层的功能,比如导入、导出、复杂校验

  3. Domain层:处理业务逻辑,比如一些Service

调用顺序是单项的:Controller->App->Domain

且感知关系为:Controller->App->Domain,即下层不感知上层

先回答你第一个问题:

你说的第一个问题我是否可以理解为,Http请求过来的参数不是Object,而是一堆基本类型,但是你的Service接收的参数是Object。正确的做法应该是,在Controller将“生”参数转换成Object对象,然后调用Service。

为何这样是正确的?因为Service属于Domain层,里面是业务逻辑,其接受的参数应该根据自己的需要而进行设计,不应该考虑Web层过来的参数是什么,这样才可以做到在不同场景下复用。

举个例子,你的Service应该可以被复用不同表现层环境下:

  1. 在一个Web程序中,用户传过来的参数是POST/GET形式给你的

  2. 在一个Web service程序中,用户传过来的参数是json或者SOAP

  3. 在一个Swing程序中,用户传过来的参数就是字符串

如果你将Service和具体某个表现层环境绑定,那么其方法参数肯定不稳定,结果就导致无法复用。

同理,Service的返回值也不应该和具体场景绑定。

在Spring MVC层面,Controller可以很方便的把参数转换成Object,相关文档

第二个问题:

这个问题可以分为三个:

1)简单的校验比如参数长度限制、非空判断等在哪里做?

简单校验利用Spring MVC的自身提供的机制做,相关文档相关文档

2)和Service本身的业务逻辑平行的校验在哪里做,比如用户下单时判断其是否账号被禁用

我倾向于将这些逻辑校验放在App层做,Controller调用App,App调用两个不同的Service,将业务编织起来

3)和Service本身有关的业务逻辑校验怎么做

你举的是登录的例子,用异常告知调用方(Controller)处理结果没有任何问题。你也可以丰富Service的返回值达到这个目的,不过需要注意的是,Service的返回值的设计不能和表现层环境绑定,否则就不能复用了,这也就是为什么 @YaTou 提到了apache-shiro采用的是异常机制处理认证失败,因为只有这样才足够通用。

我觉得Controller不负责处理数据是正确的, 因为在spring-mvcController是不能复用的, 但是如果你把业务逻辑抽象成Service, 那么这个Service就是可以复用的.

至于你说的 "在Controller就将对象封装好了,就免于在Service的方法中多次封装" , 没太明白什么意思, 你每个Service需要什么参数, Controller就给什么参数, 至于需要的参数是否需要封装成对象就可以自己权衡了.

你所说的login(String username,String password), 你想抽象成Service用异常处理处理多种不同的结果, 这个我觉得完全没有问题, 而且我觉得非常好啊, 很多认证框架都用的这种方式, 至少我看的apache-shiro就是用异常处理认证失败的不同情况的.

第一个问题感觉没有标准答案,具体情况具体分析,逻辑分层清晰易于维护就好。

第二个问题的话,你这里给出的交互是隶属于权限控制的,一般用filter、aop、代理、反射等等方式实现代码收束都可以,异常也在这些集中控制的代码里扔一次就好,直接在Controller里硬编码我反倒觉得累赘。Service这一层更多的是调用Dao层的方法来实现一些复杂的涉及多表的业务逻辑处理,事务也放在这一层(当然现在框架把这事儿都干了),所以Service这一层一般不扔异常(参数验证在Controller以及之前的层次都做掉了因此不出现业务相关异常,而Dao把数据库相关的底层异常屏蔽了)。

当然这是个人看法,没有定式,还是那句话,分层清晰易于维护就好。

1、Controller默认是单例的,但可用@Scope(value = "prototype")替换

2、登录可以返回int啊,自己加个枚举

3、规定是在Service层处理逻辑,要看业务的吧,代码冗余度低些好,也好优化

大家观点会不同,做开发更多的还是优化改,降低冗余度,而不是必须要怎么做。。。

新手上路,请多包涵

1,Controller应尽可能的不设计业务逻辑,只涉及交互
2,Service为可复用的业务逻辑
3,Controller为Service的上级调用方
4,你这个case可以在Service中返回固定的返回值,在Controller层做判断,并抛出你想相应的异常。

当然了这只是我们目前的做法,分享一下。。。

建议逻辑放在service,我们最近在做分布式微服务架构,我们之前是放在controller层的,拆分的时候基本全部重新,另外有些app需要使用的接口如果放在controller就无法共用,蛋疼吧?

封装对象到底在Controller还是Service,还是要看具体的情况,个人认为如果是简单的参数在Controller中进行封装时是完全可以的,如果放到Service反而会显得很冗余,而且导致Service通用性变差;对于第二个问题,不容同意通过枚举或者不同的状态码来在Controller左做判断抛出异常,完全可以自己定义一套异常处理机制,直接在Service层抛出,项目有针对此类业务异常的处理机制,直接两将Service的错误信息和错误码返回到View层,让客户端根据状态码和错误信息作处理

推荐问题
宣传栏