JAVA系列---HttpServletRequest

Servlet 处理 HTTP 请求的流程

一般情况下,浏览器(客户端)通过 HTTP 协议来访问服务器的资源,Servlet 主要用来处理 HTTP 请求。核心对象有三个

  • Servlet:提供service()方法处理请求
  • ServletRequest:请求信息载体
  • ServletRespond:响应信息载体
    在这里插入图片描述
  1. Servlet 容器接收到来自客户端的 HTTP 请求后,Servlet容器会针对该请求分别创建一个 HttpServletRequest 对象和 HttpServletReponse 对象。
  2. 容器将 HttpServletRequest 对象和 HttpServletReponse 对象以参数的形式传入 service() 方法内,并调用该方法。
  3. 在 service() 方法中 Servlet 通过 HttpServletRequest 对象获取客户端信息以及请求的相关信息。
  4. 对 HTTP 请求进行处理。
  5. 请求处理完成后,将响应信息封装到 HttpServletReponse 对象中。
  6. Servlet 容器将响应信息返回给客户端。
  7. 当 Servlet 容器将响应信息返回给客户端后,HttpServletRequest 对象和 HttpServletReponse 对象被销毁。

HttpServletRequest 和 HttpServletReponse 是 Servlet 处理 HTTP 请求流程中最重要的两个对象。HttpServletRequest 对象用于封装 HTTP 请求信息,HttpServletReponse 对象用于封装 HTTP 响应信息。

ServletRequest

ServletRequest接口是Java Servlet API中的一个接口,用于访问Servlet容器接收到的HTTP请求的信息。ServletRequest接口定义了一系列方法,用于获取HTTP请求的参数、属性、输入流等信息,以及对这些信息的操作。所以想要从请求中获取信息,得从ServletRequest 中去寻找

  • getProtocol():返回请求使用的协议的名称和版本号。例如,HTTP/1.1。
  • getScheme():返回请求的协议名称。例如,http、https。
  • getServerName():返回接收请求的服务器的名称。
  • getServerPort():返回接收请求的服务器的端口号。
  • getRemoteAddr():返回客户端的IP地址。
  • getRemoteHost():返回客户端的主机名。
  • getRemotePort():返回客户端的端口号。
  • getLocalAddr():返回服务器的IP地址。
  • getLocalName():返回服务器的主机名。
  • getLocalPort():返回服务器的端口号。
  • getParameter(String name):返回请求参数的值,如果请求参数不存在,则返回null。
  • getParameterNames():返回请求参数的名称的枚举。
  • getParameterValues(String name):返回请求参数的值的数组,如果请求参数不存在,则返回null。
  • getAttribute(String name):返回指定属性名称的属性值,如果属性不存在,则返回null。
  • getAttributeNames():返回所有属性名称的枚举。
  • setAttribute(String name, Object value):将指定属性名称的属性值设置为指定的值。
  • removeAttribute(String name):从请求中删除指定名称的属性。
  • getLocale():返回客户端的首选语言环境。
  • getLocales():返回客户端的所有语言环境。
  • getCharacterEncoding():返回请求字符编码的名称,如果字符编码未指定,则返回null。
  • getContentLength():返回请求的正文的长度,以字节为单位。
  • getContentType():返回请求正文的MIME类型。
  • getInputStream():返回请求正文的InputStream。
  • getReader():返回请求正文的Reader。
  • getProtocol():返回请求使用的协议的名称和版本号。
  • getRemoteUser():返回发出请求的用户的登录名,如果用户未经过身份验证,则返回null。
  • isSecure():返回请求是否通过安全连接传输。
  • getRequestDispatcher(String path):返回用于将请求转发到另一个资源的RequestDispatcher对象。
  • getRealPath(String path):返回指定虚拟路径的真实路径。
  • getRequestURI():返回请求的URI,不包括查询字符串。
  • getRequestURL():返回请求的URL,包括协议,服务器名称,端口号和请求URI,但不包括查询字符串。
  • getServletPath():返回Servlet的路径。
  • getSession():返回与此请求相关联的会话,如果请求没有会话,则创建一个新会话。
  • getSession(boolean create):返回与此请求相关联的会话,如果请求没有会话,则根据指定的create参数创建一个新会话或不创建。
  • isRequestedSessionIdValid():返回请求的会话ID是否仍然有效。
  • isRequestedSessionIdFromCookie():返回请求是否使用Cookie来检索会话ID。
  • isRequestedSessionIdFromURL():返回请求是否将会话ID附加到URL中。
  • isRequestedSessionIdFromUrl():已过时的方法,等效于isRequestedSessionIdFromURL()。

