目录
前言
之前博客讲述了 , 关于SpringAOP如何实现, 但是在实际开发中, 我们大多数不会遇到SpringAOP, 而是使用Spring拦截器代替了SpringAOP的功能, 因为Spring拦截器 比起AOP来更简单, 更好上手
模拟拦截器
- 创建一个拦截器 - 定义一个类,实现HandlerInterceptor 接口, 并重写preHandle方法 , 在这个类中 ,写业务的判断方法, (注: 这个类只是一个普通类)
package com.example.demo.config;
import com.example.demo.common.AppVariable;
import org.springframework.web.servlet.HandlerInterceptor;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
public class UserInterceptor implements HandlerInterceptor {
/**
*
* @param request
* @param response
* @param handler
* @return 返回TRUE的时候, 表示拦截器验证成功, 返回FALSE 表示拦截器验证失败
* @throws Exception
*/
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response,
Object handler) throws Exception {
// 业务方法
HttpSession session = request.getSession(false); // 默认值是TRUE ,没有会自动创建一个session会话
if (session != null && session.getAttribute(AppVariable.SESSION_KEY) != null){
// 用户已登录
return true;//如果是TRUE就继续执行后续方法
}
return false;// FALSE 不执行后续方法
}
}
- 将拦截器配置到系统的配置文件中, 并配置拦截器的拦截规则
也就是, 定义一个全局类, 加上@Configuration 注解 , 然后实现WebMvcConfigurer接口, 重写addInterceptors 方法, 在方法中定义具体拦截那些资源
注: 一个项目中可以有多个拦截规则
package com.example.demo.config;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
@Configuration //一定要加一个Configuration
public class AppConfig implements WebMvcConfigurer {
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new UserInterceptor()) // 这儿也可以使用注解的方式来注入 , 表示具体拦截的类
.addPathPatterns("/**") // 一刀切, 拦截所有的请求
.excludePathPatterns("/user/reg") // 配置不拦截的接口
.excludePathPatterns("/user/login") // 不拦截的有更多的就写更多的方法
;
}
}
- 定义一个测试类, 来测试拦截的功能
package com.example.demo.controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping("/user")
public class UserController {
@RequestMapping("/getuser")
public String getUser(){
return "do getUser";
}
@RequestMapping("/reg")
public String reg(){
return "do reg";
}
@RequestMapping("/login")
public String login(){
return "do login";
}
}
拦截器的实现原理
之前的Spring调用流程
加入拦截器的调用流程
可以看出, 拦截器就是在访问控制层之前, 先将用户的请求处理一遍, 如果正确就给与后序层去调用, 如果不正确就拦截下来,让其实现对应的业务逻辑
拦截器也是通过动态代理的方式来实现的,
什么是动态代理? 什么是静态代理
代理: 就是将我们不愿意做的事情,交给别人去做
我们先来说什么是静态代理: 在不修改被代理对象的基础上,通过扩展代理类,进行一些功能的附加与增强。简单来说静态代理就是在不改变源代码的基础上增加新的功能
比方说: 俩个类都实现了一个接口, 这样类A就可以通过类B 来加强一些自己说不具备的功能, 这种就可以说是静态代理, 因为你是根据代码提前写好去调用其他对象,这些对象在程序运行前就已经加载上了
有了静态代理的概念: 那么动态代理也就很清晰了
我们从上述可以看出静态代理受限于接口的实现。动态代理就是通过使用反射,动态地获取抽象接口的类型,从而获取相关特性进行代理
代理类在程序运行期间,创建的代理对象称之为动态代理对象。这种情况下,创建的代理对象,并不是事先在Java代码中定义好的。而是在运行期间,根据我们在动态代理对象中的“指示”,动态生成的
静态代理与动态代理的区别
静态代理需要自己写代理类并一一实现目标方法,且代理类必须实现与目标对象相同的接口。
动态代理不需要自己实现代理类,它是利用 JDK或CGLib,动态地在内存中构建代理对象(需要我们传入被代理类),并且默认实现所有目标方法。
两种常用的动态代理方式
基于接口的动态代理
是JDK提供的: 必须使用JDK官方的Proxy类创建代理对象,代理的目标对象必须实现接口
基于类的动态代理
是 CGLib提供的: 使用CGLib的Enhancer类创建代理对象
JDK Proxy 与 CGlib的区别
- 出生不同
- 实现不同 , JDK Proxy 要求代理类实现接口才能实现代理 , 而CGLib是实现代理类的子类完成动态代理
- 性能不同, JDK 7 Proxy 性能高于 CGLib , 而JDK 7 之前CGLib性能高于Proxy
其他 统⼀访问前缀添加
所有请求地址添加 api 前缀 , 在实现WebMvcConfigure 接口的类中 ,重写configurePathMatch方法 , 其中第⼆个参数是⼀个表达式,设置为 true 表示启动前缀
@Configuration
public class AppConfig implements WebMvcConfigurer {
// 所有的接⼝添加 api 前缀
@Override
public void configurePathMatch(PathMatchConfigurer configurer) {
configurer.addPathPrefix("api", c -> true);
}
}
统⼀异常处理
统⼀异常处理使⽤的是 @ControllerAdvice + @ExceptionHandler 来实现的,@ControllerAdvice 表示控制器通知类,@ExceptionHandler 是异常处理器,两个结合表示当出现异常的时候执⾏某个通知,也就是执⾏某个⽅法事件,具体实现代码如下:
⽅法名和返回值可以⾃定义,其中最重要的是@ExceptionHandler(Exception.class) 注解 , 注解中放具体的异常类型, 也可以直接使用Exception来处理所有的异常,
调用流程如下: 定义了Exception异常的前提下, 如果找到对应异常就使用对应异常处理, 找不到就是用Exception异常处理
import java.util.HashMap;
@ControllerAdvice
public class ErrorAdive {
@ExceptionHandler(Exception.class)
@ResponseBody
public Object handler(Exception e) {
HashMap<String, Object> map = new HashMap<>();
map.put("state", 0);
map.put("data", null);
map.put("msg", e.getMessage());
return map;
}
}
统⼀数据返回格式
统⼀的数据返回格式可以使⽤ @ControllerAdvice + ResponseBodyAdvice 的⽅式实现,具体实现代
码如下
如果想要使用统一数据的返回格式, 首先在类上加上@ControllerAdvice注解, 然后在类上实现 ResponseBodyAdvice 接口, 并重写俩个方法, support 与beforeBodywrite , 其中support 必须返回TRUE 否则不调用beforeBodywrite 方法, 在beforeBodywrite中写数据返回类型
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 ResponseAdvice implements ResponseBodyAdvice {
/**
* 内容是否需要重写(通过此⽅法可以选择性部分控制器和⽅法进⾏重写)
* 返回 true 表示重写
*/
@Override
public boolean supports(MethodParameter returnType, Class converterTyp
e) {
return true;
}
/**
* ⽅法返回之前调⽤此⽅法
*/
@Override
public Object beforeBodyWrite(Object body, MethodParameter returnType,
MediaType selectedContentType,
Class selectedConverterType, ServerHttpR
equest request,
ServerHttpResponse response) {
// 构造统⼀返回对象
HashMap<String, Object> result = new HashMap<>();
result.put("state", 1);
result.put("msg", "");
result.put("data", body);
return result;
}
}