一. REST风格简介
RESTFUL是一种网络应用程序的设计风格和开发方式,基于HTTP,可以使用XML格式定义或JSON格式定义。RESTFUL适用于移动互联网厂商作为业务使能接口的场景,实现第三方OTT调用移动网络资源的功能,动作类型为新增、变更、删除所调用资源
该风格的特点如下:
(1)使用url描述资源。
(2)使用http方法描述行为。
(3)使用json交互数据。
(4)Restful只是一种风格,并不是强制的标准。
二. REST风格使用
1. 简单的增删改查
(1)查询操作:使用正则表达式约束url中的片段;使用@JsonView控制返回的实体字段
1. 实体类
public class User {
public interface UserSimpleView{}
public interface UserDetailView extends UserSimpleView{}
private String username;
private String password;
public User() {
}
public User(String username, String password) {
this.username = username;
this.password = password;
}
@JsonView(UserSimpleView.class)
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
@JsonView(UserDetailView.class)
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
}
2. controller类:
@JsonView(User.UserSimpleView.class) // @JsonView(User.UserDetailView.class)
@GetMapping("/user/{id:\\d+}") //id只能为数字
public User getUserById(@PathVariable String id){
return new User(id, "123456");
}
3. 测试类
@Test
public void getUserByIdTest() throws Exception {
String res = mockMvc.perform(get("/user/1").accept(MediaType.APPLICATION_JSON))
.andExpect(status().isOk())
.andExpect(content().contentType("application/json;charset=UTF-8"))
.andExpect(jsonPath("$.username").value("1"))
.andReturn().getResponse().getContentAsString();
System.out.println(res);
}
(2)新增操作:@RequestBody的使用;日期类型参数的处理;验证参数的合法性并处理校验结果
1. controller类
@JsonView(User.UserSimpleView.class)
@PostMapping("/user")
public User addUser(@RequestBody @Valid User user, BindingResult bindingResult) throws Exception {
System.out.println("新增用户:" + objectMapper.writeValueAsString(user));
return user;
}
2. 实体类
public class User {
public interface UserSimpleView{}
public interface UserDetailView extends UserSimpleView{}
private String username;
@Password(4)
private String password;
@JsonFormat(pattern = "yyyy-MM-dd")
@DateTimeFormat(pattern = "yyyy-MM-dd")
private Date birthDay;
}
3. 自定义验证参数相关的类
@java.lang.annotation.Target({java.lang.annotation.ElementType.FIELD})
@java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.RUNTIME)
@javax.validation.Constraint(validatedBy = {MyConstraintValidtor.class})
public @interface Password {
java.lang.String message() default "密码不符合要求";
java.lang.Class>[] groups() default {};
java.lang.Class extends javax.validation.Payload>[] payload() default {};
long value();
}
public class MyConstraintValidtor implements ConstraintValidator {
@Autowired
private ObjectMapper objectMapper;
@Override
public boolean isValid(String s, ConstraintValidatorContext constraintValidatorContext) {
long passwordlen = (long) ((ConstraintValidatorContextImpl) constraintValidatorContext)
.getConstraintDescriptor().getAttributes().get("value");
return passwordlen > s.length();
}
}
@Aspect
@Component
public class AspectValidtor {
@Before("execution(* com.imooc.web.controller.*.*(..))")
public void before(JoinPoint proceedingJoinPoint){
Object[] args = proceedingJoinPoint.getArgs();
for(Object obj: args){
if(obj instanceof BindingResult){
BindingResult br = (BindingResult) obj;
if(br.hasErrors()){
throw new RuntimeException();
}
}
}
}
}
3. 测试类
@Test
public void addUserTest() throws Exception {
String content = "{\"username\":\"security\",\"password\":\"123456\",\"birthDay\":\"1997-01-22\"}";
String res =mockMvc.perform(post("/user").content(content)
.contentType("application/json"))
.andExpect(content().contentType("application/json;charset=UTF-8"))
.andExpect(status().isOk())
.andReturn().getResponse().getContentAsString();
System.out.println(res);
}
2. 服务异常处理
我们编写的rest接口有可能是通过浏览器访问的,也有可能是通过app访问的。但是出现异常的处理结果是不同的,浏览器是跳转到错误页面,而app端是返回一个错误的json数据,这是如何做的呢?在BasicErrorController
类中,是使用RequestMapping
的produces
属性进行处理的。
如果想要自定义错误页面,可以在资源目录下创建resources/error/404.html
,这样如果出现 404 异常,就会跳转到自定义页面。
spring mvc异常处理有三种方式:
(1)实现HandlerExceptionResolver
接口。
(2)@ExceptionHandler
注解
(3)@ControllerAdvice
+ @ExceptionHandler
注解`
使用方式如下:
@GetMapping("/exec")
public void testExec(){
throw new RuntimeException("测试异常");
}
1. 缺点是必须和Controller在一个类中,但是优先级最高
@ExceptionHandler(RuntimeException.class)
public Map handler(RuntimeException r){
Map map = new HashMap<>();
map.put("test","@ExceptionHander");
map.put("msg", r.getMessage());
return map;
}
2. 优先级最低
@Component
public class MyExceptionHandler implements HandlerExceptionResolver {
@Override
public ModelAndView resolveException(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o, Exception e) {
//可以定制显示的异常信息
ModelAndView mav = new ModelAndView();
mav.setViewName("error/exec.html");
return mav;
}
}
3. 优先级第二
@ControllerAdvice
public class ExecptionHandlerAnnotion {
@ExceptionHandler(RuntimeException.class)
@ResponseBody
@ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
public Map exception(RuntimeException re){
Map map = new HashMap<>();
map.put("message",re.getMessage());
return map;
}
}
3. 面向切面编程
对于一些横切关注点,比如日志记录、安全控制、事务管理、性能统计、日常处理等,我们可以使用面向切面编程,减少项目的冗余代码,易于维护和管理。
RestFul API的拦截包括三种方式。
(1)过滤器
1. 使用@WebFilter注解
@WebFilter(urlPatterns = {"/hello"})
public class TimeFilter implements Filter {
@Override
public void init(javax.servlet.FilterConfig filterConfig) throws ServletException { }
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
System.out.println("测试@WebFilter");
filterChain.doFilter(servletRequest, servletResponse);
}
@Override
public void destroy() { }
}
启动类上要加入 @ServletComponentScan注解
2. 手动注册过滤器
@Component
public class LoginFilter implements Filter {
@Override
public void init(FilterConfig filterConfig) throws ServletException { }
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
System.out.println("手动注册过滤器测试");
filterChain.doFilter(servletRequest, servletResponse);
}
@Override
public void destroy() { }
}
@Configuration
public class FilterConfig {
@Bean
public FilterRegistrationBean filterRegistrationBean(LoginFilter loginFilter){
FilterRegistrationBean frb = new FilterRegistrationBean();
frb.setUrlPatterns(Arrays.asList("/user/*"));
frb.setFilter(loginFilter);
return frb;
}
}
注意:@WebFilter和@Component注解不能同时使用,否则会出现异常情况,@WebFilter指定的拦截 url 会失效。
(2)拦截器
public class MyInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
System.out.println("测试拦截器");
return true;
}
......
}
@Configuration
public class FilterConfig implements WebMvcConfigurer {
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new MyInterceptor())
.addPathPatterns("/hello");
}
}
(3)切片
@Aspect
@Component
public class AspectDemo {
@Before("execution(* com.imooc.web.controller.*.*(..))")
public void before(){
System.out.println("aspect测试");
}
}
4. spring mvc的异步处理
spring mvc的异步处理有三种方式
(1)Callable
@GetMapping("/asyncCallable")
public Callable buyShopAsyncCallable(){
logger.info("主线程开始!!");
Callable result = new Callable() {
@Override
public String call() throws Exception {
logger.info("下单开始!!");
Thread.sleep(10000);
logger.info("下单结束!!");
return "下单成功";
}
};
logger.info("主线程结束!!");
return result;
}
由上述代码可知,该方式适合简单场景,因为子线程是由主线程开启的,但是比较复杂的应用场景,就无法使用这种方式。比如下列这种方式:
(2)DeferredResult
@GetMapping("/asyncDeferredResult")
public DeferredResult buyShopAsync(){
logger.info("主线程开始!!");
DeferredResult result = new DeferredResult<>();
DeferredResultHolder.map.put("orderNo", result);
logger.info("主线程结束!!");
return result;
}
//监听到消息,开始处理
DeferredResult orderNo = DeferredResultHolder.map.get("orderNo");
logger.info("开始下单!!");
Thread.sleep(10000);
logger.info("下单结束!!");
orderNo.setResult("orderNo:下单成功!");
(3)ResponseBodyEmitter
@GetMapping("/ResponseBodyEmitter")
public ResponseBodyEmitter buyShopAsyncResponseBodyEmitter(){
logger.info("主线程开始!!");
ResponseBodyEmitter rbe = new ResponseBodyEmitter();
ResponseBodyEmitterHolder.map.put("orderNo", rbe);
logger.info("主线程结束!!");
return rbe;
}
//监听到消息,开始处理
ResponseBodyEmitter orderNo = ResponseBodyEmitterHolder.map.get("orderNo");
logger.info("开始下单!!");
Thread.sleep(10000);
logger.info("下单结束!!");
orderNo.send("orderNo:success!");
orderNo.complete();
ResponseBodyEmitterHolder.map.remove("orderNo");