这才是Controller 层代码最优雅的写法,其他写法都非常Low!

526 阅读6分钟

这才是Controller 层代码最优雅的写法,其他写法都非常Low!

在Java开发中,Controller层是一个关键的存在,它负责处理来自客户端的HTTP请求,并将这些请求映射到相应的服务层或者业务逻辑层。如何写出优雅的Controller层代码,不仅关系到代码的可读性和可维护性,还影响整个系统的健壮性和扩展性。

1. 合理设计接口

首先,合理设计接口是关键。接口的设计应简洁明了,遵循RESTful规范,让人一看就懂。使用HTTP方法来表示操作,路径来表示资源。例如:

@RestController
@RequestMapping("/api/users")
public class UserController {

    @GetMapping("/{id}")
    public ResponseEntity<UserDTO> getUserById(@PathVariable Long id) {
        // 调用服务层获取用户信息
        UserDTO userDTO = userService.getUserById(id);
        return ResponseEntity.ok(userDTO);
    }

    @PostMapping
    public ResponseEntity<UserDTO> createUser(@RequestBody @Valid UserDTO userDTO) {
        // 调用服务层创建新用户
        UserDTO createdUser = userService.createUser(userDTO);
        return ResponseEntity.status(HttpStatus.CREATED).body(createdUser);
    }

    @PutMapping("/{id}")
    public ResponseEntity<UserDTO> updateUser(@PathVariable Long id, @RequestBody @Valid UserDTO userDTO) {
        // 调用服务层更新用户信息
        UserDTO updatedUser = userService.updateUser(id, userDTO);
        return ResponseEntity.ok(updatedUser);
    }

    @DeleteMapping("/{id}")
    public ResponseEntity<Void> deleteUser(@PathVariable Long id) {
        // 调用服务层删除用户
        userService.deleteUser(id);
        return ResponseEntity.noContent().build();
    }
}

上面的代码示例展示了一个典型的RESTful接口设计,涵盖了获取、创建、更新和删除用户的操作。

2. 参数校验

参数校验是保证接口健壮性的重要环节。使用Java的Bean Validation规范,可以在方法参数上直接进行校验。例如:

@PostMapping
public ResponseEntity<UserDTO> createUser(@RequestBody @Valid UserDTO userDTO) {
    UserDTO createdUser = userService.createUser(userDTO);
    return ResponseEntity.status(HttpStatus.CREATED).body(createdUser);
}

在UserDTO类中,我们可以使用注解来进行校验:

public class UserDTO {

    @NotBlank(message = "用户名不能为空")
    private String username;

    @Email(message = "邮箱格式不正确")
    private String email;

    // 其他字段和方法
}

3. 统一异常处理

为了避免每个接口方法中都写大量的try-catch代码,可以使用全局异常处理器,统一处理异常。

@ControllerAdvice
public class GlobalExceptionHandler {

    @ExceptionHandler(MethodArgumentNotValidException.class)
    public ResponseEntity<Map<String, String>> handleValidationExceptions(MethodArgumentNotValidException ex) {
        Map<String, String> errors = new HashMap<>();
        ex.getBindingResult().getAllErrors().forEach((error) -> {
            String fieldName = ((FieldError) error).getField();
            String errorMessage = error.getDefaultMessage();
            errors.put(fieldName, errorMessage);
        });
        return ResponseEntity.badRequest().body(errors);
    }

    @ExceptionHandler(ResourceNotFoundException.class)
    public ResponseEntity<String> handleResourceNotFoundException(ResourceNotFoundException ex) {
        return ResponseEntity.status(HttpStatus.NOT_FOUND).body(ex.getMessage());
    }

    // 其他异常处理方法
}

通过统一异常处理,可以将异常的处理逻辑集中管理,代码更加简洁清晰。

4. 使用DTO进行数据传输

在Controller层与服务层之间传递数据时,使用DTO(Data Transfer Object)可以避免直接暴露数据库实体类,提高系统的安全性和灵活性。

public class UserDTO {

    private Long id;
    private String username;
    private String email;

    // 其他字段和方法
}

在服务层和Controller层之间,通过DTO来传递数据:

