基于 Java 接口的控制器

问题的提出:一般情况下,我们是这样定义一个 Spring Boot/MVC 控制器的:

@PostMapping("/save/{id}")
@ResponseBody
public Book save(@RequestBody Book book, @PathVariable int id) {
    // 调用你的业务类
}

一直如此没啥问题。但我们渐渐发现,这个 Controller 里面只有一行调用 Service 的方法……于是我们考虑能不能把这一行代码都省呢?——答案是肯定的!我们就来看看怎么做。

把接口作控制器

Spring MVC 5.1 引入了 新功能

Controller parameter annotations get detected on interfaces as well: Allowing for complete mapping contracts in controller interfaces.
在接口上的控制器注解也能检测到,并自动映射到实现类上。

说白了就是控制器只是个 Java Interface,对应的实现却不是控制器,而是业务类。这样相当于简化了 Controller 编写。我们用一个用户的例子来看看。首先定义控制器,这里提供了相关的 MVC 的注解。

import com.ajaxjs.user.model.User;
import org.springframework.web.bind.annotation.*;

@RestController
@RequestMapping("/user")
public interface UserController {
    @GetMapping("/{id}")
    User info(@PathVariable Long id);

    @PostMapping
    Long create(@RequestBody User user);

    /**
        * 检查用户某个值是否已经存在一样的值
        *
        * @param field 字段名,当前只能是 username/email/phone 中的任意一种
        * @param value 字段值,要校验的值
        * @return 是否已经存在一样的值,true 表示存在
        */
    @GetMapping("/checkRepeat")
    Boolean checkRepeat(@RequestParam String field, @RequestParam Object value);

    @PutMapping
    Boolean update(@RequestBody User user);

    @DeleteMapping("/{id}")
    Boolean delete(@PathVariable Long id);
}

接着我们实现这个接口,特别地,这是个业务类。当然你也可以说他是控制器类,但这样就失去简化的意义了,只是把之前类上的注解搬到接口身上,并没有简化控制器。我们看看实现类:

import com.ajaxjs.data.CRUD;
import com.ajaxjs.data.entity.CrudUtils;
import com.ajaxjs.framework.entity.BaseEntityConstants;
import com.ajaxjs.sass.SaasUtils;
import com.ajaxjs.user.controller.UserController;
import com.ajaxjs.user.model.User;
import org.springframework.stereotype.Service;

import javax.validation.Valid;

@Service
public class UserService implements UserController {
    @Override
    public User info(Long id) {
        String sql = "SELECT * FROM user WHERE stat != 1 AND id = ?";
        sql = SaasUtils.addTenantIdQuery(sql);

        return CRUD.info(User.class, sql, id);
    }

    @Override
    public Long create(@Valid User user) {
        if (checkRepeat("username", user.getUsername()))
            throw new IllegalArgumentException("用户的登录名" + user.getUsername() + "重复");

        return CRUD.create(user);
    }

    @Override
    public Boolean checkRepeat(String field, Object value) {
        String sql = "SELECT * FROM user WHERE stat != 1 AND " + field + " = ?";
        sql = SaasUtils.addTenantIdQuery(sql);
        sql += "LIMIT 1";

        return CRUD.info(sql, value) != null;
    }

    @Override
    public Boolean update(User user) {
        CrudUtils.checkId(user);

        return CRUD.update(user);
    }

    @Override
    public Boolean delete(Long id) {
        // 逻辑删除
        User user = new User();
        user.setId(id);
        user.setStat(BaseEntityConstants.STATUS_DELETED);

        return update(user);
    }
}

这有点控制器与业务类合二为一的味道。实际上为我们减少不少编码量,还是相当值得使用的。