HttpServletRequest

在 Servlet API 中,定义了一个 HttpServletRequest 接口,它继承自 ServletRequest 接口。HttpServletRequest 专门用于封装 HTTP 请求消息,该接口在ServletRequest接口的基础上增加了许多HTTP特定的方法,以提供更多的HTTP请求和响应相关的信息。部分新增信息如下:

  • getMethod():返回HTTP请求的方法类型(GET、POST等)。
  • getPathInfo():返回HTTP请求的路径信息。
  • getPathTranslated():返回HTTP请求的路径翻译后的信息。
  • getQueryString():返回HTTP请求的查询字符串。
  • getContentLength():返回HTTP请求的主体内容长度。
  • getContentType():返回HTTP请求的主体内容类型。
  • getServletPath():返回HTTP请求的Servlet路径。
  • getRequestURI():返回HTTP请求的统一资源标识符(URI)。
  • getRequestURL():返回HTTP请求的统一资源定位器(URL)。
  • getProtocol():返回HTTP请求的协议名称和版本号。
  • getRemoteAddr():返回HTTP请求的客户端IP地址。
  • getRemoteHost():返回HTTP请求的客户端主机名。
  • getServerName():返回HTTP请求的服务器名称。
  • getServerPort():返回HTTP请求的服务器端口号。
  • getScheme():返回HTTP请求使用的协议名称(http、https等)。
  • isSecure():返回HTTP请求是否通过安全套接字层(SSL)进行传输。

URI和URL的区别

  • URI(Uniform Resource Identifier,统一资源标识符)是一个标识某个资源的字符串,可以用来唯一地标识一个资源,包括但不限于Web页面、图片、文本文件等。URI包括两个部分:URL和URN。URN(Uniform Resource Name,统一资源名称)是通过名字来标识资源的。
  • URL(Uniform Resource Locator,统一资源定位符)则是通过地址来标识资源的,即URL是一种特定类型的URI,它通过指定地址来标识特定的资源。例如,"https://www.example.com/index.html"是一个URL,它指定了要访问的资源的地址和协议。

因此,**URL是URI的一个子集,所有的URL都是URI,但不是所有的URI都是URL。URI还可以表示一些没有直接关联到网络资源的抽象概念,例如一个人的名字或者一份文档的版本号。**而URL则是用来指定如何访问Web资源的字符串,包括协议、主机名、端口号、路径和查询参数等。

Tomcat 中创建 HttpServletRequest

在 Tomcat 中,HttpServletRequest 对象的创建是由 Catalina 处理请求的核心组件 org.apache.catalina.connector.Connector 负责的。具体来说,当 Connector 接收到客户端请求时,会创建 Request 对象(该对象封装了 HttpServletRequest 对象)并将其传递给 Tomcat 的管道处理器 Pipeline 进行处理。Pipeline 中会将 Request 对象传递给多个阶段的处理器(Valve),在每个阶段中对 Request 对象进行处理并执行相应的操作(如安全认证、过滤器等),最终将 Request 对象传递给对应的 Servlet 进行业务逻辑处理。

具体创建 HttpServletRequest 对象的源码如下所示:

@Override
public void service(SocketWrapperBase<?> socket) throws IOException {
    // ...
    // 创建 Request 对象并将其封装到 Processor 实例中
    Http11Processor processor = createProcessor();
    processor.assign(socket);
    if (proto.isSSLEnabled() && (socket instanceof SecureNio2Channel)) {
        SecureNio2Channel channel = (SecureNio2Channel) socket;
        SSLEngine engine = proto.getSSLEngine();
        if (engine != null) {
            channel.reset(engine);
        }
    }
    // 将请求交给 Pipeline 处理器进行处理
    pipeline.getFirst().invoke(processor.getRequest(), processor.getResponse());
    // ...
}

