controller 层是否需要抽取 CRUD?

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

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

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

    142 引用 • 442 回帖

相关帖子

欢迎来到这里!

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

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

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

  • 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 回复
  • 88250 1

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

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

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

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

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

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

    1 回复
  • 查看全部回帖

推荐标签 标签

  • DevOps

    DevOps(Development 和 Operations 的组合词)是一组过程、方法与系统的统称,用于促进开发(应用程序/软件工程)、技术运营和质量保障(QA)部门之间的沟通、协作与整合。

    47 引用 • 25 回帖
  • IDEA

    IDEA 全称 IntelliJ IDEA,是一款 Java 语言开发的集成环境,在业界被公认为最好的 Java 开发工具之一。IDEA 是 JetBrains 公司的产品,这家公司总部位于捷克共和国的首都布拉格,开发人员以严谨著称的东欧程序员为主。

    180 引用 • 400 回帖
  • GitHub

    GitHub 于 2008 年上线,目前,除了 Git 代码仓库托管及基本的 Web 管理界面以外,还提供了订阅、讨论组、文本渲染、在线文件编辑器、协作图谱(报表)、代码片段分享(Gist)等功能。正因为这些功能所提供的便利,又经过长期的积累,GitHub 的用户活跃度很高,在开源世界里享有深远的声望,并形成了社交化编程文化(Social Coding)。

    209 引用 • 2031 回帖
  • C

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

    85 引用 • 165 回帖 • 3 关注
  • 域名

    域名(Domain Name),简称域名、网域,是由一串用点分隔的名字组成的 Internet 上某一台计算机或计算机组的名称,用于在数据传输时标识计算机的电子方位(有时也指地理位置)。

    43 引用 • 208 回帖 • 2 关注
  • ZeroNet

    ZeroNet 是一个基于比特币加密技术和 BT 网络技术的去中心化的、开放开源的网络和交流系统。

    1 引用 • 21 回帖 • 638 关注
  • RYMCU

    RYMCU 致力于打造一个即严谨又活泼、专业又不失有趣,为数百万人服务的开源嵌入式知识学习交流平台。

    4 引用 • 6 回帖 • 52 关注
  • Redis

    Redis 是一个开源的使用 ANSI C 语言编写、支持网络、可基于内存亦可持久化的日志型、Key-Value 数据库,并提供多种语言的 API。从 2010 年 3 月 15 日起,Redis 的开发工作由 VMware 主持。从 2013 年 5 月开始,Redis 的开发由 Pivotal 赞助。

    286 引用 • 248 回帖 • 62 关注
  • Kubernetes

    Kubernetes 是 Google 开源的一个容器编排引擎,它支持自动化部署、大规模可伸缩、应用容器化管理。

    110 引用 • 54 回帖
  • 博客

    记录并分享人生的经历。

    273 引用 • 2388 回帖
  • 游戏

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

    176 引用 • 815 回帖
  • SOHO

    为成为自由职业者在家办公而努力吧!

    7 引用 • 55 回帖 • 17 关注
  • 设计模式

    设计模式(Design pattern)代表了最佳的实践,通常被有经验的面向对象的软件开发人员所采用。设计模式是软件开发人员在软件开发过程中面临的一般问题的解决方案。这些解决方案是众多软件开发人员经过相当长的一段时间的试验和错误总结出来的。

    200 引用 • 120 回帖
  • Quicker

    Quicker 您的指尖工具箱!操作更少,收获更多!

    32 引用 • 131 回帖 • 2 关注
  • Docker

    Docker 是一个开源的应用容器引擎,让开发者可以打包他们的应用以及依赖包到一个可移植的容器中,然后发布到任何流行的操作系统上。容器完全使用沙箱机制,几乎没有性能开销,可以很容易地在机器和数据中心中运行。

    491 引用 • 917 回帖 • 2 关注
  • RIP

    愿逝者安息!

    8 引用 • 92 回帖 • 351 关注
  • SVN

    SVN 是 Subversion 的简称,是一个开放源代码的版本控制系统,相较于 RCS、CVS,它采用了分支管理系统,它的设计目标就是取代 CVS。

    29 引用 • 98 回帖 • 683 关注
  • Spring

    Spring 是一个开源框架,是于 2003 年兴起的一个轻量级的 Java 开发框架,由 Rod Johnson 在其著作《Expert One-On-One J2EE Development and Design》中阐述的部分理念和原型衍生而来。它是为了解决企业应用开发的复杂性而创建的。框架的主要优势之一就是其分层架构,分层架构允许使用者选择使用哪一个组件,同时为 JavaEE 应用程序开发提供集成的框架。

    944 引用 • 1459 回帖 • 16 关注
  • 外包

    有空闲时间是接外包好呢还是学习好呢?

    26 引用 • 232 回帖 • 4 关注
  • V2Ray
    1 引用 • 15 回帖 • 2 关注
  • 禅道

    禅道是一款国产的开源项目管理软件,她的核心管理思想基于敏捷方法 scrum,内置了产品管理和项目管理,同时又根据国内研发现状补充了测试管理、计划管理、发布管理、文档管理、事务管理等功能,在一个软件中就可以将软件研发中的需求、任务、bug、用例、计划、发布等要素有序的跟踪管理起来,完整地覆盖了项目管理的核心流程。

    6 引用 • 15 回帖 • 113 关注
  • SQLServer

    SQL Server 是由 [微软] 开发和推广的关系数据库管理系统(DBMS),它最初是由 微软、Sybase 和 Ashton-Tate 三家公司共同开发的,并于 1988 年推出了第一个 OS/2 版本。

    21 引用 • 31 回帖 • 1 关注
  • iOS

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

    85 引用 • 139 回帖
  • 正则表达式

    正则表达式(Regular Expression)使用单个字符串来描述、匹配一系列遵循某个句法规则的字符串。

    31 引用 • 94 回帖
  • 职场

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

    127 引用 • 1705 回帖
  • Windows

    Microsoft Windows 是美国微软公司研发的一套操作系统,它问世于 1985 年,起初仅仅是 Microsoft-DOS 模拟环境,后续的系统版本由于微软不断的更新升级,不但易用,也慢慢的成为家家户户人们最喜爱的操作系统。

    222 引用 • 473 回帖
  • 面试

    面试造航母,上班拧螺丝。多面试,少加班。

    325 引用 • 1395 回帖 • 1 关注