文章目录
- Spring拦截器
- 为何需要Spring拦截器?
- 自定义拦截器
- 加入系统配置并配置拦截规则
- 验证登陆拦截器
- 添加统一访问前缀
- 验证统一前缀是否添加成功
- 统一异常处理
- 统一数据返回格式
Spring拦截器
为何需要Spring拦截器?
在之前Servlet开发中,对登陆校验有两种方法:
- 在每个方法中都获取到session中的用户信息,然后判断用户是否存在,如果用户存在则登陆,如果用户不存在则未登陆
- 提供统一的方法验证用户是否登陆,在需要验证用户登陆的地方调用该方法进行判断
从上述可以看出这样做比较复杂,因为如果有许多地方需要进行登陆验证的话,就需要写重复的校验代码或者重复调用判断登陆的方法
对此我们提供Spring拦截器来实现用户的统一登陆验证,拦截器的实现分为以下两个步骤:
- 创建自定义拦截器类,实现
HandlerInterceptor
接口并重写preHandle
方法 - 创建配置类,将自定义的拦截器添加到该配置类中,并添加拦截规则
- 给配置类添加
@Configuration
注解 - 实现
WebMvcConfigurer
接口 - 重写
addInterceptors
方法
- 给配置类添加
注意:一个项目中可以配置多个拦截器
自定义拦截器
public class LoginInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
HttpSession session = request.getSession();
if(session != null && session.getAttribute("user") != null){
return true;
}
return false;
}
}
preHandle方法的返回值为boolean类型:
- 如果返回值为true,表示通过拦截,可以访问后续接口
- 如果返回值为false,表示拦截未通过,直接返回结果给前端
如果返回为false,前端展示的为空白,对于登陆校验,当拦截未通过时,我们希望返回到登陆界面,所以可以在返回false之前重定向到登陆页面
public class LoginInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
HttpSession session = request.getSession();
if(session != null && session.getAttribute("user") != null){
return true;
}
//当代码进行到这一步时,说明拦截未通过,防止前端显示空白,重定向到登陆页面
response.sendRedirect("/login.html");
return false;
}
}
加入系统配置并配置拦截规则
@Configuration
public class AppConfig implements WebMvcConfigurer {
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new LoginInterceptor()).
addPathPatterns("/**"). //拦截所有url
excludePathPatterns("/user/login"). //排除登陆接口
excludePathPatterns("/user/register"). //排除注册接口
//排除所有的静态页面
excludePathPatterns("/login.html").
excludePathPatterns("/register.html").
excludePathPatterns("/**/*.js").
excludePathPatterns("/**/*.css").
excludePathPatterns("/**/*.jpg");
}
}
说明:addPathPatterns表示需要拦截的URL,excludePathPatterns表示需要排除的URL
验证登陆拦截器
- 先创建login.html和content.html页面,然后创建后端接口
@RequestMapping("/login")
public String login(String username, String password, HttpServletRequest req){
if(username==null || password==null){
return "请输入用户名和密码";
}
if(username.equals("abc") && password.equals("123")){
HttpSession session = req.getSession(true);
session.setAttribute("user","user");
return "登陆成功";
}
return "登陆失败";
}
@RequestMapping("/content")
public String content(){
return "已经登陆成功";
}
- 创建拦截器(与上述相同,去掉重定向的那行代码),配置拦截规则:我们排除对登陆页面和登陆接口的拦截
@Configuration
public class AppConfig implements WebMvcConfigurer {
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new LoginInterceptor()).
addPathPatterns("/**"). //拦截所有url
excludePathPatterns("/user/login"). //排除登陆接口
excludePathPatterns("/login.html");
}
}
- 启动程序访问登陆页面,登陆页面正常访问
- 访问登陆接口,正常访问,此时未登陆
- 在未登录的情况下访问content.html,可以看到显示空白说明拦截器拦截成功
- 在未登录的情况下访问content接口,可以看到显示空白,说明拦截成功
- 我们进行登陆后再访问content页面和content接口
- 登陆成功后,访问content页面成功
- 登陆成功后,访问content接口成功
经过上述步骤,我们的登陆拦截器验证成功,满足我们需求
添加统一访问前缀
给所有的请求url添加前缀
- 添加方式一:在配置拦截规则的配置类中重写configurePathMatch方法
@Override
public void configurePathMatch(PathMatchConfigurer configurer) {
//c -> true意思是给所有的controller都添加api前缀
configurer.addPathPrefix("api",c -> true);
}
- 添加方式二:在application配置文件中添加,该方式是给全局添加访问前缀,包括访问html静态文件
server:
servlet:
context-path: /api
验证统一前缀是否添加成功
启动程序,用之前的url访问登陆接口,发现找不到资源
添加api前缀访问,访问成功
注意:如果添加了拦截规则,也必须给拦截规则添加前缀,否则会被拦截器拦截到,前端将显示空白
excludePathPatterns("/api/user/login")
统一异常处理
统一异常的步骤:
- 创建一个类,添加@ControllerAdvice注解
- 创建一个方法,方法上添加@ExceptionHandler注解
@ControllerAdvice
表示当前类是针对controller的通知类(增强类),@ExceptionHandler
是异常处理器,两个结合表示当出现异常的时候执行某个通知
//返回json格式数据,将@Controller与@ResponseBody注解合成一个注解@RestController
@RestControllerAdvice
public class MyException {
@ExceptionHandler(Exception.class)
public Object handler(Exception e){
Map<String,Object> map = new HashMap<>();
map.put("state",-1);
map.put("data",null);
map.put("msg",e.getMessage());
return map;
}
}
对异常进行统一处理后,当服务器出现500错误时,会给前端返回自定义的错误信息,不会再直接返回给前端500状态码了
统一数据返回格式
统一数据返回格式步骤:
- 创建一个类,添加@ControllerAdvice注解,实现ResponseBodyAdvice接口
- 重写supports和beforeBodyWrite方法
@RestControllerAdvice
public class MyResponse implements ResponseBodyAdvice {
@Override
public boolean supports(MethodParameter returnType, Class converterType) {
//返回true表示返回数据之前对数据进行重写,也就是会进入beforeBodyWrite方法再返回
//返回false表示对返回数据不进行任何处理,不会进入beforeBodyWrite方法,直接返回
return true;
}
@Override
public Object beforeBodyWrite(Object body, MethodParameter returnType, MediaType selectedContentType, Class selectedConverterType, ServerHttpRequest request, ServerHttpResponse response) {
Map<String,Object> result = new HashMap<>();
result.put("state",1);
result.put("data",body);
result.put("message","");
return request;
}
}