controller 层是否需要抽取 CRUD?

本贴最后更新于 1771 天前,其中的信息可能已经时异事殊

controller 的抽取

最近看项目,看到某个项目当中对 controller 里面的 CRUD 进行了抽取,代码如下:

public abstract class BaseController<E extends Serializable, S extends IService<E>> {

    @Autowired
    protected S  baseService;

    @PutMapping
    public E save(E e) {
        baseService.save(e);
        log.debug("保存-{}", JSONObject.toJSON(e));
        return e;
    }

    @GetMapping("list/{currentPage}/{size}")
    public IPage<E> list(@PathVariable("currentPage") int currentPage, @PathVariable("size") int size) {
        return baseService.page(new Page<>(currentPage, size));
    }

    @GetMapping("{id}")
    @ApiImplicitParams({
            @ApiImplicitParam(name = "id", value = "id", paramType = "path", dataType = "String")})
    public E detail(@PathVariable("id") Serializable id) {
        E e = baseService.getById(id);
        return e;
    }

    @DeleteMapping("{id}")
    @ApiImplicitParams({
            @ApiImplicitParam(name = "id", value = "id", paramType = "path", dataType = "String")})
    public void delete(@PathVariable("id") Serializable id) {
        baseService.removeById(id);
        log.debug("删除-{}", id);
    }


    @PostMapping
    public E update(E e) {
        baseService.updateById(e);
        log.debug("修改-{}", JSONObject.toJSON(e));
        return e;
    }


    @GetMapping
    public IPage<E> list(Page<E> page) {
        return baseService.page(page);
    }
}

使用时,其他的 controller 只需要继承这个类,传入对应的实体和 service 接口就可以了。

感受

之前是没有抽取过 controller 层的 CRUD,看到了这个 controller 后,自己尝试了一下,感受到了这么做的一些优缺点,如下:

  1. 跟随请求进入某个具体的 controller 后,无法直接找到 service 接口实现。
  2. 规定好了 CRUD 的请求类型及路径,统一化。
  3. 感觉有点不符合单一职责。

想问问有没有大佬知道这么做的好处啊?或者这么做合适么?

  • MVC
    7 引用 • 119 回帖
  • 架构

    我们平时所说的“架构”主要是指软件架构,这是有关软件整体结构与组件的抽象描述,用于指导软件系统各个方面的设计。另外还有“业务架构”、“网络架构”、“硬件架构”等细分领域。

    140 引用 • 441 回帖

相关帖子

欢迎来到这里!

我们正在构建一个小众社区,大家在这里相互信任,以平等 • 自由 • 奔放的价值观进行分享交流。最终,希望大家能够找到与自己志同道合的伙伴,共同成长。

