登录验证通过拦截器实现,拦截器就是在用户访问服务器时,预先拦截检查一下用户的访问请求。
拦截器起到了预先帮助Controller层检查请求是否合规的作用,让Controller层的代码更加专注于核心的业务逻辑。
登录验证就是拦截器的一种应用。在请求没Controller层处理前,先判断发出请求的用户是否是登录状态。
创建拦截器类
@Component
public class LoginInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
HttpSession session=request.getSession(false);
if(session!=null && session.getAttribute("userinfo")!=null){
return true;
}
return false;
}
}
配置拦截器拦截的url
@Configuration
public class LoginConfiguration implements WebMvcConfigurer {
@Autowired
private LoginInterceptor interceptor;
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(interceptor).
addPathPatterns("/**").
excludePathPatterns("/image/**").excludePathPatterns("/sayhi");
}
}
拦截器的工作原理:
为了理解拦截器源码实现,首先要知道一件事,一个http请求访问到项目,项目进行处理的时候,是有一个调度器(DispatcherServlet)来调度程序执行的先后顺序的.这个调度器是SpringMVC框架实现的。
当spring boot项目启动后,控制台没有DispatcherServlet启动的日志
但是当首个http请求发出后,DispatcherServlet就会被初始化
调度器在收到http请求后的首先就会执行拦截器列表中的拦截器对象的方法,只有列表中的所有方法都返回true后,拦截器才会调度深层代码继续执行。有一个返回false都会直接返回响应。
当前端传入的某些特殊数据时,可能会导致后端程序在执行时抛出异常,如果不对这个异常做出处理,后端会直接返回500的错误页面,破坏前后端交互的正常进行,异常处理的作用就是就算后端抛出了异常,响应也要按照正确格式返回,只是在返回的时候说明后端发生了错误。
这个类要用@ControllerAdvice和@ResponseBody修饰(可用@RestControllerAdvice代替这两个)
类内的方法要指定是针对哪个异常进行处理,使用@ExceptionHandeler注解指定异常,注解参数是异常类的class对象
处理方法的返回值就是响应的body返回值(要将按正常格式返回)
@ControllerAdvice
@ResponseBody
public class MyExceptionController {
@ExceptionHandler(Exception.class)
public HashMap<String,Object> handException(Exception e){
HashMap<String,Object> map=new HashMap<>();
map.put("data",e.getMessage());
return map;
}
}
上面的代码指定的异常类的class是所有异常的父类Exception,这样指定是起到一个保底的作用,因为异常是不可预知的,你在写代码的时候,并不知道代码会抛出什么异常,针对特定异常的处理是极少的,直接使用父类异常接受可以兜底保证异常抛出时,会被正确的处理。
抛出异常时,处理方法的匹配原则是先匹配对应异常的处理方法,如果没有才会匹配父类异常的处理方法。
Support方法的返回值就是设置是否要调用beforeBodyWrite方法的,true表示要调用,false表示不调用
beforeBodyWrite就是修改body的数据格式的方法,可以根据自带的object参数body获取判断参数格式是否符合要求。(用instanceof方法判断Object是否是约定要返回的对象类型)
@ControllerAdvice
public class ResponseAdvice implements ResponseBodyAdvice {
@Override
public boolean supports(MethodParameter returnType, Class converterType) {
return true;
}
@Override
public Object beforeBodyWrite(Object body, MethodParameter returnType, MediaType selectedContentType, Class selectedConverterType, ServerHttpRequest request, ServerHttpResponse response) {
if(body instanceof HashMap){
//格式正确
return body;
}
HashMap<String,Object> map=new HashMap();
map.put("code",1);
map.put("data",body);
return map;
}
}
注意:如果body是String类型的数据,将这个hashMap最终转换成json格式的时候,会报错。
原因就是在hashmap转换成json字符串的时候会判断参数Object data的源类型,如果原类型是String就会使用的StringHttpMessageConverter进行转换,这个转换只能进行单纯的字符串转换,最终生成的响应的body就是字符串,bodyType就是Text/html,但是hashMap无法被转换成字符串,正常hashMap也应该被被转换成json格式,所以会报错。
最根本的原因是,统一返回数据格式使用的是AOP思想,在所有方法返回之后拦截了响应方法的返回值,但是响应方法的返回值的类型就决定了返回值转换成响应中body的转换器以及响应中的bodyType。
如果是String类型的返回值,默认的转换器就是StringHttpMessageConvert,由此bodyType为Text/html
如果是其他类型,则会是json格式
解决办法:
1.在统一格式转换方法里,判断当前的Object body的源类型是不是String,如果是就直接在方法中将最终统一返回的类型对象使用ObjectMapper转换成json格式的字符串返回,ObjectMapper对象是Spring框架也内置了,可以直接注入使用。
2.直接禁用StringHttpMessageConvert(查查chatgpt咋禁用)。
@ControllerAdvice
public class ResponseAdvice implements ResponseBodyAdvice {
@Override
public boolean supports(MethodParameter returnType, Class converterType) {
return true;
}
@Override
public Object beforeBodyWrite(Object body, MethodParameter returnType, MediaType selectedContentType, Class selectedConverterType, ServerHttpRequest request, ServerHttpResponse response) {
if(body instanceof HashMap){
//格式正确
return body;
}
HashMap<String,Object> map=new HashMap();
map.put("code",1);
map.put("data",body);
//判断是否是String
if(body instanceof String){
ObjectMapper objectMapper=new ObjectMapper();
String result= null;
try {
result = objectMapper.writeValueAsString(map);
} catch (JsonProcessingException e) {
e.printStackTrace();
}
return result;
}
return map;
}
}