SpringMVC源码分析 RequestContextHolder使用与源码分析

举报
长路 发表于 2022/11/28 19:28:59 2022/11/28
【摘要】 文章目录前言起因描述实际应用(RequestContextHolder获取到request)引入坐标+启动类AOP切面(使用RequestContextHolder)分析认识RequestContextHolder介绍getRequestAttributes()=》ServletRequestAttributes参考文章 前言 本篇博客是对SpringMVC中的RquestContextHolde

@[toc]

前言

本篇博客是对SpringMVC中的RquestContextHolder的分析,若文章中出现相关问题,请指出!

所有博客文件目录索引:博客目录索引(持续更新)

起因描述

Springboot项目中,多次看到对controller进行切面AOP时在前置通知方法中通过使用下面的方法拿到Request对象:

//通过使用工具类RequestContextHolder拿到ServletRequestAttributes
ServletRequestAttributes requestAttributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
//通过该实例方法获取到request对象
HttpServletRequest request = requestAttributes.getRequest();

接着通过该request对象获取到请求URL以及远程的ip地址,接着进行一系列的日志记录操作存储到数据库中:

image-20210424132929275

提出疑问:为什么通过该工具类调用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>

image-20210424140219894

接着添加一个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)

image-20210424140836996

@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/hellohttp://172.23.153.250:8080/hello,通过request对象获取到了以下信息:

image-20210424141337510

image-20210424141617850



分析

对于通过ServletRequestAttributes调用getRequest()方法实际上获取的是HttpServletRequest接口的实现类:

image-20210424141958194

image-20210424142229283

  • 通过该request能够获取到内容长度、类型、参数属性、协议、服务器名称、服务器端口、ip地址、发送请求的URI及URL、cookies…


认识RequestContextHolder

介绍

RequestContextHolder:持有上下文的Request容器,在该类中持有两个ThreadLocal保存当前线程下的request,其中常用方法如下:

image-20210424142536495

若是想要获取到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

查看下RequestContextHoldergetRequestAttributes()方法:

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

image-20210424144612921

看一下该实现类的方法:

image-20210424144732818

我们可以看到在该实现类中比其接口多出来几个方法(红圈画出来的),这也是为什么我们之前需要进行向下转型的原因!

这样的话我们在进行AOP切面时能够拿到request、response请求响应对象就能够做很多事情了,对一些请求信息进行保存等等的操作…



参考文章

[1]. SpringMVC之RequestContextHolder分析 详细介绍了其存储的是什么,以及何时存储request、response对象的,十分详细

【版权声明】本文为华为云社区用户原创内容,转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息, 否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。