1. AOP存在的问题
- 获取参数复杂
- AOP的规则相对简单
2. 拦截器
2.1. 应用(以登录为例)
2.1.1. 自定义拦截器
新建interceptor文件夹
import org.springframework.web.servlet.HandlerInterceptor;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
@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("username")!=null){
//通过
return true;
}else {
//没登录
response.setStatus(401);
return false;
}
}
}
2.1.2. 将自定义拦截器加入到系统配置
新建config文件夹
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import springaop.interceptor.LoginInterceptor;
@Configuration
public class AppConfig implements WebMvcConfigurer {
@Autowired
private LoginInterceptor loginInterceptor;
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(loginInterceptor)
.addPathPatterns("/**") //"/**"表示拦截所有
.excludePathPatterns("/user/login") //除了登录
.excludePathPatterns("/user/reg"); //除了注册
}
}
2.1.3. 业务代码
import lombok.extern.slf4j.Slf4j;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpSession;
@Slf4j
@RequestMapping("/user")
@RestController
public class UserController {
//获取用户信息
@RequestMapping("/getInfo")
public String getInfo(){
log.info("log.getInfo");
return "get info...";
}
//注册
@RequestMapping("/reg")
public String reg(){
log.info("log.reg");
return "reg...";
}
//登录
@RequestMapping("/login")
public boolean login(HttpServletRequest request,String username,String password){
log.info("log.login");
//判断username和password是否为空
// if (username!=null && username.equals("") && password!=null && password.equals("")){
// //
// }
if (!StringUtils.hasLength(username) || !StringUtils.hasLength(password)){
return false;
}
if (!"admin".equals(username) || !"admin".equals(password)){
return false;
}
HttpSession session = request.getSession(true);
session.setAttribute("username",username);
return true;
}
}
2.2. 排除所有静态资源方法
方法1
registry.addInterceptor(new LoginInterceptor())
.addPathPatterns("/**") // 拦截所有接口
.excludePathPatterns("/**/*.js")
.excludePathPatterns("/**/*.css")
.excludePathPatterns("/**/*.jpg")
.excludePathPatterns("/login.html")
.excludePathPatterns("/**/login"); // 排除接口
方法2
private final List<String> excludePaths =
//注意List的这种方式的初始化赋值不允许再追加元素
Arrays.asList("/**/*.html",
"blog-editormd",
"/css/**",
"/js/**",
"/pic/**",
"/user/login",
"/user/res");
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(loginInterceptor)
.addPathPatterns("/**")
.excludePathPatterns(excludePaths);
}
2.3. 实现原理
2.3.1. 总体思路
2.3.2. 部分源码分析
拦截器是基于AOP实现的
(AOP基于动态代理(JDK|CGLIB))
3. 统一功能处理
3.1. 统一登录处理
参考上一节 拦截器的应用
3.2. 统一异常处理
对于下面的异常, 我们在访问页面的时候,不希望让用户看到
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping("/exception")
public class ExceptionController {
//算数异常
@RequestMapping("/test1")
public boolean test1(){
int a = 10/0;
return true;
}
//空指针异常
@RequestMapping("/test2")
public boolean test2(){
String str = null;
System.out.println(str.length());
return true;
}
//手动异常
@RequestMapping("/test3")
public boolean test3(){
throw new RuntimeException("手动异常");
}
}
可能出现的问题:
类上面的@RequestMapping("/ex")
访问的时候会有未知的问题,改成@RequestMapping("/exception")
比较好
3.2.1. 配置ErrorHandler
针对不同的异常,进行不同的操作
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;
import java.util.HashMap;
@Slf4j
@ControllerAdvice
@ResponseBody
public class ErrorHandler {
@ExceptionHandler
public Object error(Exception e){
HashMap<String,Object> result = new HashMap<>();
log.info("内部异常:",e);
result.put("success",0);
result.put("code",-1);
result.put("msg","内部异常");
return result;
}
@ExceptionHandler
public Object error(ArithmeticException e){
HashMap<String,Object> result = new HashMap<>();
log.info("算数异常:",e);
result.put("success",0);
result.put("code",-2);
result.put("msg","算术异常");
return result;
}
@ExceptionHandler
public Object error(NullPointerException e){
HashMap<String,Object> result = new HashMap<>();
log.info("空指针异常:",e);
result.put("success",0);
result.put("code",-3);
result.put("msg","空指针异常");
return result;
}
}
3.2.1.1. 可能会遇到的问题
①写完代码访问的时候,会是错误404, 而且异常是返回的不是一个视图, 需要添加@ResponseBody
注解,让程序意识到返回的就是数据, 而不是视图
②分辨不出来异常
之前在切面的@Around环绕通知里有一段代码
要是遇到了异常都会抛出RuntimeException, 从而掩盖了真正的异常, 需要改成下面的样子, 抛出真正的异常
3.3. 统一数据返回格式
3.3.1. 好处
统一数据返回格式的优点有很多. 比如以下几个:
- 方便前端程序员更好的接收和解析后端数据接口返回的数据
- 降低前端程序员和后端程序员的沟通成本,按照某个格式实现就行了,因为所有接口都是这样返回的
- 有利于项目统一数据的维护和修改
- 有利于后端技术部门的统一规范的标准制定,不会出现稀奇古怪的返回内容
3.3.2. 代码
新建ResponseHandler
import org.springframework.core.MethodParameter;
import org.springframework.http.MediaType;
import org.springframework.http.server.ServerHttpRequest;
import org.springframework.http.server.ServerHttpResponse;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.servlet.mvc.method.annotation.ResponseBodyAdvice;
import java.util.HashMap;
@ControllerAdvice
public class ResponseHandler implements ResponseBodyAdvice {
@Override
public boolean supports(MethodParameter returnType, Class converterType) {
return true; //一定要改成true
}
@SneakyThrows
@Override
public Object beforeBodyWrite(Object body, MethodParameter returnType, MediaType selectedContentType, Class selectedConverterType, ServerHttpRequest request, ServerHttpResponse response) {
HashMap<String,Object> result = new HashMap<>();
result.put("success",1);
result.put("data",body);
result.put("errMsg","");
//对字符串进行特殊处理
if (body instanceof String){
ObjectMapper mapper = new ObjectMapper();
return mapper.writeValueAsString(result);
}
return result;
}
}
添加这段代码之前, 访问成功了,得到的是true, 加上这段代码就变成了{"data":true,"success":1,"errMsg":""}
3.3.2.1. 可能出现的问题
①supports()的返回值一定要改成true
②getInfo()的业务代码的返回值是String, 会引发java.util.HashMap cannot be cast to java.lang.String
错误. 由于内部的数据类型转换问题导致, 解决方法是加一步验证.
这段代码会有异常, 用@SneakyThrows
处理. 这个注解的作用就是自动生成一个try-catch, 直接抛出异常