如何编写优雅的 Controller 代码?

开发 前端
看过很多代码,业务逻辑全部写在 Controller层,并不能说这样的做法是错的,但是看起来很别扭,不优雅!那么,Controller里面应该如何编写才算优雅呢?

作为一名 Java程序员,对 Controller肯定不陌生,它是与外部客户端通信的入口,比如常见的 REST 操作(GET、PUT、POST、DELETE等),那么,Controller里面应该如何编写才算优雅呢?

其实,一个优雅的 Controller,里面的代码主要包含下面 6个部分:

  • 接收 HTTP(s)请求
  • 解析请求参数
  • 验证请求参数
  • 调用业务方法
  • 组织返回数据
  • 统一异常处理

下面一一讲解这 6个部分:

一、接收 HTTP(s)请求

接收 HTTP(s)请求是 Controller的入口,这里以查询用户信息为例进行说明,如下代码:

@RestController
public class UserController {
    @GetMapping("/user/{userId}")
    public void getUserById(@PathVariable String userId) {
        // 业务逻辑
    }
}

在上面的示例中,我们使用 URL/user/{id}接收用户发出的 GET请求,然后通过getUserById方法进行真实的业务处理。通过上面的代码,一个请求就被 Controller层成功接收了。

二、解析请求参数

接收到请求后,一般需要对请求参数进行解析,如下示例代码:

@RestController
public class UserController {
    @PostMapping("/user/register")
    public void getGradeById(@RequestBody User user) {
        // 代码逻辑
    }
}

public class User {
    private String nickname;
    private Integer age;
    // getters and setters and constructors
}

上述示例代码将请求的 body映射到 User对象上,因此,请求的 body体应该是:

{
  "nickname": "huahua",
  "age": "18"
}

在 SpringMVC 中,常见的参数类型及其用途如下:

1.原始 HTTP请求和响应对象

直接接收原始的 HTTP请求和响应对象,HttpServletRequest 和 HttpServletResponse

@RequestMapping("/test")
public void example(HttpServletRequest request, HttpServletResponse response) {
    // 处理请求和响应
}

2.路径变量 (@PathVariable)

用于获取 URL 路径中的动态部分。

@RequestMapping("/user/{id}")
public String getUser(@PathVariable("id") String userId) {
    // 使用 userId 进行处理
    return "userDetail";
}

3.请求参数 (@RequestParam)

用于获取 URL 查询参数或表单数据。

@RequestMapping("/search")
public String search(@RequestParam("query") String query) {
    // 使用 query 进行搜索
    return "searchResults";
}

4.请求体 (@RequestBody)

用于接收请求体中的数据,常用于处理 JSON 或 XML 格式的数据。

@RequestMapping(value = "/create", method = RequestMethod.POST)
public String create(@RequestBody User user) {
    // 处理 user 对象
    return "user";
}

5.模型属性 (@ModelAttribute)

用于绑定表单数据到模型对象。

@RequestMapping("/register")
public String register(@ModelAttribute User user) {
    // 处理 user 对象
    return "user";
}

6.会话属性 (@SessionAttribute)

用于访问会话中的属性。

@RequestMapping("/profile")
public String profile(@SessionAttribute("user") User user) {
    // 处理会话中的 user 对象
    return "profile";
}

7.请求头 (@RequestHeader)

用于访问 HTTP 请求头信息。

@RequestMapping("/headers")
public String headers(@RequestHeader("User-Agent") String userAgent) {
    // 使用 userAgent 进行处理
    return "headerInfo";
}

8.Cookie 值 (@CookieValue)

用于访问 Cookie 的值。

@RequestMapping("/cookies")
public String cookies(@CookieValue("sessionId") String sessionId) {
    // 使用 sessionId 进行处理
    return sessionId;
}

9.自定义参数解析器

可以通过实现 HandlerMethodArgumentResolver接口来自定义参数解析逻辑。

@RequestMapping("/custom")
public String custom(CustomObject customObject) {
// 使用自定义对象进行处理
    return "";
}

三、验证请求参数

请求参数的验证需要在 Controller层完成,如下代码,对 nickname进行判空处理,参数验证一般有 2种方式:

  • 原始方式,这种方式比较灵活,如果需要对参数进行一些逻辑计算后再校验;
  • 借助三方工具,比如 Spring validation,javax validation等,这种方式灵活度会低一些,但是更优雅;
// 原始方式校验参数
@RestController
public class UserController {
    @PostMapping("/user/register")
    public void getGradeById(@RequestBody User user) {
        // 代码逻辑
        if (StringUtils.isBlank(user.getNickname)) {
            throw new Exception("Nickname is required.");
        }
    }
}

或者使用 Spring validation验证机制,Controller需要增加@Validated注解,User对象中增加@NotBlank注解。

