Github地址:
访问GitHub下载最新源码:https://github.com/JYG0723/SpringBootAction/tree/master/chapter9
简介:
其实就是自己总结的一些套路。然后揉到了一起,其中包含 Spring 的 Aop
思想,GlobalExecptionHandle
,ResultFul Api
以及单元测试MockMvc
的使用。
Aop
Controller
@RestController
@RequestMapping(value = "/user")
public class UserController {
// 创建线程安全的Map
static Map users = Collections.synchronizedMap(new HashMap());
@GetMapping(value = "/")
public Result> getUserList() {
List userList = new ArrayList(users.values());
return ResultUtil.success(userList);
}
@GetMapping(value = "/{id}")
public Result getUser(@PathVariable Integer id) {
return ResultUtil.success(users.get(id));
}
@PostMapping(value = "/")
public Result saveUser(@Valid User user, BindingResult bindingResult) throws Exception{
if (bindingResult.hasErrors()) {
return ResultUtil.error(ResultEnum.UNKONW_ERROR.getCode(),
bindingResult.getFieldError().getDefaultMessage());
}
return ResultUtil.success(users.put(user.getId(), user));
}
@PutMapping(value = "/{id}")
public Result updateUser(@PathVariable Integer id, @ModelAttribute User user) {
User u = new User();
u.setUsername(user.getUsername());
u.setPassword(user.getPassword());
return ResultUtil.success(users.put(id, u));
}
@DeleteMapping(value = "/{id}")
public Result removeUser(@PathVariable Integer id) {
return ResultUtil.success(users.remove(id));
}
}
这里我使用了一个 Map 集合来模拟数据库操作。
Utils
public class ResultUtil {
public static Result success(Object object) {
Result result = new Result();
result.setCode(ResultEnum.SUCCESS.getCode());
result.setMsg(ResultEnum.SUCCESS.getMsg());
result.setData(object);
return result;
}
public static Result success() {
return success(null);
}
public static Result error(Integer code, String msg) {
Result result = new Result();
result.setCode(code);
result.setMsg(msg);
return result;
}
}
Dto
@Data
@NoArgsConstructor
public class Result implements Serializable {
/** 状态码. */
private Integer code;
/** 提示信息 */
private String msg;
/** 具体内容 */
private T data;
}
在写 Result
架构式的 http 请求时,想了一下想结合一下 Spring 的 aop
思想,在 Controller 层的访问前后用日志记录一下,方法的调用以及请求的响应情况。因为这是在生产环境中必然要做到的,时刻监听记录后台的请求情况。所以:
Aspect
@Aspect
@Component
public class HttpAspect {
private final static Logger logger = LoggerFactory.getLogger(HttpAspect.class);
@Pointcut("execution(public * nuc.jyg.chapter9.controller.UserController.*(..))")
public void log() {
}
@Before(value = "log()")
public void doBefore(JoinPoint joinPoint) {
// request
ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
HttpServletRequest request = attributes.getRequest();
// url
logger.info("url={}", request.getRequestURL());
// Request method
logger.info("method={}", request.getMethod());
// ip
logger.info("ip={}", request.getRemoteAddr());
// Class Method
logger.info("class_method={}", joinPoint.getSignature().getDeclaringTypeName() + "." + joinPoint
.getSignature().getName());
// Args
logger.info("args={}", joinPoint.getArgs());
}
@After(value = "log()")
public void doAfter() {
}
/**
* returning. 返回结果绑定到的参数
*/
@AfterReturning(returning = "object", pointcut = "log()")
public void doAfterReturning(Object object) {
logger.info("response={}", object.toString());
}
}
注意:
SpringBoot 工程中并不需要同 Spring 中一样使用@EnableAspectJAutoProx
注解来启用。可以查看关于AOP的默认配置属性:
# AOP
spring.aop.auto=true # Add @EnableAspectJAutoProxy.
spring.aop.proxy-target-class=false # Whether subclass-based (CGLIB) proxies are to be created (true) as
opposed to standard Java interface-based proxies (false).
当需要使用 CGLIB 来实现 AOP 的时候,需要配置spring.aop.proxy-target-class=true
,不然默认使用的是标准 Java 的实现。
这样我们简单的 aop 思想揉合就完成了。很简单
GlobalException
Handle
@ControllerAdvice
public class ExceptionHandle {
private static final Logger logger = LoggerFactory.getLogger(ExceptionHandle.class);
@ExceptionHandler(value = Exception.class)
@ResponseBody
public Result handle(Exception e) {
if (e instanceof UserException) {
UserException userException = (UserException) e;
return ResultUtil.error(userException.getCode(), userException.getMessage());
} else {
logger.error(" [系统异常] {} ", e);
return ResultUtil.error(ResultEnum.UNKONW_ERROR.getCode(), e.getMessage());
}
}
}
首先通过@ControllerAdvice
设置全局异常处理类,而因为默认的异常只接受msg
,并且因为统一接口的原因,我们返回给前台的结果也应该是格式统一的,成功与失败各对应一种,所以这里我们定义了自定义的异常类UserException
。
UserException
@Data
public class UserException extends RuntimeException {
private Integer code;
public UserException(ResultEnum resultEnum) {
super(resultEnum.getMsg());
this.code = resultEnum.getCode();
}
}
注意:
Spring 只会对你抛出的是RuntimeException
才会进行回滚。如果抛出的是Exception
是不会进行回滚的。所以这里我们选择继承的是RuntimeException
而不是Exception
。
我们为其添加了code
属性。然而紧接着新的问题也就出现了,我们后台 Service 层处理业务逻辑的时候,面对 Controller 传过来的部分数据需要进行验证已经逻辑判断,这时就会出现因参数或逻辑出现各种各样的异常:
Service
@Service
public class UserService {
public void saveUser(Integer id) throws Exception {
User user = new User();
String username = user.getUsername();
String password = user.getPassword();
if ("".equals(username) || username == null) {
throw new UserException(ResultEnum.USERNAME_ILLEGALITY);
} else if ("".equals(password) || password == null) {
throw new UserException(ResultEnum.PASSWORD_ILLEGALITY);
}
}
}
- 用户名为空:
ResultEnum.USERNAME_ILLEGALITY
- 密码为空:
ResultEnum.PASSWORD_ILLEGALITY
而面对不同的异常我们也一定应该有其对应的异常状态码来与之对应,并且日后出现异常之后,我们通过log
也能快速的定位,排查异常。
所以对应Result 的两个code
,message
属性,我们定义成了枚举统一管理。这样日后维护管理起来才会更方便
public enum ResultEnum {
UNKONW_ERROR(-1, "未知错误"),
SUCCESS(0, "成功"),
USERNAME_ILLEGALITY(100, "用户名非法"),
PASSWORD_ILLEGALITY(101, "密码非法");
private int code;
private String msg;
ResultEnum(int code, String mas) {
}
public int getCode() {
return code;
}
public String getMsg() {
return msg;
}
}
枚举的使用都是直接用它的构造方法来创建。不必重新 Set
单元测试
Controller
@AutoConfigureMockMvc
public class UserControllerTest extends Chapter9ApplicationTests{
private MockMvc mockMvc;
@Test
public void getUserList() throws Exception {
mockMvc.perform(MockMvcRequestBuilders.get("/user/"))
.andExpect(MockMvcResultMatchers.status().isOk())
.andExpect(MockMvcResultMatchers.content().string("user")); // 示例错误信息
}
}
Service
public class UserServiceTest extends Chapter9ApplicationTests {
@Autowired
UserService userService;
@Test
public void saveUser() throws Exception {
User user = new User();
user.setUsername("张三");
user.setPassword("zhangsan");
Integer userId = userService.saveUser(user);
// service 层利用断言判断检测方法
Assert.assertEquals(new Integer(2), userId);
}
}