美文网首页SpringBoot之路@IT·互联网Java学习笔记
Spring Boot之路[4]--操作Request和Resp

Spring Boot之路[4]--操作Request和Resp

作者: BeeNoisy | 来源:发表于2016-08-17 15:54 被阅读16313次

    专题简介

    SpringBoot之路专题是一个记录本人在使用Spring和SpringBoot相关技术中所遇到的问题和要解决的问题。每用到一处知识点,就会把这处知识补充到Github一个对应的分支上。会以专题的方式,力争每一篇博客,由浅入深,把每个知识点讲解到实战级别,并且分析Spring源码。整个项目会以一个开发一个博客系统为最终目标,每一个分支都记录着一步一步搭建的过程。与大家分享,代码会同步发布到这里

    本节简介

    本节介绍如何在SpringBoot的环境中引入原始的HttpServletRequest和Response。

    1. 概念

    1. 什么是Request、Response
      这里指的Request和Response对应的是一次Http请求中的Request,在Java的Servlet协议中,把其封装为HttpServletRequest和HttpServletResponse,我们可以方便的从这两个类中取出Http Request中的内容,包括header、cookie、uri、params等等,也能方便的使用HttpServletResponse处理Http Response。
    2. 应用于什么场景、解决了什么问题
      操作Request和Response是编写一个web程序的最基本的需求。比如,读取参数、读取Cookie;设置相应内容,设置响应头等等。

    2. 实战

    1. 可运行的代码
      本节代码在这里
      代码截图:
      运行截图
    2. 代码串讲
    3. 在参数列表中加入HttpServletRequest
    4. 在参数列表中加入HttpServletResponse
      上面两步,Spring会使用反射将这两个属性从上下文环境中注入到对应到本方法中。
    5. 使用reqeust,获取Header中的参数。
    6. 使用response,获取writer。这里的writer可以直接将数据写回到response中。
    7. 使用writer写入数据
    8. 清空write流
      另外,这里没有调用close()方法关闭writer流,请使用者注意在finally中调用close。
    9. 运行截图
    运行截图

    3. 原理

    1. 原理分析
      大体上,我们知道spring是一个反射框架,使用反射技术对相应的参数做到了参数注入。反射,是一个用于操作字节码的java类库,Oracle自身提供了反射的实现,也有一些公司根据java字节码规范实现了基于字节码实现的反射类库,如ASM、Cglib等。
    2. 源码串讲
      在开始前,给大家讲讲我在读源码时的一些技巧。
      代码不能像散文一样,从头到尾的读,而是要把自己模拟成一个颗CPU,更确切一点,把自己模拟成JVM。模拟整个方法的调用,梳理清楚各个方法的调用关系。这样读起源码事半功倍,能梳理清楚主干流程上的关键源码,除掉和关键方法调用关系不大的代码。
      而善于使用eclipse和idea的调试工具,直接查看整个调用栈也是很关键的一个技巧。

      我们来看整个方法的调用栈:
      在idea中,index方法的调用栈截部分图
      红色部分为:java反射包提供的类
      黄色部分为:spring-web提供的类
      绿色部分为:spring-webmvc提供的类
      蓝色部分为:servlet接口提供的类,由tomcat-embed-core做的实现(中间一行的由spring-webmvc提供。)
      灰色部分为及下面更多的内容:tomcat提供的servlet实现。
      这里,我们重点关注
      invokeForRequest(), InvocableHandlerMethod (org.springframework.web.method.support)
      代码如下:
    public Object invokeForRequest(NativeWebRequest request, ModelAndViewContainer mavContainer, Object... providedArgs) throws Exception {
            Object[] args = this.getMethodArgumentValues(request, mavContainer, providedArgs);
            if(this.logger.isTraceEnabled()) {
                StringBuilder returnValue = new StringBuilder("Invoking [");
                returnValue.append(this.getBeanType().getSimpleName()).append(".");
                returnValue.append(this.getMethod().getName()).append("] method with arguments ");
                returnValue.append(Arrays.asList(args));
                this.logger.trace(returnValue.toString());
            }
    
            Object returnValue1 = this.doInvoke(args);
            if(this.logger.isTraceEnabled()) {
                this.logger.trace("Method [" + this.getMethod().getName() + "] returned [" + returnValue1 + "]");
            }
    
            return returnValue1;
        }
    

    其中,调用this.doInvoke(args)的地方会调用反射相关api直接调用对应的index方法。那么,我们需要关注的,就是这个args是如何生成的。
    可以看到在第二行:Object[] args = this.getMethodArgumentValues(request, mavContainer, providedArgs);方法中,对args进行了赋值。那么我们来具体查看这个方法的实现:

    /**
         * Get the method argument values for the current request.
         */
        private Object[] getMethodArgumentValues(NativeWebRequest request, ModelAndViewContainer mavContainer,
                Object... providedArgs) throws Exception {
    
            MethodParameter[] parameters = getMethodParameters();
            Object[] args = new Object[parameters.length];
            for (int i = 0; i < parameters.length; i++) {
                MethodParameter parameter = parameters[i];
                parameter.initParameterNameDiscovery(this.parameterNameDiscoverer);
                GenericTypeResolver.resolveParameterType(parameter, getBean().getClass());
                args[i] = resolveProvidedArgument(parameter, providedArgs);
                if (args[i] != null) {
                    continue;
                }
                if (this.argumentResolvers.supportsParameter(parameter)) {
                    try {
                        args[i] = this.argumentResolvers.resolveArgument(
                                parameter, mavContainer, request, this.dataBinderFactory);
                        continue;
                    }
                    catch (Exception ex) {
                        if (logger.isDebugEnabled()) {
                            logger.debug(getArgumentResolutionErrorMessage("Error resolving argument", i), ex);
                        }
                        throw ex;
                    }
                }
                if (args[i] == null) {
                    String msg = getArgumentResolutionErrorMessage("No suitable resolver for argument", i);
                    throw new IllegalStateException(msg);
                }
            }
            return args;
        }
    

    在上面代码中:
    this.argumentResolvers.resolveArgument(parameter, mavContainer, request, this.dataBinderFactory)对args数组的每一个参数进行了赋值,继续深入源码。

    /**
         * Attempt to resolve a method parameter from the list of provided argument values.
         */
        private Object resolveProvidedArgument(MethodParameter parameter, Object... providedArgs) {
            if (providedArgs == null) {
                return null;
            }
            for (Object providedArg : providedArgs) {
                if (parameter.getParameterType().isInstance(providedArg)) {
                    return providedArg;
                }
            }
            return null;
        }
    

    如果参数类型和提供的参数类型可以匹配,则直接返回。至此,我们分析完了从源码级别分析了,如何做的参数注入。

    相关文章

      网友评论

      • fightingMan2016:想问一下你使用什么编译器?字体是什么?挺漂亮的
        BeeNoisy:@fightingMan2016 ide 是idea 字体是monaco

      本文标题:Spring Boot之路[4]--操作Request和Resp

      本文链接:https://www.haomeiwen.com/subject/fowusttx.html