从上述代码中可以看出,当接收到客户端请求时,Tomcat 会通过 createProcessor 方法创建 Http11Processor 实例,并将 SocketWrapperBase 作为参数传递给该实例。在 Http11Processor 中,会调用 createRequest 方法创建 Request 对象,并将 SocketWrapperBase 和 Http11InputBuffer 封装到 Request 对象中。最终,Tomcat 将 Request 对象作为参数传递给 Pipeline 处理器进行处理。在 Pipeline 中,会将 Request 对象传递给多个 Valve 进行处理,最终将 Request 对象传递给对应的 Servlet 进行业务逻辑处理。

Servlet接收请求中创建的对象

  • HttpServletRequest 对象:封装了客户端请求的信息,包括请求参数、请求头、请求体等信息。
  • HttpServletResponse 对象:封装了响应信息,包括响应状态码、响应头、响应体等信息。
  • ServletContext 对象:代表 Servlet 上下文,可以用于在不同 Servlet 之间共享数据。
  • HttpSession 对象:代表客户端的会话,可以用于在同一客户端多次请求之间共享数据。
  • ServletConfig 对象:代表 Servlet 的配置信息,包括 Servlet 的初始化参数等信息。
  • ServletRequest 对象:是 HttpServletRequest 的父接口,定义了通用的请求方法,如获取请求参数等。
  • ServletResponse 对象:是 HttpServletResponse 的父接口,定义了通用的响应方法,如设置响应头等。

以上对象都是由 Servlet 容器在接收到客户端请求时创建的,可以在 Servlet 中直接使用。其中,HttpServletRequest 和 HttpServletResponse 对象是每个请求独有的,而 ServletContext 和 HttpSession 对象则是在多个请求之间共享的。
这就是为什么springboot中可以直接获取这两个对象,这两个对象不需要传入。

需要注意的是,具体创建对象的方式可能因 Servlet 容器而异。例如,在 Tomcat 中,HttpServletRequest 和 HttpServletResponse 对象是由 Catalina 处理请求的核心组件 org.apache.catalina.connector.Connector 负责创建的,而 ServletContext 和 HttpSession 对象则是由 org.apache.catalina.core.StandardContext 创建的。

接口中怎么使用

@RequestMapping("name2")
public Object task2() {
    log.info("start");
    RequestAttributes requestAttributes = RequestContextHolder.currentRequestAttributes();
    HttpServletRequest request = ((ServletRequestAttributes)requestAttributes).getRequest();
    System.out.println(request.getHeader("Host"));
    return name2;
}
@RequestMapping("name2")
public Object task2( HttpServletRequest request, HttpServletResponse response) {
    log.info("start");
    System.out.println(request.getHeader("Host"));
    return name2;
}
@Autowired
HttpServletRequest request;

@RequestMapping("name2")
public Object task2() {
    log.info("start");
    System.out.println(request.getHeader("Host"));
    return name2;
}

以上伪代码的作用是一致的,无论在接口上写参数还是方法内创建还是自动注入,都不会影响调用者,因为这两个对象是Servlet创建和销毁的,可以直接使用。

spring中直接注入HttpServletRequest

spring中居然可以将HttpServletRequest直接通过@Autowired注入,突然有点懵。

  • HttpServletRequest并非Spring中的类,且在没有手动通过@Bean的方式注入,Spring是怎么做到帮开发者完成注入的?理论上,我们需要注入其他包中的对象的时候,是必须在@Configuration修饰的类下面使用@Bean进行方法修饰的才行的

    @Configuration
    public class ApplicationConfig {
        @Bean
        public RestTemplate getRestTemplate(){
            return new RestTemplate();
        }
    }
    
  • 同时,ioc容器中默认注入的Bean是单例,而每个请求都是独立的,这样不会出问题吗?

