专题简介
SpringBoot之路专题是一个记录本人在使用Spring和SpringBoot相关技术中所遇到的问题和要解决的问题。每用到一处知识点,就会把这处知识补充到Github一个对应的分支上。会以专题的方式,力争每一篇博客,由浅入深,把每个知识点讲解到实战级别,并且分析Spring源码。整个项目会以一个开发一个博客系统为最终目标,每一个分支都记录着一步一步搭建的过程。与大家分享,代码会同步发布到这里。
本节简介
本节介绍如何在SpringBoot的环境中引入原始的HttpServletRequest和Response。
1. 概念
- 什么是Request、Response
这里指的Request和Response对应的是一次Http请求中的Request,在Java的Servlet协议中,把其封装为HttpServletRequest和HttpServletResponse,我们可以方便的从这两个类中取出Http Request中的内容,包括header、cookie、uri、params等等,也能方便的使用HttpServletResponse处理Http Response。 - 应用于什么场景、解决了什么问题
操作Request和Response是编写一个web程序的最基本的需求。比如,读取参数、读取Cookie;设置相应内容,设置响应头等等。
2. 实战
- 可运行的代码
本节代码在这里。
代码截图:
运行截图 - 代码串讲
- 在参数列表中加入HttpServletRequest
- 在参数列表中加入HttpServletResponse
上面两步,Spring会使用反射将这两个属性从上下文环境中注入到对应到本方法中。 - 使用reqeust,获取Header中的参数。
- 使用response,获取writer。这里的writer可以直接将数据写回到response中。
- 使用writer写入数据
- 清空write流
另外,这里没有调用close()方法关闭writer流,请使用者注意在finally中调用close。 - 运行截图
3. 原理
- 原理分析
大体上,我们知道spring是一个反射框架,使用反射技术对相应的参数做到了参数注入。反射,是一个用于操作字节码的java类库,Oracle自身提供了反射的实现,也有一些公司根据java字节码规范实现了基于字节码实现的反射类库,如ASM、Cglib等。 - 源码串讲
在开始前,给大家讲讲我在读源码时的一些技巧。
代码不能像散文一样,从头到尾的读,而是要把自己模拟成一个颗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;
}
如果参数类型和提供的参数类型可以匹配,则直接返回。至此,我们分析完了从源码级别分析了,如何做的参数注入。
网友评论