controller 层是否需要抽取 CRUD?

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

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 回帖
  • 架构

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

    139 引用 • 441 回帖 • 1 关注

相关帖子

欢迎来到这里!

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

注册 关于
请输入回帖内容 ...
  • 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;
    	}
    	
    	
    }
    
    
  • hjljy
    作者

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

  • 88250 1

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

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

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

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

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

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

    1 回复
  • hjljy
    作者

    看了看项目整体代码,发现子类控制器基本都重写了 crud,感觉意义不大。

  • InkDP

    我们公司做 go 的,公司自己的框架是抽离了的,感觉 controller 更像个总控,接受数据,验证数据,送去给 server 完成业务。感觉这样子写很清晰

请输入回帖内容 ...

推荐标签 标签

  • WebSocket

    WebSocket 是 HTML5 中定义的一种新协议,它实现了浏览器与服务器之间的全双工通信(full-duplex)。

    48 引用 • 206 回帖 • 399 关注
  • 宕机

    宕机,多指一些网站、游戏、网络应用等服务器一种区别于正常运行的状态,也叫“Down 机”、“当机”或“死机”。宕机状态不仅仅是指服务器“挂掉了”、“死机了”状态,也包括服务器假死、停用、关闭等一些原因而导致出现的不能够正常运行的状态。

    13 引用 • 82 回帖 • 36 关注
  • 负能量

    上帝为你关上了一扇门,然后就去睡觉了....努力不一定能成功,但不努力一定很轻松 (° ー °〃)

    85 引用 • 1200 回帖 • 455 关注
  • 数据库

    据说 99% 的性能瓶颈都在数据库。

    330 引用 • 614 回帖
  • 生活

    生活是指人类生存过程中的各项活动的总和,范畴较广,一般指为幸福的意义而存在。生活实际上是对人生的一种诠释。生活包括人类在社会中与自己息息相关的日常活动和心理影射。

    228 引用 • 1450 回帖 • 1 关注
  • Ngui

    Ngui 是一个 GUI 的排版显示引擎和跨平台的 GUI 应用程序开发框架,基于
    Node.js / OpenGL。目标是在此基础上开发 GUI 应用程序可拥有开发 WEB 应用般简单与速度同时兼顾 Native 应用程序的性能与体验。

    7 引用 • 9 回帖 • 344 关注
  • 酷鸟浏览器

    安全 · 稳定 · 快速
    为跨境从业人员提供专业的跨境浏览器

    3 引用 • 59 回帖 • 24 关注
  • Ant-Design

    Ant Design 是服务于企业级产品的设计体系,基于确定和自然的设计价值观上的模块化解决方案,让设计者和开发者专注于更好的用户体验。

    17 引用 • 23 回帖 • 1 关注
  • Markdown

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

    163 引用 • 1450 回帖
  • 反馈

    Communication channel for makers and users.

    123 引用 • 906 回帖 • 189 关注
  • 游戏

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

    169 引用 • 799 回帖
  • 大数据

    大数据(big data)是指无法在一定时间范围内用常规软件工具进行捕捉、管理和处理的数据集合,是需要新处理模式才能具有更强的决策力、洞察发现力和流程优化能力的海量、高增长率和多样化的信息资产。

    89 引用 • 113 回帖 • 1 关注
  • React

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

    192 引用 • 291 回帖 • 441 关注
  • 职场

    找到自己的位置,萌新烦恼少。

    126 引用 • 1699 回帖 • 1 关注
  • 工具

    子曰:“工欲善其事,必先利其器。”

    273 引用 • 678 回帖
  • QQ

    1999 年 2 月腾讯正式推出“腾讯 QQ”,在线用户由 1999 年的 2 人(马化腾和张志东)到现在已经发展到上亿用户了,在线人数超过一亿,是目前使用最广泛的聊天软件之一。

    45 引用 • 557 回帖 • 228 关注
  • Tomcat

    Tomcat 最早是由 Sun Microsystems 开发的一个 Servlet 容器,在 1999 年被捐献给 ASF(Apache Software Foundation),隶属于 Jakarta 项目,现在已经独立为一个顶级项目。Tomcat 主要实现了 JavaEE 中的 Servlet、JSP 规范,同时也提供 HTTP 服务,是市场上非常流行的 Java Web 容器。

    162 引用 • 529 回帖
  • App

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

    90 引用 • 383 回帖
  • ZooKeeper

    ZooKeeper 是一个分布式的,开放源码的分布式应用程序协调服务,是 Google 的 Chubby 一个开源的实现,是 Hadoop 和 HBase 的重要组件。它是一个为分布式应用提供一致性服务的软件,提供的功能包括:配置维护、域名服务、分布式同步、组服务等。

    59 引用 • 29 回帖 • 15 关注
  • BAE

    百度应用引擎(Baidu App Engine)提供了 PHP、Java、Python 的执行环境,以及云存储、消息服务、云数据库等全面的云服务。它可以让开发者实现自动地部署和管理应用,并且提供动态扩容和负载均衡的运行环境,让开发者不用考虑高成本的运维工作,只需专注于业务逻辑,大大降低了开发者学习和迁移的成本。

    19 引用 • 75 回帖 • 617 关注
  • LeetCode

    LeetCode(力扣)是一个全球极客挚爱的高质量技术成长平台,想要学习和提升专业能力从这里开始,充足技术干货等你来啃,轻松拿下 Dream Offer!

    209 引用 • 72 回帖
  • 前端

    前端技术一般分为前端设计和前端开发,前端设计可以理解为网站的视觉设计,前端开发则是网站的前台代码实现,包括 HTML、CSS 以及 JavaScript 等。

    247 引用 • 1347 回帖
  • Electron

    Electron 基于 Chromium 和 Node.js,让你可以使用 HTML、CSS 和 JavaScript 构建应用。它是一个由 GitHub 及众多贡献者组成的活跃社区共同维护的开源项目,兼容 Mac、Windows 和 Linux,它构建的应用可在这三个操作系统上面运行。

    15 引用 • 136 回帖 • 7 关注
  • C

    C 语言是一门通用计算机编程语言,应用广泛。C 语言的设计目标是提供一种能以简易的方式编译、处理低级存储器、产生少量的机器码以及不需要任何运行环境支持便能运行的编程语言。

    83 引用 • 165 回帖 • 53 关注
  • abitmean

    有点意思就行了

    19 关注
  • Telegram

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

    5 引用 • 35 回帖
  • BND

    BND(Baidu Netdisk Downloader)是一款图形界面的百度网盘不限速下载器,支持 Windows、Linux 和 Mac,详细介绍请看这里

    107 引用 • 1281 回帖 • 19 关注