@Service
public class UserService {

    // 注入UserRepository

    public UserDTO getUserById(Long id) {
        User user = userRepository.findById(id)
                .orElseThrow(() -> new ResourceNotFoundException("User not found with id " + id));
        return convertToDTO(user);
    }

    public UserDTO createUser(UserDTO userDTO) {
        User user = convertToEntity(userDTO);
        user = userRepository.save(user);
        return convertToDTO(user);
    }

    public UserDTO updateUser(Long id, UserDTO userDTO) {
        User user = userRepository.findById(id)
                .orElseThrow(() -> new ResourceNotFoundException("User not found with id " + id));
        user.setUsername(userDTO.getUsername());
        user.setEmail(userDTO.getEmail());
        user = userRepository.save(user);
        return convertToDTO(user);
    }

    public void deleteUser(Long id) {
        User user = userRepository.findById(id)
                .orElseThrow(() -> new ResourceNotFoundException("User not found with id " + id));
        userRepository.delete(user);
    }

    private UserDTO convertToDTO(User user) {
        UserDTO userDTO = new UserDTO();
        userDTO.setId(user.getId());
        userDTO.setUsername(user.getUsername());
        userDTO.setEmail(user.getEmail());
        return userDTO;
    }

    private User convertToEntity(UserDTO userDTO) {
        User user = new User();
        user.setUsername(userDTO.getUsername());
        user.setEmail(userDTO.getEmail());
        return user;
    }
}

5. 充分利用Spring的特性

Spring框架提供了很多方便的功能,可以简化代码。例如,可以使用@CrossOrigin注解来处理跨域问题,使用@PreAuthorize注解来进行权限控制。

@RestController
@RequestMapping("/api/users")
@CrossOrigin(origins = "http://localhost:3000")
public class UserController {

    @GetMapping("/{id}")
    @PreAuthorize("hasRole('ADMIN') or hasRole('USER')")
    public ResponseEntity<UserDTO> getUserById(@PathVariable Long id) {
        UserDTO userDTO = userService.getUserById(id);
        return ResponseEntity.ok(userDTO);
    }

    // 其他方法
}

6. 使用日志记录

日志记录是非常重要的,尤其是在生产环境中,合理的日志记录可以帮助我们快速定位问题。使用SLF4J和Logback等日志框架,可以实现高效的日志管理。

@RestController
@RequestMapping("/api/users")
public class UserController {

    private static final Logger logger = LoggerFactory.getLogger(UserController.class);

    @GetMapping("/{id}")
    public ResponseEntity<UserDTO> getUserById(@PathVariable Long id) {
        logger.info("Fetching user with id {}", id);
        UserDTO userDTO = userService.getUserById(id);
        logger.info("Fetched user: {}", userDTO);
        return ResponseEntity.ok(userDTO);
    }

    // 其他方法
}

7. 使用ResponseEntity灵活处理响应

ResponseEntity可以灵活处理HTTP响应状态码和响应体,提高代码的清晰度和可维护性。

@RestController
@RequestMapping("/api/orders")
public class OrderController {

    @GetMapping("/{id}")
    public ResponseEntity<OrderDTO> getOrderById(@PathVariable Long id) {
        OrderDTO orderDTO = orderService.getOrderById(id);
        if (orderDTO == null) {
            return ResponseEntity.status(HttpStatus.NOT_FOUND).body(null);
        }
        return ResponseEntity.ok(orderDTO);
    }

    @PostMapping
    public ResponseEntity<OrderDTO> createOrder(@RequestBody @Valid OrderDTO orderDTO) {
        OrderDTO createdOrder = orderService.createOrder(orderDTO);
        return ResponseEntity.status(HttpStatus.CREATED).body(createdOrder);
    }

    @PutMapping("/{id}")
    public ResponseEntity<OrderDTO> updateOrder(@PathVariable Long id, @RequestBody @Valid OrderDTO orderDTO) {
        OrderDTO updatedOrder = orderService.updateOrder(id, orderDTO);
        if (updatedOrder == null) {
            return ResponseEntity.status(HttpStatus.NOT_FOUND).body(null);
        }
        return ResponseEntity.ok(updatedOrder);
    }