demo分析

@RestController
public class HttpServletRequestTest {
 
    @Autowired
    private HttpServletRequest httpServletRequest;
 
    @GetMapping("/sayHi")
    public void sayHi(String name) {
 
        System.out.println(httpServletRequest.getRequestURL().toString());
        JSONObject res = new JSONObject();
        res.put("requestId", UUID.randomUUID().toString());
        res.put("datatime", DateUtils.getDatetime());
        // ReturnResult.get(res);
 
        System.out.println("hello: " +  name);
    }
 
    @PostConstruct
    public void after(){
        System.out.println(this.httpServletRequest);
    }
 
}
  • @PostConstruct:后构造注解,见名知意,标注于方法之上,在对象加载完依赖注入后执行,常用于初始化连接源等信息。是java的注解。在Servlet生命周期中有一定作用,它通常都是一些初始化的操作,但初始化可能依赖于注入的其他组件,所以要等依赖全部加载完再执行。
  • @PreDestroy:前销毁注解,同理后构造
  • 在spring项目中,在一个bean的初始化过程中,方法执行先后顺序为Constructor > @Autowired > @PostConstruct > @PreDestroy

Spring不是只能自动注入被管理的对象吗?

作为一个外部对象,HttpServletRequest为什么可以在Spring项目中通过注入的方式获取?

@PostConstruct注解被用在执行完依赖注入之后的方法调用上,我们将断点打在上述demo的第19行,即可查看HttpServletRequest httpServletRequest实例化之后的情况。

在这里插入图片描述
由上图我们可以看到这个httpServletRequest对象是一个代理对象(org.springframework.web.context.support.WebApplicationContextUtils.RequestObjectFactory),该对象是一个请求的对象工厂。进入WebApplicationContextUtils这个类,Command + F12我们发现了一个往 ConfigurableListableBeanFactory工厂对象注入bean对象的方法registerWebApplicationScopes。

该方法有这样一行代码:

beanFactory.``registerResolvableDependency(ServletRequest.class, new RequestObjectFactory()``);

由于HttpServletRequest正是继承自ServletRequest,这里引起了我们的关注。
在这里插入图片描述
ConfigurableListableBeanFactory的registerResolvableDependency方法又是用什么的呢?我们继续往下看。
在这里插入图片描述
使用相应的自动装配值注册一个特殊的依赖类型。
这适用于被认为可自动装配但未在工厂中定义为 bean 的工厂/上下文引用

原来Spring可以为一个类注入另一个完全不同的对象值,这样从ioc容器中引用这个类的时候其实拿到的就是这个对象值。且上面的描述正好回应了,为什么我们没有手动定义HttpServletRequest却可以完成它的自动装配,秘诀就在这里。

进一步拓展思考,上面两张图一起看,除了ServletRequest.class,ServletResponse.class,HttpSession.class以及WebRequest.class类型对象,均被Spring自动注入,可以直接通过注解的方式引用。

继续进入registerResolvableDependency方法,我们发现该方法的实现是将ServletRequest.class的依赖类型作为key, RequestObjectFactory作为装配的value,放入了resolvableDependencies这个map中。
在这里插入图片描述

Spring中的对象不是单例吗?

HttpServletRequest注入和引用我们知道了,那么针对每个独立的请求,多线程场景下,通过自动注入的方式,HttpServletRequest 是否会有线程安全的问题呢?

我们已经知道注入拿到的HttpServletRequest返回的对象其实是下面这个

org.springframework.web.context.support.WebApplicationContextUtils.``RequestObjectFactory对象,那么它是怎么做到线程安全的呢?

我们看它的具体实现。

在这里插入图片描述
沿着currentRequestAttributes()方法一路点击,最终发现它的返回值,是来自org.springframework.web.context.request.RequestContextHolder#requestAttributesHolder。

在这里插入图片描述
requestAttributesHolder对象是一个ThreadLocal对象,由此我们明白了,Spring自动注入的HttpServletRequest能够保证请求的唯一性,原来是通过跟每个线程绑定了,从ThreadLocal中取得请求信息,由此保证的线程安全!