注册 关于
请输入回帖内容 ...
  • hjljy
    作者

    感谢,对比还是很大的,这个没有涉及到具体业务,是对业务的校验,错误处理等。我那个是涉及到了具体业务。

  • 其他回帖
  • PeterChu 1

    这是 jeeplus 中的 Controller 层基类,你对比下

    /**
     * 控制器支持类
     * @author jeeplus
     * @version 2016-3-23
     */
    public abstract class BaseController {
    
    	/**
    	 * 日志对象
    	 */
    	protected Logger logger = LoggerFactory.getLogger(getClass());
    
    	/**
    	 * 管理基础路径
    	 */
    	@Value("${adminPath}")
    	protected String adminPath;
    	
    	/**
    	 * 前端基础路径
    	 */
    	@Value("${frontPath}")
    	protected String frontPath;
    	
    	/**
    	 * 前端URL后缀
    	 */
    	@Value("${urlSuffix}")
    	protected String urlSuffix;
    	
    	/**
    	 * 验证Bean实例对象
    	 */
    	@Autowired
    	protected Validator validator;
    
    	/**
    	 * 服务端参数有效性验证
    	 * @param object 验证的实体对象
    	 * @param groups 验证组
    	 * @return 验证成功:返回true;严重失败:将错误信息添加到 message 中
    	 */
    	protected boolean beanValidator(Model model, Object object, Class<?>... groups) {
    		try{
    			BeanValidators.validateWithException(validator, object, groups);
    		}catch(ConstraintViolationException ex){
    			List<String> list = BeanValidators.extractPropertyAndMessageAsList(ex, ": ");
    			list.add(0, "数据验证失败:");
    			addMessage(model, list.toArray(new String[]{}));
    			return false;
    		}
    		return true;
    	}
    	
    	/**
    	 * 服务端参数有效性验证
    	 * @param object 验证的实体对象
    	 * @param groups 验证组
    	 * @return 验证成功:返回true;严重失败:将错误信息添加到 flash message 中
    	 */
    	protected boolean beanValidator(RedirectAttributes redirectAttributes, Object object, Class<?>... groups) {
    		try{
    			BeanValidators.validateWithException(validator, object, groups);
    		}catch(ConstraintViolationException ex){
    			List<String> list = BeanValidators.extractPropertyAndMessageAsList(ex, ": ");
    			list.add(0, "数据验证失败:");
    			addMessage(redirectAttributes, list.toArray(new String[]{}));
    			return false;
    		}
    		return true;
    	}
    	
    	/**
    	 * 服务端参数有效性验证
    	 * @param object 验证的实体对象
    	 * @param groups 验证组,不传入此参数时,同@Valid注解验证
    	 * @return 验证成功:继续执行;验证失败:抛出异常跳转400页面。
    	 */
    	protected void beanValidator(Object object, Class<?>... groups) {
    		BeanValidators.validateWithException(validator, object, groups);
    	}
    	
    	/**
    	 * 添加Model消息
    	 * @param message
    	 */
    	protected void addMessage(Model model, String... messages) {
    		StringBuilder sb = new StringBuilder();
    		for (String message : messages){
    			sb.append(message).append(messages.length>1?"<br/>":"");
    		}
    		model.addAttribute("message", sb.toString());
    	}
    	
    	/**
    	 * 添加Flash消息
    	 * @param message
    	 */
    	protected void addMessage(RedirectAttributes redirectAttributes, String... messages) {
    		StringBuilder sb = new StringBuilder();
    		for (String message : messages){
    			sb.append(message).append(messages.length>1?"<br/>":"");
    		}
    		redirectAttributes.addFlashAttribute("message", sb.toString());
    	}
    	
    	/**
    	 * 客户端返回JSON字符串
    	 * @param response
    	 * @param object
    	 * @return
    	 */
    	protected String renderString(HttpServletResponse response, Object object) {
    		return renderString(response, JsonMapper.toJsonString(object));
    	}
    	
    	/**
    	 * 客户端返回字符串
    	 * @param response
    	 * @param string
    	 * @return
    	 */
    	protected String renderString(HttpServletResponse response, String string) {
    		try {
    			response.reset();
    	        response.setContentType("application/json");
    	        response.setCharacterEncoding("utf-8");
    			response.getWriter().print(string);
    			return null;
    		} catch (IOException e) {
    			return null;
    		}
    	}
    
    	/**
    	 * 参数绑定异常
    	 */
    	@ExceptionHandler({BindException.class, ConstraintViolationException.class, ValidationException.class})
        public String bindException() {  
            return "error/400";
        }
    
    
    
    1 回复
  • PeterChu

    接上楼(😳 一楼最多 4096 字符)

    	
    	/**
    	 * 授权登录异常
    	 */
    	@ExceptionHandler({AuthenticationException.class})
        public String authenticationException() {  
            return "error/403";
        }
    	
    	/**
    	 *系统登录异常
    	 */
    	@ExceptionHandler({Exception.class})
        public String exception() {  
            return "error/500";
        }
    	
    	/**
    	 * 初始化数据绑定
    	 * 1. 将所有传递进来的String进行HTML编码,防止XSS攻击
    	 * 2. 将字段中Date类型转换为String类型
    	 */
    	@InitBinder
    	protected void initBinder(WebDataBinder binder) {
    		// String类型转换,将所有传递进来的String进行HTML编码,防止XSS攻击
    		binder.registerCustomEditor(String.class, new PropertyEditorSupport() {
    			@Override
    			public void setAsText(String text) {
    				setValue(text == null ? null : StringEscapeUtils.escapeHtml4(text.trim()));
    			}
    			@Override
    			public String getAsText() {
    				Object value = getValue();
    				return value != null ? value.toString() : "";
    			}
    		});
    		// Date 类型转换
    		binder.registerCustomEditor(Date.class, new PropertyEditorSupport() {
    			@Override
    			public void setAsText(String text) {
    				setValue(DateUtils.parseDate(text));
    			}
    //			@Override
    //			public String getAsText() {
    //				Object value = getValue();
    //				return value != null ? DateUtils.formatDateTime((Date)value) : "";
    //			}
    		});
    	}
    	
    	
    	/**
    	 * 获取bootstrap data分页数据
    	 * @param page
    	 * @return map对象
    	 */
    	public <T> Map<String, Object> getBootstrapData(Page page){
    		Map<String, Object> map = new HashMap<String, Object>();
    		map.put("rows", page.getList());
    		map.put("total", page.getCount());
    		return map;
    	}
    	
    	
    }
    
    
  • 88250 1

    好处是整体请求处理实现看上去可以比较统一,但实际上缺点是比较多的:

    1. 必须用 BaseXXX:BaseController、BaseService、BaseRepository、BaseObject。一般来说编程范式上鼓励用组合而不是继承,因为继承在某些时候容易冗余或者难以补足
    2. 控制器层的参数校验只能在 Service 中实现,如果子类控制器自己需要重写的话,那么这个 BaseController 抽象的意义就不大了,如果不需要重写,那么至少应该用模板方法进行抽象

    总的来说就是这个设计抽象程度不足,如果还能继续抽象提供扩展,并在整套设计上进行配套,那还是有点“好看”的意义的。但说实话,除非设计模式玩得很溜才敢在看似简单的 CRUD 业务上做抽象设计,否则建议不要做,因为意义真的不大;如果做了,新来搬砖的人可能会觉得设计很奇葩,说不定搬两天就走了,因为坑实在太多 😂

    在 Controller 层如果需要处理类似场景,建议以下两种方案:

    1. 通过代码生成器生成,实现上各个控制器依然是完全独立
    2. 实现一个 REST CRUD 控制器,通过数据 Type 参数路由具体的 XXXService

    朴素的代码比什么都重要,可以节省大家大量的时间。

    1 回复
  • 查看全部回帖