    @DeleteMapping("/{id}")
    public ResponseEntity<Void> deleteOrder(@PathVariable Long id) {
        orderService.deleteOrder(id);
        return ResponseEntity.noContent().build();
    }
}

8. 采用统一的响应格式

为了让客户端更方便地处理响应数据,采用统一的响应格式是一个不错的选择。例如,可以使用一个标准的响应结构:

public class ApiResponse<T> {
    private int code;
    private String message;
    private T data;

    public ApiResponse(int code, String message, T data) {
        this.code = code;
        this.message = message;
        this.data = data;
    }

    // getters and setters
}

在Controller中使用这种统一的响应格式:

@RestController
@RequestMapping("/api/customers")
public class CustomerController {

    @GetMapping("/{id}")
    public ResponseEntity<ApiResponse<CustomerDTO>> getCustomerById(@PathVariable Long id) {
        CustomerDTO customerDTO = customerService.getCustomerById(id);
        return ResponseEntity.ok(new ApiResponse<>(200, "Success", customerDTO));
    }

    @PostMapping
    public ResponseEntity<ApiResponse<CustomerDTO>> createCustomer(@RequestBody @Valid CustomerDTO customerDTO) {
        CustomerDTO createdCustomer = customerService.createCustomer(customerDTO);
        return ResponseEntity.status(HttpStatus.CREATED).body(new ApiResponse<>(201, "Customer created", createdCustomer));
    }
}

9. 使用分页和排序

在处理列表数据时,分页和排序是非常常见的需求。Spring Data JPA提供了很好的支持,可以轻松实现这些功能。

@RestController
@RequestMapping("/api/products")
public class ProductController {

    @GetMapping
    public ResponseEntity<Page<ProductDTO>> getProducts(Pageable pageable) {
        Page<ProductDTO> products = productService.getAllProducts(pageable);
        return ResponseEntity.ok(products);
    }
}

在服务层中实现分页和排序逻辑:

@Service
public class ProductService {

    @Autowired
    private ProductRepository productRepository;

    public Page<ProductDTO> getAllProducts(Pageable pageable) {
        Page<Product> products = productRepository.findAll(pageable);
        return products.map(this::convertToDTO);
    }

    private ProductDTO convertToDTO(Product product) {
        // 转换逻辑
    }
}

10. 使用AOP实现跨领域关注点

AOP(面向切面编程)可以帮助我们将日志记录、权限控制等跨领域关注点从业务代码中分离出来,使代码更加清晰。

@Aspect
@Component
public class LoggingAspect {

    private static final Logger logger = LoggerFactory.getLogger(LoggingAspect.class);

    @Before("execution(* com.example.service.*.*(..))")
    public void logBeforeMethod(JoinPoint joinPoint) {
        logger.info("Entering method: " + joinPoint.getSignature().getName());
    }

    @AfterReturning(pointcut = "execution(* com.example.service.*.*(..))", returning = "result")
    public void logAfterMethod(JoinPoint joinPoint, Object result) {
        logger.info("Exiting method: " + joinPoint.getSignature().getName() + " with result: " + result);
    }

    @AfterThrowing(pointcut = "execution(* com.example.service.*.*(..))", throwing = "error")
    public void logAfterThrowing(JoinPoint joinPoint, Throwable error) {
        logger.error("Exception in method: " + joinPoint.getSignature().getName() + " with error: " + error);
    }
}

在Controller层中,可以利用AOP实现日志记录、权限校验等功能,减少重复代码,使业务逻辑更加专注于业务本身。

总结一下

写出优雅的Controller层代码需要考虑多方面的因素,包括接口设计、参数校验、异常处理、数据传输、Spring特性和日志记录等。通过遵循这些最佳实践,可以显著提高代码的可读性、可维护性和健壮性,让系统在面对复杂业务需求时依然保持良好的扩展性和可靠性。

已收录于,我的技术网站:ddkk.com 里面有,500套技术系列教程、1万+道,面试八股文、BAT面试真题、简历模版,工作经验分享、架构师成长之路,等等什么都有,欢迎收藏和转发。