大家好,欢迎来到IT知识分享网。
在初学Spring时曾被Service绕晕,为何MVC模式下会多出一个Service层?设计Service时候为何需要先写一个接口,然后再去实现?Service之间是否可以相互调用?而这篇文章就是当初疑问的解决,也是对MVC模式深入理解。
Service从何而来
Spring MVC,是一个MVC框架,提到MVC,大家都不陌生,简单说一下,M为模型层,处理数据逻辑,V为视图层,负责展示,而C为控制层,负责M与V的交互。
在我大学的课堂上,也有学习到MVC模式,最简单的javabean+jsp+serlvet构成MVC模式,而老师千叮万嘱我们,Servlet中的逻辑一定要少,逻辑部分应该放在javabean,即模型层处理,但是模型层还肩负着存储数据的任务(其实这个就是Model所负责的数据逻辑,要实现数据逻辑,要有数据,要有处理),而Service就是将处理,也可以说业务,抽离出来,可以说是服务层,也可以说是业务层。MVC的划分概念,更加细致。
为什么设计Service时候需要先写接口
业务层接口
public interface IUserService {
boolean login(User user);
}
业务层实现类
@Service("userService")
public class UserServiceImpl implements IUserService {
public boolean login(User user) {
//登录判断逻辑
}
}
在初学时,我写Service时大概就是这种感觉,但并没有想过为什么要这样做。要想理解为什么要这样写Service,首先要理解,接口的作用是什么。
接口的作用
接口主要用于描述类具有什么功能,而并不给出每个功能的具体实现。一个类可以实现一个或多个接口,并在需要接口的地方,随时使用实现了相应接口的对象。——《Java核心技术卷一》
这句话所描述的就是接口所带来的扩展性。而用我的话去概括,就是「统一的接入」与「统一的暴露」,举一个例子来解释,数据源的接口DataSource。
统一的接入是对于数据源的开发者来说,要实现数据源,就需要去实现DataSource接口,然后实现其方法。对于DBCP、c3p0、Druid数据源来说,不同的开发者,实现同一套东西,这就是统一的接入。
统一的暴露是对于数据源的使用者来说。对于数据源的使用者,使用时所要关注的是如何获取数据库连接的动作,即getConnection方法,至于这个动作的具体实现,不需要知道也不关心。
统一的接入与统一暴露,将实现与使用分离开来,也就是接口所带来的好处。
开头所写的IUserService的例子,所做的就是将登陆的实现与使用分离,举一个具体的例子
@Service("databaseUserService")
public class DatabaseUserServiceImpl implements IUserService {
public boolean login(User user) {
//从数据库查询用户信息,判断是否账号/密码是否一致
}
}
@Service("oauthUserService")
public class OauthUserServiceImpl implements IUserService {
public boolean login(User user) {
//使用Oauth登陆,如微信登陆
}
}
对于登陆的实现,就可以分为从数据库查询和使用第三方授权登陆两种方式分别实现,对于使用者来说,所关注的就是login这一动作,整个登陆操作变得十分灵活,根据不同的场景使用不同的方式登陆。
看起来这种实现Service的方式非常不错,以后写Service都这样写吧。
想一下之前所写的代码,通常都是一个Service接口对应一个实现,基本上是没有第二个实现的必要,也就是所获取到接口的好处非常少,但是却付出了实现接口的代价,即对动作的抽象,这是一个成本,例如你想为Service添加功能,你需要先将这个功能抽象出来,其中的参数,返回值都要定好,如果需求改动,改动这个功能的功夫是不少的。这样的话,为何不去除这个借口,直接实现Service类呢?
去除接口,让Service成为业务
将接口去除,其实是将Service层,从服务改变为业务,即专注于业务,Service中的都是业务逻辑。有何区别呢?简单点来说,不用考虑向外提供服务,只为自己系统的业务功能提供服务。
对于一个中小项目来说,脱掉Service层接口的枷锁,实现起业务来,十分流畅,不再用抽象,只关注于业务即可。
在学校时,一位师兄经常提醒我,知其然知其所以然,只有清楚为何写Service时需要先写接口,才能明白为何要去除接口。
Service的互相调用
Service间是否可以相互调用,这个很久当时是在困扰了我很久,现在可以明确的说,Service层不应该相互调用,特别是我上面提到的去除接口,让Service完全成为一个业务体的设计下,更加不应该相互调用Service。
你会问,一个Service的实现的业务,确实可以在另一个Service中用到,难道要重新写?要清楚,这个情况是出于Service间有通用的逻辑,而不是通用的业务,每个Service对应一个业务,业务之间应该有明确的分界,不然会出现业务间的耦合,这是设计的不合理。
既然是Service间的逻辑通用,我们大可创建一个ServiceHelper类,里面放的,就是Service间的通用逻辑,各自调用这个逻辑即可。当然如果系统庞大起来,这种情况会经常出现,这时再抽象一层,可以叫provider层,提供操作逻辑,例如发短信功能,provider里放的是如何发短信的操作逻辑,而Service层放的是什么时候发短信,发多少,发给谁的业务逻辑。
业务逻辑只由Service自己知道
上面所写的UserService例子
@Service("userService")
public class UserService {
public boolean login(User user) {
//登录判断逻辑
}
}
这样写,我觉得还不是最好。这个Service最终会被一个Controller所调用,当Controller调用该方法时,还需要判断返回值是true还是false,再返回结果,其逻辑“泄露”了,因为Controller要了解业务是true是登陆成功,false是登陆失败。如下
@Controller @RequestMapping(“/user”) public class UserController extends BaseController {
@Autowired
private UserService userService;
@RequestMapping(value = "/login", method = RequestMethod.POST)
public
@ResponseBody ResponseResult login(@RequestBody User form) {
if(userService.login(form)) {
return ResponseResult.getOk("登陆成功");
} else {
return ResponseResult.getError("登陆失败");
}
}
}
Controller不应该知道怎么才算登陆成功,登陆失败,它应该只需要知道调用那个业务。
我们可以这样写
@Service("userService")
public class UserService {
/** * @return ResponseResult响应结果.为{"status":"状态码","msg":"响应信息","data":"响应数据"} */
public ResponseResult login(User entity) {
//用MD5加密密码
entity.setPassword(MD5Utils.getMD5(entity.getPassword()));
//用账号和加密后的密码为查询条件,查询数据库中是否有对应的数据
Optional<User> optional = userRepository.selectOne(entity);
return optional.map(ResponseResult::getOk).orElseGet(() -> ResponseResult.getError("账号/密码错误"));
}
}
@Controller
@RequestMapping("/user")
public class UserController extends BaseController {
@Autowired
UserService userService;
@RequestMapping(value = "/login", method = RequestMethod.POST)
public
@ResponseBodyResponseResult login(@RequestBody User form){
return userService.login(form);
}
}
这样写,不就向Controller屏蔽了登陆是否成功的判断逻辑。
平时我们一直说,Controller一定要尽量少的逻辑,其实反过来说,是指Service的逻辑应该高内聚,这样Controller如Service的耦合自然就是最低,Controller真真正正的坐到,不用理会Service的实现,只需要调用即可。
当然这个登陆的例子可能有点小题大做,但是如果这个业务是查找一批用户,如果查找不到需要返回错误码呢?难道要在Controller层去判断返回的集合是否为空,然后构造返回信息吗?这样其实是将Service的部分逻辑放在了Controller去完成。
转载https://my.oschina.net/bingzhong/blog/1559856
免责声明:本站所有文章内容,图片,视频等均是来源于用户投稿和互联网及文摘转载整编而成,不代表本站观点,不承担相关法律责任。其著作权各归其原作者或其出版社所有。如发现本站有涉嫌抄袭侵权/违法违规的内容,侵犯到您的权益,请在线联系站长,一经查实,本站将立刻删除。
本文来自网络,若有侵权,请联系删除,如若转载,请注明出处:https://yundeesoft.com/22275.html