推荐标签 标签

  • 游戏

    沉迷游戏伤身,强撸灰飞烟灭。

    169 引用 • 800 回帖
  • Python

    Python 是一种面向对象、直译式电脑编程语言,具有近二十年的发展历史,成熟且稳定。它包含了一组完善而且容易理解的标准库,能够轻松完成很多常见的任务。它的语法简捷和清晰,尽量使用无异义的英语单词,与其它大多数程序设计语言使用大括号不一样,它使用缩进来定义语句块。

    536 引用 • 672 回帖
  • Flume

    Flume 是一套分布式的、可靠的,可用于有效地收集、聚合和搬运大量日志数据的服务架构。

    9 引用 • 6 回帖 • 601 关注
  • PHP

    PHP(Hypertext Preprocessor)是一种开源脚本语言。语法吸收了 C 语言、 Java 和 Perl 的特点,主要适用于 Web 开发领域,据说是世界上最好的编程语言。

    165 引用 • 407 回帖 • 526 关注
  • Laravel

    Laravel 是一套简洁、优雅的 PHP Web 开发框架。它采用 MVC 设计,是一款崇尚开发效率的全栈框架。

    19 引用 • 23 回帖 • 692 关注
  • Caddy

    Caddy 是一款默认自动启用 HTTPS 的 HTTP/2 Web 服务器。

    10 引用 • 54 回帖 • 134 关注
  • App

    App(应用程序,Application 的缩写)一般指手机软件。

    90 引用 • 383 回帖 • 1 关注
  • 京东

    京东是中国最大的自营式电商企业,2015 年第一季度在中国自营式 B2C 电商市场的占有率为 56.3%。2014 年 5 月,京东在美国纳斯达克证券交易所正式挂牌上市(股票代码:JD),是中国第一个成功赴美上市的大型综合型电商平台,与腾讯、百度等中国互联网巨头共同跻身全球前十大互联网公司排行榜。

    14 引用 • 102 回帖 • 407 关注
  • 安装

    你若安好,便是晴天。

    130 引用 • 1184 回帖
  • 互联网

    互联网(Internet),又称网际网络,或音译因特网、英特网。互联网始于 1969 年美国的阿帕网,是网络与网络之间所串连成的庞大网络,这些网络以一组通用的协议相连,形成逻辑上的单一巨大国际网络。

    96 引用 • 330 回帖
  • Pipe

    Pipe 是一款小而美的开源博客平台。Pipe 有着非常活跃的社区,可将文章作为帖子推送到社区,来自社区的回帖将作为博客评论进行联动(具体细节请浏览 B3log 构思 - 分布式社区网络)。

    这是一种全新的网络社区体验,让热爱记录和分享的你不再感到孤单!

    131 引用 • 1114 回帖 • 146 关注
  • V2EX

    V2EX 是创意工作者们的社区。这里目前汇聚了超过 400,000 名主要来自互联网行业、游戏行业和媒体行业的创意工作者。V2EX 希望能够成为创意工作者们的生活和事业的一部分。

    17 引用 • 236 回帖 • 410 关注
  • jQuery

    jQuery 是一套跨浏览器的 JavaScript 库,强化 HTML 与 JavaScript 之间的操作。由 John Resig 在 2006 年 1 月的 BarCamp NYC 上释出第一个版本。全球约有 28% 的网站使用 jQuery,是非常受欢迎的 JavaScript 库。

    63 引用 • 134 回帖 • 742 关注
  • WiFiDog

    WiFiDog 是一套开源的无线热点认证管理工具,主要功能包括:位置相关的内容递送;用户认证和授权;集中式网络监控。

    1 引用 • 7 回帖 • 544 关注
  • OnlyOffice
    4 引用 • 17 关注
  • 机器学习

    机器学习(Machine Learning)是一门多领域交叉学科,涉及概率论、统计学、逼近论、凸分析、算法复杂度理论等多门学科。专门研究计算机怎样模拟或实现人类的学习行为,以获取新的知识或技能,重新组织已有的知识结构使之不断改善自身的性能。

    76 引用 • 37 回帖
  • Chrome

    Chrome 又称 Google 浏览器,是一个由谷歌公司开发的网页浏览器。该浏览器是基于其他开源软件所编写,包括 WebKit,目标是提升稳定性、速度和安全性,并创造出简单且有效率的使用者界面。

    60 引用 • 287 回帖
  • React

    React 是 Facebook 开源的一个用于构建 UI 的 JavaScript 库。

    192 引用 • 291 回帖 • 440 关注
  • Dubbo

    Dubbo 是一个分布式服务框架,致力于提供高性能和透明化的 RPC 远程服务调用方案,是 [阿里巴巴] SOA 服务化治理方案的核心框架,每天为 2,000+ 个服务提供 3,000,000,000+ 次访问量支持,并被广泛应用于阿里巴巴集团的各成员站点。

    60 引用 • 82 回帖 • 611 关注
  • Markdown

    Markdown 是一种轻量级标记语言,用户可使用纯文本编辑器来排版文档,最终通过 Markdown 引擎将文档转换为所需格式(比如 HTML、PDF 等)。

    165 引用 • 1460 回帖
  • BookxNote

    BookxNote 是一款全新的电子书学习工具,助力您的学习与思考,让您的大脑更高效的记忆。

    笔记整理交给我,一心只读圣贤书。

    1 引用 • 1 回帖 • 6 关注
  • Angular

    AngularAngularJS 的新版本。

    26 引用 • 66 回帖 • 512 关注
  • TGIF

    Thank God It's Friday! 感谢老天,总算到星期五啦!

    284 引用 • 4481 回帖 • 651 关注
  • iOS

    iOS 是由苹果公司开发的移动操作系统,最早于 2007 年 1 月 9 日的 Macworld 大会上公布这个系统,最初是设计给 iPhone 使用的,后来陆续套用到 iPod touch、iPad 以及 Apple TV 等产品上。iOS 与苹果的 Mac OS X 操作系统一样,属于类 Unix 的商业操作系统。

    84 引用 • 139 回帖
  • Telegram

    Telegram 是一个非盈利性、基于云端的即时消息服务。它提供了支持各大操作系统平台的开源的客户端,也提供了很多强大的 APIs 给开发者创建自己的客户端和机器人。

    5 引用 • 35 回帖
  • 友情链接

    确认过眼神后的灵魂连接,站在链在!

    24 引用 • 373 回帖
  • 钉钉

    钉钉,专为中国企业打造的免费沟通协同多端平台, 阿里巴巴出品。

    15 引用 • 67 回帖 • 366 关注