"controller 的抽取 最近看项目,看到某个项目当中对 controller 里面的 CRUD 进行了抽取,代码如下: public abstract class BaseController> { @Autowired protected S baseService; @PutMapping public .."

controller 层是否需要抽取 CRUD?

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

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

    65 引用 • 333 回帖
6 回帖   
请输入回帖内容...
  • PeterChu  

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

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

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

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

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

    1 回复
  • hjljy        

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

  • jinjianh  

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

请输入回帖内容 ...