SpringMVC源码分析 RequestContextHolder使用与源码分析
@[toc]
前言
本篇博客是对SpringMVC中的RquestContextHolder的分析,若文章中出现相关问题,请指出!
所有博客文件目录索引:博客目录索引(持续更新)
起因描述
在Springboot
项目中,多次看到对controller
进行切面AOP时在前置通知方法中通过使用下面的方法拿到Request
对象:
//通过使用工具类RequestContextHolder拿到ServletRequestAttributes
ServletRequestAttributes requestAttributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
//通过该实例方法获取到request对象
HttpServletRequest request = requestAttributes.getRequest();
接着通过该request对象获取到请求URL以及远程的ip地址,接着进行一系列的日志记录操作存储到数据库中:
提出疑问:为什么通过该工具类调用getRequest()
就能够获取到请求对象?
实际应用(RequestContextHolder获取到request)
引入坐标+启动类
通过使用springboot框架来减少配置文件的编写,即拿即用:
<!-- web启动器:包含sping、springmvc、tomcat以及相关web集成工具、日志等 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<version>2.4.5</version>
</dependency>
<!-- 若想使用AOP,需要引入该jar包进行切面 -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aspects</artifactId>
<version>5.3.2</version>
</dependency>
接着添加一个Springboot启动类即可运行使用了!(内置了tomcat服务器,并且内部自动完成了spring、springmvc的配置)
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class SmartparkingApplication {
public static void main(String[] args) {
SpringApplication.run(com.SmartparkingApplication.class, args);
}
}
接着我们编写一个控制器HelloController
来接收指定请求:
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
/**
* @ClassName HelloController
* @Author ChangLu
* @Date 2021/4/24 11:13
* @Description TODO
*/
@Controller
public class HelloController {
@RequestMapping("/hello")
public String hello(){
return "hello"; //跳转到template目录下的hello.html,方便快速测试我们可以不使用thymeleaf以及html文件,能够达到日志切面效果即可
}
}
此时我们启动通过启动类即可运行springboot项目了,特别简单!
AOP切面(使用RequestContextHolder)
@Component
@Aspect
public class LogAspect {
//对控制器包下的所有类方法进行切面
@Pointcut("execution(* com.controller.*.*(..))")
public void point(){}
//前置通知
@Before("point()")
public void before(JoinPoint joinPoint){
ServletRequestAttributes requestAttributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();//通过工具类来获取到
HttpServletRequest request = requestAttributes.getRequest();
System.out.println(request.getSession());//session对象
System.out.println(request.getMethod());//方法名
System.out.println(request.getRequestURI());//请求路径(仅仅是被代理方法上的uri)
System.out.println(request.getRequestURL());//完整请求路径
System.out.println(request.getRemoteAddr());//可看做URI。返回发送请求的客户端或最后一个代理的Internet协议(IP)地址。 对于HTTP Servlet,与CGI变量REMOTE_ADDR的值相同
System.out.println(request.getRemoteHost());//可看做URL。返回客户端或发送请求的最后一个代理的标准名称。 如果引擎无法或选择不解析主机名(以提高性能),则此方法返回IP地址的点分字符串形式。 对于HTTP Servlet,与CGI变量REMOTE_HOST的值相同。
System.out.println(request.getRemotePort());//发送客户端的端口号
System.out.println(request.getRemoteUser());//主机用户
System.out.println(request.getContextPath());//上下文路径
}
}
启动之后,分别使用浏览器或手机访问:http://localhost:8080/hello 或 http://172.23.153.250:8080/hello,通过request对象获取到了以下信息:
分析
对于通过ServletRequestAttributes
调用getRequest()
方法实际上获取的是HttpServletRequest
接口的实现类:
- 通过该request能够获取到内容长度、类型、参数属性、协议、服务器名称、服务器端口、ip地址、发送请求的URI及URL、cookies…
认识RequestContextHolder
介绍
RequestContextHolder
:持有上下文的Request容器,在该类中持有两个ThreadLocal
保存当前线程下的request
,其中常用方法如下:
若是想要获取到request、response,需要调用getRequestAttrebutes()
获取到RequestAttrebutes
,接着通过该对象来获取,如下:
//首先通过RequestContextHolder请求上下文执行者拿到ServletRequestAttributes
//你可将ServletRequestAttributes看做是HttpServletRequest的容器,通过该容器存储
ServletRequestAttributes requestAttributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
HttpServletRequest request = requestAttributes.getRequest();//获取请求对象
HttpServletResponse response = requestAttributes.getResponse();//获取相应对象
简述:springmvc在执行service()
,doGet()
,doPost()
这类方法时,其方法内部都有一个预处理方法processRequest(request, response)
,在预处理方法中有initContextHolders(request, localeContext, requestAttributes)
初始化上下文执行者,其中包含了对应的三个参数,即把新的RequestAttributes设置进LocalThread
保存,实际上保存的类型为ServletRequestAttributes。
getRequestAttributes()=》ServletRequestAttributes
查看下RequestContextHolder
的getRequestAttributes()
方法:
public abstract class RequestContextHolder {
private static final boolean jsfPresent =
ClassUtils.isPresent("javax.faces.context.FacesContext", RequestContextHolder.class.getClassLoader());
//有两个ThreadLocal存储的就是请求对象
private static final ThreadLocal<RequestAttributes> requestAttributesHolder =
new NamedThreadLocal<>("Request attributes");
private static final ThreadLocal<RequestAttributes> inheritableRequestAttributesHolder =
new NamedInheritableThreadLocal<>("Request context");
//调用方法获取到ThreadLocal中的RequestAttributes
@Nullable
public static RequestAttributes getRequestAttributes() {
RequestAttributes attributes = requestAttributesHolder.get();
if (attributes == null) {
attributes = inheritableRequestAttributesHolder.get();
}
return attributes;
}
}
ThreadLocal
:其解决多线程的数据安全问题,保证在多个线程下一个线程能够关联绑定指定的数据。只要线程是活动的并且ThreadLocal实例是可访问的,则每个线程都对其线程局部变量的副本持有隐式引用。 线程消失后,其线程本地实例的所有副本都将进行垃圾回收(除非存在对这些副本的其他引用
通过调用该方法返回获取到一个RequestAttributes
该返回值是一个接口,实际返回的是其实现类ServletRequestAttributes
看一下该实现类的方法:
我们可以看到在该实现类中比其接口多出来几个方法(红圈画出来的),这也是为什么我们之前需要进行向下转型的原因!
这样的话我们在进行AOP切面时能够拿到request、response请求响应对象就能够做很多事情了,对一些请求信息进行保存等等的操作…
参考文章
[1]. SpringMVC之RequestContextHolder分析 详细介绍了其存储的是什么,以及何时存储request、response对象的,十分详细
- 点赞
- 收藏
- 关注作者
评论(0)