// 借助Spring validation方式校验参数
@RestController
public class UserController {
    @PostMapping("/user/register")
    public void getGradeById(@Validated @RequestBody User user) {
        // 代码逻辑
    }
}

public class User {
    @NotBlank(message = "Nickname is required.")
    private String nickname;
    private Integer age;
    // getters and setters and constructors
}

四、调用业务方法

如下代码,调用 UserService.register()进行注册业务处理:

@RestController
public class UserController {
    private final UserService userService;
    public UserController(UserService userService) {
        this.userService = userService;
    }

    @PostMapping("/user/register")
    public void getGradeById(@Validated @RequestBody User user) {
        // 调用注册的业务方法
        userService.register(user);
    }
}

public class User {
    @NotBlank(message = "Nickname is required.")
    private String nickname;
    private Integer age;
    // getters and setters and constructors
}

关于调用业务方法,这里的业务方法是写一个大而全的方法?还是需要按业务归类?

遵守一个原则:有强关联性的逻辑放在一个service方法内,没有强关联性的单令拎出来。

这里以用户注册之后需要新人发券为例进行说明:

大而全的方法:

    @PostMapping("/user/register")
    public void getGradeById(@Validated @RequestBody User user) {
        // 调用注册的业务方法
        userService.doRegister(user);
    }
    
    public String doRegister(Uswr user){
        String userId = userService.register(user);
        coupon.sendCoupon(userId);
        // 其他业务逻辑
        return userId;
    }

业务归类:

    @PostMapping("/user/register")
    public void getGradeById(@Validated @RequestBody User user) {
        // 调用注册的业务方法
        userService.register(user);
        coupon.sendCoupon(userId);
    }

五、组织返回数据

如下代码,调用 UserService.register()进行注册业务处理:

@RestController
public class UserController {
    private final UserService userService;

    public UserController(UserService userService) {
        this.userService = userService;

    }

    @PostMapping("/user/register")
    public UserResponse getGradeById(@Validated @RequestBody User user) {
        // 调用注册的业务方法
        String userId = userService.regist(user);

        return new UserResponse(userId, user.getNickname);
    }
}

public class UserResponse {
    private String userId;
    private String nickname;
    // getters and setters and constructors
}

六、统一异常处理

比如上述过程在 userService.regist(user);出现异常时,可以做一个try-catch,然后在 Controller层封装有业务意思的异常信息:

@RestController
public class UserController {
    private final UserService userService;

    @PostMapping("/user/register")
    public UserResponse getGradeById(@Validated @RequestBody User user) {
        // 调用注册的业务方法
        try {
            String userId = userService.regist(user);
        } catch (Exception e) {
            throw new CustomException();
        }
        return new UserResponse(userId, user.getNickname);
    }
}

建议和总结

看过很多代码,业务逻辑全部写在 Controller层,并不能说这样的做法是错的,但是看起来很别扭,不优雅!因此,建议在编写代码时,最好能遵守一个比较好的规范,比如常见的SOLID规范。

SOLID 实际上是五个设计原则首字母的缩写,它们分别是:

  • 单一职责原则(Single responsibility principle, SRP)
  • 开放封闭原则(Open–closed principle, OCP)
  • Liskov 替换原则(Liskov substitution principle, LSP)
  • 接口隔离原则(Interface segregation principle, ISP)
  • 依赖倒置原则(Dependency inversion principle, DIP)

另外,建议我们技术人员平时多去阅读一些优秀开源框架,学习他们的设计思想,代码规范,相信我:养成一个良好的编码规范,绝对受益颇多!

责任编辑:赵宁宁 来源: 猿java
相关推荐

2024-08-16 21:38:43

Python代码编程

2019-04-19 08:04:57

程序员Dockerfile容器

2024-06-17 08:04:23

2012-07-11 10:51:37

编程

2024-03-08 08:00:00

Python开发装饰器

2022-08-01 23:45:23

代码识别项目

2022-05-24 06:07:48

JShack用户代码

2024-05-11 18:48:40

技巧代码技能

2024-10-14 08:46:50

Controller开发代码

2022-06-07 09:30:35

JavaScript变量名参数

2021-03-17 08:00:59

JS语言Javascript

2021-01-04 07:57:07

C++工具代码

2023-07-30 22:25:00

JavaScrip服务端Web

2022-03-11 12:14:43

CSS代码前端

2019-09-20 15:47:24

代码JavaScript副作用

2023-10-10 08:00:00

2024-03-20 08:00:00

软件开发Java编程语言

2014-04-25 09:02:17

LuaLua优化Lua代码

2013-04-15 09:02:43

JavaScriptJS

2022-12-15 10:52:26

代码开发
点赞
收藏

51CTO技术栈公众号