我们再来拓展一下,既然请求信息是从ThreadLocal中取的,那么请求信息是如何放进去的呢?

我们找到requestAttributesHolder属性的set方法,在这里打一个断点,重启系统,并发起一个http请求。
在这里插入图片描述
最终,通过调用堆栈信息,我们看到了org.springframework.web.filter.RequestContextFilter#doFilterInternal
中调用了initContextHolder方法,而initContextHolder方法调用了requestAttributesHolder这个ThreadLocal的set方法。

每个http请求过来会进入RequestContextFilter这个filter,在这个filter中会将request信息设置到当前的线程里。

总结

  • 在代码中通过注解注入HttpServletRequest的方式,拿到的其实并不是真正的 HttpServletRequest,而是一个Spring项目启动时自动注入的代理对象org.springframework.web.context.support.``WebApplicationContextUtils.RequestObjectFactory。
    该对象跟ServletRequest.class做了映射关联,放入了Spring管理bean注入的map中。

    private static class RequestObjectFactory implements ObjectFactory<ServletRequest>, Serializable {
    
            @Override
            public ServletRequest getObject() {
            
                    // 获取当前 ServletRequest
                return currentRequestAttributes().getRequest();
            }
    
            @Override
            public String toString() {
                return "Current HttpServletRequest";
            }
        }
        
        
        private static ServletRequestAttributes currentRequestAttributes() {
            RequestAttributes requestAttr = RequestContextHolder.currentRequestAttributes();
            if (!(requestAttr instanceof ServletRequestAttributes)) {
                throw new IllegalStateException("Current request is not a servlet request");
            }
            return (ServletRequestAttributes) requestAttr;
        }
    }
    
    // 那这个“当前 ServletRequest”从哪来呢?
    //Servlet 除了 ServletContextListener 之外,还有一个 ServletRequestListener:
    
    public interface ServletRequestListener extends EventListener {
        void requestDestroyed(ServletRequestEvent var1);
    
        void requestInitialized(ServletRequestEvent var1);
    }
    //只要你注册了这个,就能在请求前执行requestDestroyed
    //Spring 弄了一个 org.springframework.web.context.request.RequestContextListener,在这个里面获取并存储的
    
    @Override
        public void requestInitialized(ServletRequestEvent requestEvent) {
            if (!(requestEvent.getServletRequest() instanceof HttpServletRequest)) {
                throw new IllegalArgumentException(
                        "Request is not an HttpServletRequest: " + requestEvent.getServletRequest());
            }
            HttpServletRequest request = (HttpServletRequest) requestEvent.getServletRequest();
            ServletRequestAttributes attributes = new ServletRequestAttributes(request);
            request.setAttribute(REQUEST_ATTRIBUTES_ATTRIBUTE, attributes);
            LocaleContextHolder.setLocale(request.getLocale());
            
            // 存储 ServletRequest
            RequestContextHolder.setRequestAttributes(attributes);
        }
    
  • 每次请求时,如何保证自动注入的HttpServelet请求线程安全,等价于问 org.springframework.web.context.support.WebApplicationContextUtils.``RequestObjectFactory 怎么做到的线程安全。这个类只是一个简单的对象工厂,getObject方法最终从org.springframework.web.context.request.RequestContextHolder#requestAttributesHolder这个ThreadLocal中获取请求的信息,由此做到了请求之间的隔离。

  • 其实spring中还有很多类可以直接注入:import org.springframework.cloud.client.discovery.DiscoveryClient。

  • 直接注入和需要@Bean修饰之后才能注入的区别:一个是内部实现好了,默认已经放进去了;一个是我们业务需求自己放进去的,也就是你要把这个bean交给spring管理,这就是spring ioc 控制反转。

  • 特殊情况特殊处理。不要把ioc死板的搬过来。Request, Response, HttpEntity等都是特殊处理的。而且这几个对象和请求有关,不在窗口里面。

  • 21
    点赞
  • 25
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

lipviolet

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值