- 浏览: 78431 次
- 性别:
- 来自: 上海
最新评论
-
zhangdong92:
终于找到这个我很疑惑的问题的答案了。之前也遇到过想在一个方法里 ...
【spring】spring是如何处理请求的?源码分析 -
zxmsdyz:
解决了我三年后的问题 看了下源码凌乱了,楼主分析的不错有耐 ...
【mybatis】IF判断的坑 -
不忘初心ok:
厉害,解决了
【mybatis】IF判断的坑 -
浑身卟域帖:
...
【mybatis】IF判断的坑 -
u011654693:
问题解决了,非常感谢。
【mybatis】IF判断的坑
最近在处理一些问题的时候,突然想到spring的两个问题。
1.spring是如何回调我们的controller中定义的方法的,request response model 是怎么来的?为什么先后顺序可以颠倒?
2.spring是如何帮我们注入基本类型的参数的,比如我参数中有两个String,但是参数名字不一样,一个是id,一个是type,但是spring可以直接帮我们注入进来,不需要添加任何注解,他是怎么实现的?
抱着这两个问题,我开始看spring是如何处理请求的。
首先,spring是为我们封装了servlet,所以肯定会有一个类继承HttpServlet,从这个入口开始寻找,我们就找到了DispatcherServlet ,他是HttpServlet的一个实现类。其中重写了父类的doService方法。看到方法最后有调用doDispatch(request, response);方法,到了spring的处理器。
在这个方法中做了如下几件事情
1.寻找URL所对应的mappedHandler处理器,如果找不到的话,执行noHandlerFound方法,其中就是会返回404。
2.如果是get请求的话,并且HTTP请求头标签中包含If-Modified-Since,在发送HTTP请求时,把浏览器端缓存页面的最后修改时间一起发到服务器去,服务器会把这个时间与服务器上实际文件的最后修改时间进行比较。如果时间一致,那么返回HTTP状态码304(不返回文件内容),客户端接到之后,就直接把本地缓存文件显示到浏览器中。
3.获取到自己定义的拦截器,循环invoke拦截器。
4.回调我们所定义的mapperHandler 也就是我们再controller中所定义的处理这个请求的方法。
5.获取到返回的 MV 通过response写出。
下面主要来看下第四部,spring是怎么回调我们自己定义的controller的。
这里ha是HandlerAdapter 接口,根据spring的配置文件,走不同的处理器。
如上配置可以看到,配置的处理器为RequestMappingHandlerAdapter。他实现了父类的handler方法,然后调用子类的handleInternal方法处理请求。
RequestMappingHandlerAdapter中实现了父类的handleInternal,在这个类里面主要做了一些限制判断。
1.如HTTP请求方式和方法上限制了HTTP请求不同的话,则会抛出HttpRequestMethodNotSupportedException。
2.如果在配置文件中配置了requireSession=true的话,此处会抛出 HttpSessionRequiredException("Pre-existing session required but none found");
现在回到我们handleInternal方法中,接下去如果我们配置了synchronizeOnSession配置,表示该控制器是否在执行时同步session,从而保证该会话的用户串行访问该控制器。接下去执行invokeHandlerMethod方法。
这里主要看一下 requestMappingMethod.invokeAndHandle(webRequest, mavContainer); 中的invokeForRequest方法。
这个方法中,主要做两件事情,
1.获取到了本次请求所对应执行方法的参数。
2.通过反射invoke 调用方法。注入参数
这里也是让我一开始很困惑的地方,因为反射只能获取到方法参数的类型,无法获取到方法参数的变量名,那spring是如何做到获取到方法参数变量名呢?我们继续往下面看。
看到spring通过getMethodArgumentValues方法获取了所有的参数列表。
其中resolveArgument的方法中HandlerMethodArgumentResolver是一个接口,通过 getArgumentResolver(parameter); 方法获取到不同的解析器。来解析参数值。
HandlerMethodArgumentResolver接口中定义了resolveArgument让子类来实现方法,获取到具体的参数,在@RequestParam注解的参数的解析器是使用的AbstractNamedValueMethodArgumentResolver类来实现的。我们具体来看一下AbstractNamedValueMethodArgumentResolver类中的实现。
看到如上代码中,首先获取到了某个参数的类型,再通过getNamedValueInfo(parameter);方法返回了NamedValueInfo对象,在NamedValueInfo对象中,就已经包含了当前参数的名字是什么,那就继续看getNamedValueInfo方法。
spring代码中写了一个cache用于保存已经解析过的nameValueInfo,因为在程序运行期间,这个值肯定是固定的。
如果cache中返回空的话,则会通过createNamedValueInfo(parameter);方法创建一个nameValueInfo,再通过updateNamedValueInfo(parameter, namedValueInfo);更新内部的name,这里createNamedValueInfo是一个抽象方法,通过子类去实现,如PathVariableMethodArgumentResolver,RequestParamMethodArgumentResolver等等。这里我们主要看一下RequestParamMethodArgumentResolver中的createNamedValueInfo方法。和
首先,spring先获取了我们家在参数上的RequestParam的注解,如果为空的话,就调用RequestParamNamedValueInfo的无参构造,否则通过annotation构建RequestParamNamedValueInfo。默认的值为value="" , require = false , DEFAULT_NONE = "\n\t\t\n\t\t\n\uE000\uE001\uE002\n\t\t\t\t\n"。
下面来看一下updateNamedValueInfo的方法,
首先先获取了info里面的name,如果name非空的话,name=parameter.getParameterName(); , 在getParameterName方法中获取到了参数的变量名。
可以看到根据当前的method是否为空来判断是根据method获取参数名还是根据构造器获取参数名。我们现在这里是通过LocalVariableTableParameterNameDiscoverer中的getParameterNames方法来获取参数名字,返回的是一个参数名字的数组,然后根据当前的索引值parameterIndex来确定返回的参数是哪一个,下面来看下一内部的具体实现。
首先先获取当前method的作用的class,先在缓存中看看当前class有没有,(注意缓存的结构,Map<Class<?>, Map<Member, String[]>> key是class , value是Method 和 方法参数的对应关系)。如果有的话直接返回,否则把当前class传入inspectClass方法获取方法的参数值。
这里spring通过类加载器获取到这个类的inputStream,最后通过了ClassReader获取到了类中的所有方法和方法参数,依赖于asm的jar包。ASM是一个java字节码操纵框架,它能被用来动态生成类或者增强既有类的功能。ASM 可以直接产生二进制 class 文件,也可以在类被加载入 Java 虚拟机之前动态改变类行为。Java class 被存储在严格格式定义的 .class文件里,这些类文件拥有足够的元数据来解析类中的所有元素:类名称、方法、属性以及 Java 字节码(指令)。ASM从类文件中读入信息后,能够改变类行为,分析类信息,甚至能够根据用户要求生成新类。具体请看
http://www.cnblogs.com/liuling/archive/2013/05/25/asm.html
看到这里才知道,原来spring不是通过反射来获取到的,而是通过asm jar包来解析class的字节获取到了方法的参数名字,用来注入到方法中。
下面是本人看完spring源码的一点心得
1.spring 在很多地方使用了缓存Map,用于提高性能,我们再编写代码的时候是否也可以做到呢?
2.java本来是反射是不支持获取到方法参数的变量名的,spring为了提供更好的服务,通过解析class的字节码,来完成这项功能,那我们在编写代码的时候是否可以做到呢?
1.spring是如何回调我们的controller中定义的方法的,request response model 是怎么来的?为什么先后顺序可以颠倒?
2.spring是如何帮我们注入基本类型的参数的,比如我参数中有两个String,但是参数名字不一样,一个是id,一个是type,但是spring可以直接帮我们注入进来,不需要添加任何注解,他是怎么实现的?
抱着这两个问题,我开始看spring是如何处理请求的。
首先,spring是为我们封装了servlet,所以肯定会有一个类继承HttpServlet,从这个入口开始寻找,我们就找到了DispatcherServlet ,他是HttpServlet的一个实现类。其中重写了父类的doService方法。看到方法最后有调用doDispatch(request, response);方法,到了spring的处理器。
/** * Process the actual dispatching to the handler. * <p>The handler will be obtained by applying the servlet's HandlerMappings in order. * The HandlerAdapter will be obtained by querying the servlet's installed HandlerAdapters * to find the first that supports the handler class. * <p>All HTTP methods are handled by this method. It's up to HandlerAdapters or handlers * themselves to decide which methods are acceptable. * @param request current HTTP request * @param response current HTTP response * @throws Exception in case of any kind of processing failure */ protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception { HttpServletRequest processedRequest = request; HandlerExecutionChain mappedHandler = null; int interceptorIndex = -1; try { ModelAndView mv; boolean errorView = false; try { processedRequest = checkMultipart(request); // Determine handler for the current request. mappedHandler = getHandler(processedRequest, false); if (mappedHandler == null || mappedHandler.getHandler() == null) { noHandlerFound(processedRequest, response); return; } // Determine handler adapter for the current request. HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler()); // Process last-modified header, if supported by the handler. String method = request.getMethod(); boolean isGet = "GET".equals(method); if (isGet || "HEAD".equals(method)) { long lastModified = ha.getLastModified(request, mappedHandler.getHandler()); if (logger.isDebugEnabled()) { String requestUri = urlPathHelper.getRequestUri(request); logger.debug("Last-Modified value for [" + requestUri + "] is: " + lastModified); } if (new ServletWebRequest(request, response).checkNotModified(lastModified) && isGet) { return; } } // Apply preHandle methods of registered interceptors. HandlerInterceptor[] interceptors = mappedHandler.getInterceptors(); if (interceptors != null) { for (int i = 0; i < interceptors.length; i++) { HandlerInterceptor interceptor = interceptors[i]; if (!interceptor.preHandle(processedRequest, response, mappedHandler.getHandler())) { triggerAfterCompletion(mappedHandler, interceptorIndex, processedRequest, response, null); return; } interceptorIndex = i; } } // Actually invoke the handler. mv = ha.handle(processedRequest, response, mappedHandler.getHandler()); // Do we need view name translation? if (mv != null && !mv.hasView()) { mv.setViewName(getDefaultViewName(request)); } // Apply postHandle methods of registered interceptors. if (interceptors != null) { for (int i = interceptors.length - 1; i >= 0; i--) { HandlerInterceptor interceptor = interceptors[i]; interceptor.postHandle(processedRequest, response, mappedHandler.getHandler(), mv); } } }catch (ModelAndViewDefiningException ex) { logger.debug("ModelAndViewDefiningException encountered", ex); mv = ex.getModelAndView(); }catch (Exception ex) { Object handler = (mappedHandler != null ? mappedHandler.getHandler() : null); mv = processHandlerException(processedRequest, response, handler, ex); errorView = (mv != null); } // Did the handler return a view to render? if (mv != null && !mv.wasCleared()) { render(mv, processedRequest, response); if (errorView) { WebUtils.clearErrorRequestAttributes(request); } } else { if (logger.isDebugEnabled()) { logger.debug("Null ModelAndView returned to DispatcherServlet with name '" + getServletName() + "': assuming HandlerAdapter completed request handling"); } } // Trigger after-completion for successful outcome. triggerAfterCompletion(mappedHandler, interceptorIndex, processedRequest, response, null); }catch (Exception ex) { // Trigger after-completion for thrown exception. triggerAfterCompletion(mappedHandler, interceptorIndex, processedRequest, response, ex); throw ex; }catch (Error err) { ServletException ex = new NestedServletException("Handler processing failed", err); // Trigger after-completion for thrown exception. triggerAfterCompletion(mappedHandler, interceptorIndex, processedRequest, response, ex); throw ex; }finally { // Clean up any resources used by a multipart request. if (processedRequest != request) { cleanupMultipart(processedRequest); } } }
在这个方法中做了如下几件事情
1.寻找URL所对应的mappedHandler处理器,如果找不到的话,执行noHandlerFound方法,其中就是会返回404。
2.如果是get请求的话,并且HTTP请求头标签中包含If-Modified-Since,在发送HTTP请求时,把浏览器端缓存页面的最后修改时间一起发到服务器去,服务器会把这个时间与服务器上实际文件的最后修改时间进行比较。如果时间一致,那么返回HTTP状态码304(不返回文件内容),客户端接到之后,就直接把本地缓存文件显示到浏览器中。
3.获取到自己定义的拦截器,循环invoke拦截器。
4.回调我们所定义的mapperHandler 也就是我们再controller中所定义的处理这个请求的方法。
5.获取到返回的 MV 通过response写出。
下面主要来看下第四部,spring是怎么回调我们自己定义的controller的。
mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
这里ha是HandlerAdapter 接口,根据spring的配置文件,走不同的处理器。
<bean class="org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter"> <property name="webBindingInitializer" ref="webBindingInitializer"/> <property name="requireSession" value="false"/> <property name="order" value="0"/> </bean>
如上配置可以看到,配置的处理器为RequestMappingHandlerAdapter。他实现了父类的handler方法,然后调用子类的handleInternal方法处理请求。
/** * {@inheritDoc} <p>This implementation expects the handler to be an {@link HandlerMethod}. */ public final ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler) hrows Exception { return handleInternal(request, response, (HandlerMethod) handler); }
@Override protected final ModelAndView handleInternal(HttpServletRequest request, HttpServletResponse response,HandlerMethod handlerMethod) throws Exception { if (getSessionAttributesHandler(handlerMethod).hasSessionAttributes()){ // Always prevent caching in case of session attribute management. checkAndPrepare(request, response, this.cacheSecondsForSessionAttributeHandlers, true); } else { // Uses configured default cacheSeconds setting. checkAndPrepare(request, response, true); } // Execute invokeHandlerMethod in synchronized block if required. if (this.synchronizeOnSession) { HttpSession session = request.getSession(false); if (session != null) { Object mutex = WebUtils.getSessionMutex(session); synchronized (mutex) { return invokeHandlerMethod(request, response, handlerMethod); } } } return invokeHandlerMethod(request, response, handlerMethod); }
RequestMappingHandlerAdapter中实现了父类的handleInternal,在这个类里面主要做了一些限制判断。
1.如HTTP请求方式和方法上限制了HTTP请求不同的话,则会抛出HttpRequestMethodNotSupportedException。
2.如果在配置文件中配置了requireSession=true的话,此处会抛出 HttpSessionRequiredException("Pre-existing session required but none found");
现在回到我们handleInternal方法中,接下去如果我们配置了synchronizeOnSession配置,表示该控制器是否在执行时同步session,从而保证该会话的用户串行访问该控制器。接下去执行invokeHandlerMethod方法。
/** * Invoke the {@link RequestMapping} handler method preparing a {@link ModelAndView} if view resolution is required. */ private ModelAndView invokeHandlerMethod(HttpServletRequest request, HttpServletResponse response, HandlerMethod handlerMethod) throws Exception { ServletWebRequest webRequest = new ServletWebRequest(request, response); WebDataBinderFactory binderFactory = getDataBinderFactory(handlerMethod); ModelFactory modelFactory = getModelFactory(handlerMethod, binderFactory); ServletInvocableHandlerMethod requestMappingMethod = createRequestMappingMethod(handlerMethod, binderFactory); ModelAndViewContainer mavContainer = new ModelAndViewContainer(); mavContainer.addAllAttributes(RequestContextUtils.getInputFlashMap(request)); modelFactory.initModel(webRequest, mavContainer, requestMappingMethod); mavContainer.setIgnoreDefaultModelOnRedirect (this.ignoreDefaultModelOnRedirect); requestMappingMethod.invokeAndHandle(webRequest, mavContainer); modelFactory.updateModel(webRequest, mavContainer); if (mavContainer.isRequestHandled()) { return null; }else { ModelMap model = mavContainer.getModel(); ModelAndView mav = new ModelAndView(mavContainer.getViewName(), model); if (!mavContainer.isViewReference()) { mav.setView((View) mavContainer.getView()); } if (model instanceof RedirectAttributes) { Map<String, ?> flashAttributes = ((RedirectAttributes) model).getFlashAttributes(); RequestContextUtils.getOutputFlashMap(request). putAll(flashAttributes); } return mav; } }
这里主要看一下 requestMappingMethod.invokeAndHandle(webRequest, mavContainer); 中的invokeForRequest方法。
public final Object invokeForRequest(NativeWebRequest request, ModelAndViewContainer mavContainer, Object... providedArgs) throws Exception { Object[] args = getMethodArgumentValues(request, mavContainer, providedArgs); if (logger.isTraceEnabled()) { StringBuilder builder = new StringBuilder("Invoking ["); builder.append(this.getMethod().getName()).append("] method with arguments "); builder.append(Arrays.asList(args)); logger.trace(builder.toString()); } Object returnValue = invoke(args); if (logger.isTraceEnabled()) { logger.trace("Method [" + this.getMethod().getName() + "] returned [" + returnValue + "]"); } return returnValue; }
这个方法中,主要做两件事情,
1.获取到了本次请求所对应执行方法的参数。
2.通过反射invoke 调用方法。注入参数
这里也是让我一开始很困惑的地方,因为反射只能获取到方法参数的类型,无法获取到方法参数的变量名,那spring是如何做到获取到方法参数变量名呢?我们继续往下面看。
看到spring通过getMethodArgumentValues方法获取了所有的参数列表。
/** * 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(parameterNameDiscoverer); GenericTypeResolver.resolveParameterType(parameter, getBean().getClass()); args[i] = resolveProvidedArgument(parameter, providedArgs); if (args[i] != null) { continue; } if (argumentResolvers.supportsParameter(parameter)) { try { args[i] = argumentResolvers.resolveArgument(parameter, mavContainer, request, dataBinderFactory); continue; } catch (Exception ex) { if (logger.isTraceEnabled()) { logger.trace(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; }
其中resolveArgument的方法中HandlerMethodArgumentResolver是一个接口,通过 getArgumentResolver(parameter); 方法获取到不同的解析器。来解析参数值。
HandlerMethodArgumentResolver接口中定义了resolveArgument让子类来实现方法,获取到具体的参数,在@RequestParam注解的参数的解析器是使用的AbstractNamedValueMethodArgumentResolver类来实现的。我们具体来看一下AbstractNamedValueMethodArgumentResolver类中的实现。
public final Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer, NativeWebRequest webRequest, WebDataBinderFactory binderFactory)throws Exception { Class<?> paramType = parameter.getParameterType(); NamedValueInfo namedValueInfo = getNamedValueInfo(parameter); Object arg = resolveName(namedValueInfo.name, parameter, webRequest); if (arg == null) { if (namedValueInfo.defaultValue != null) { arg = resolveDefaultValue(namedValueInfo.defaultValue); } else if (namedValueInfo.required) { handleMissingValue(namedValueInfo.name, parameter); } arg = handleNullValue(namedValueInfo.name, arg, paramType); } if (binderFactory != null) { WebDataBinder binder = binderFactory.createBinder(webRequest, null, namedValueInfo.name); arg = binder.convertIfNecessary(arg, paramType, parameter); } handleResolvedValue(arg, namedValueInfo.name, parameter, mavContainer, webRequest); return arg; }
看到如上代码中,首先获取到了某个参数的类型,再通过getNamedValueInfo(parameter);方法返回了NamedValueInfo对象,在NamedValueInfo对象中,就已经包含了当前参数的名字是什么,那就继续看getNamedValueInfo方法。
/** * Obtain the named value for the given method parameter. */ private NamedValueInfo getNamedValueInfo(MethodParameter parameter) { NamedValueInfo namedValueInfo = this.namedValueInfoCache.get(parameter); if (namedValueInfo == null) { namedValueInfo = createNamedValueInfo(parameter); namedValueInfo = updateNamedValueInfo(parameter, namedValueInfo); this.namedValueInfoCache.put(parameter, namedValueInfo); } return namedValueInfo; }
spring代码中写了一个cache用于保存已经解析过的nameValueInfo,因为在程序运行期间,这个值肯定是固定的。
如果cache中返回空的话,则会通过createNamedValueInfo(parameter);方法创建一个nameValueInfo,再通过updateNamedValueInfo(parameter, namedValueInfo);更新内部的name,这里createNamedValueInfo是一个抽象方法,通过子类去实现,如PathVariableMethodArgumentResolver,RequestParamMethodArgumentResolver等等。这里我们主要看一下RequestParamMethodArgumentResolver中的createNamedValueInfo方法。和
@Override protected NamedValueInfo createNamedValueInfo(MethodParameter parameter) { RequestParam annotation = parameter.getParameterAnnotation(RequestParam.class); return (annotation != null) ? new RequestParamNamedValueInfo(annotation) : new RequestParamNamedValueInfo(); }
首先,spring先获取了我们家在参数上的RequestParam的注解,如果为空的话,就调用RequestParamNamedValueInfo的无参构造,否则通过annotation构建RequestParamNamedValueInfo。默认的值为value="" , require = false , DEFAULT_NONE = "\n\t\t\n\t\t\n\uE000\uE001\uE002\n\t\t\t\t\n"。
下面来看一下updateNamedValueInfo的方法,
/** * Create a new NamedValueInfo based on the given NamedValueInfo with sanitized values. */ private NamedValueInfo updateNamedValueInfo(MethodParameter parameter, NamedValueInfo info) { String name = info.name; if (info.name.length() == 0) { name = parameter.getParameterName(); Assert.notNull(name, "Name for argument type [" + parameter.getParameterType().getName() + "] not available, and parameter name information not found in class file either."); } String defaultValue = (ValueConstants.DEFAULT_NONE.equals(info.defaultValue) ? null : info.defaultValue); return new NamedValueInfo(name, info.required, defaultValue); }
首先先获取了info里面的name,如果name非空的话,name=parameter.getParameterName(); , 在getParameterName方法中获取到了参数的变量名。
public String getParameterName() { if (this.parameterNameDiscoverer != null) { String[] parameterNames = (this.method != null ? this.parameterNameDiscoverer.getParameterNames(this.method) : this.parameterNameDiscoverer.getParameterNames(this.constructor)); if (parameterNames != null) { this.parameterName = parameterNames[this.parameterIndex]; } this.parameterNameDiscoverer = null; } return this.parameterName; }
可以看到根据当前的method是否为空来判断是根据method获取参数名还是根据构造器获取参数名。我们现在这里是通过LocalVariableTableParameterNameDiscoverer中的getParameterNames方法来获取参数名字,返回的是一个参数名字的数组,然后根据当前的索引值parameterIndex来确定返回的参数是哪一个,下面来看下一内部的具体实现。
public String[] getParameterNames(Method method) { Class<?> declaringClass = method.getDeclaringClass(); Map<Member, String[]> map = this.parameterNamesCache.get(declaringClass); if (map == null) { // initialize cache map = inspectClass(declaringClass); this.parameterNamesCache.put(declaringClass, map); } if (map != NO_DEBUG_INFO_MAP) { return map.get(method); } return null; }
首先先获取当前method的作用的class,先在缓存中看看当前class有没有,(注意缓存的结构,Map<Class<?>, Map<Member, String[]>> key是class , value是Method 和 方法参数的对应关系)。如果有的话直接返回,否则把当前class传入inspectClass方法获取方法的参数值。
/** * Inspects the target class. Exceptions will be logged and a maker map returned * to indicate the lack of debug information. */ private Map<Member, String[]> inspectClass(Class<?> clazz) { InputStream is = clazz.getResourceAsStream(ClassUtils.getClassFileName(clazz)); if (is == null) { // We couldn't load the class file, which is not fatal as it // simply means this method of discovering parameter names won't work. if (logger.isDebugEnabled()) { logger.debug("Cannot find '.class' file for class [" + clazz + "] - unable to determine constructors/methods parameter names"); } return NO_DEBUG_INFO_MAP; } try { ClassReader classReader = new ClassReader(is); Map<Member, String[]> map = new ConcurrentHashMap<Member, String[]>(); classReader.accept(new ParameterNameDiscoveringVisitor(clazz, map), false); return map; } catch (IOException ex) { if (logger.isDebugEnabled()) { logger.debug("Exception thrown while reading '.class' file for class [" + clazz + "] - unable to determine constructors/methods parameter names", ex); } } finally { try { is.close(); } catch (IOException ex) { // ignore } } return NO_DEBUG_INFO_MAP; }
这里spring通过类加载器获取到这个类的inputStream,最后通过了ClassReader获取到了类中的所有方法和方法参数,依赖于asm的jar包。ASM是一个java字节码操纵框架,它能被用来动态生成类或者增强既有类的功能。ASM 可以直接产生二进制 class 文件,也可以在类被加载入 Java 虚拟机之前动态改变类行为。Java class 被存储在严格格式定义的 .class文件里,这些类文件拥有足够的元数据来解析类中的所有元素:类名称、方法、属性以及 Java 字节码(指令)。ASM从类文件中读入信息后,能够改变类行为,分析类信息,甚至能够根据用户要求生成新类。具体请看
http://www.cnblogs.com/liuling/archive/2013/05/25/asm.html
看到这里才知道,原来spring不是通过反射来获取到的,而是通过asm jar包来解析class的字节获取到了方法的参数名字,用来注入到方法中。
下面是本人看完spring源码的一点心得
1.spring 在很多地方使用了缓存Map,用于提高性能,我们再编写代码的时候是否也可以做到呢?
2.java本来是反射是不支持获取到方法参数的变量名的,spring为了提供更好的服务,通过解析class的字节码,来完成这项功能,那我们在编写代码的时候是否可以做到呢?
评论
2 楼
zhangdong92
2017-09-14
终于找到这个我很疑惑的问题的答案了。之前也遇到过想在一个方法里想要获取传入的参数的名称的情况,但是代码中包括反射都拿不到这个方法名。所以很奇怪Spring的@RequestParam是怎么获取到我们定义的变量名的。据我了解,可能拿到参数名信息的,只有编译时-g参数写入的信息。(刚知道jdk1.8也可以用-parameter参数记录参数名信息)
搜到了这篇才知道,原来spring就是用这种方法通过asm读取class字节码取到参数名的。不过实现起来还是挺复杂的。org.springframework.core.LocalVariableTableParameterNameDiscoverer的inspectClass方法中,用classReader.accept(...)读取class文件字节码,解析方法时调用org.springframework.core.LocalVariableTableParameterNameDiscoverer.LocalVariableTableParameterNameDiscoverer的visitMethod方法,desc参数中包含了方法参数信息,包括参数名。
所以在jdk1.8之前,如果编译没有-g参数的话,根据源码,估计spring会抛出异常了:
org.springframework.web.method.annotation.AbstractNamedValueMethodArgumentResolver#updateNamedValueInfo():
throw new IllegalArgumentException(
"Name for argument type [" + parameter.getNestedParameterType().getName() +
"] not available, and parameter name information not found in class file either.");
还学到了一个神奇的新用法,返回的map对象,如果想传递一个额外的错误信息,可以在拿到返回值后用==NO_DEBUG_INFO_MAP判断。
搜到了这篇才知道,原来spring就是用这种方法通过asm读取class字节码取到参数名的。不过实现起来还是挺复杂的。org.springframework.core.LocalVariableTableParameterNameDiscoverer的inspectClass方法中,用classReader.accept(...)读取class文件字节码,解析方法时调用org.springframework.core.LocalVariableTableParameterNameDiscoverer.LocalVariableTableParameterNameDiscoverer的visitMethod方法,desc参数中包含了方法参数信息,包括参数名。
所以在jdk1.8之前,如果编译没有-g参数的话,根据源码,估计spring会抛出异常了:
org.springframework.web.method.annotation.AbstractNamedValueMethodArgumentResolver#updateNamedValueInfo():
throw new IllegalArgumentException(
"Name for argument type [" + parameter.getNestedParameterType().getName() +
"] not available, and parameter name information not found in class file either.");
还学到了一个神奇的新用法,返回的map对象,如果想传递一个额外的错误信息,可以在拿到返回值后用==NO_DEBUG_INFO_MAP判断。
1 楼
krdsg
2014-02-27
楼主牛逼啊 刨根问底 受教了
发表评论
-
【java】论integer是地址传递还是值传递
2014-12-01 16:53 1819论integer是地址传递还是 ... -
【mybatis】多次查询缓存的问题
2014-02-25 15:17 4852最近在使用mybatis的过程中,发现一个问题。如果在 ... -
【spring】@async原理
2014-02-22 19:27 6078在我们使用spring框架的过程中,在很多时候我们会使 ... -
【spring】Placeholder的坑
2014-01-27 15:22 1204今天自己在搭建项目的时候,抛出了如下异常。 org.sprin ... -
【mybatis】IF判断的坑
2014-01-22 14:06 50577最近在项目使用mybatis中碰到个问题 <if t ... -
【坑】自己山寨缓存出现的坑自己踩
2014-01-18 09:12 689就在前两天,在编写一段业务逻辑的时候,考虑到数据库中的 ... -
【代码规范】我所理解的代码规范
2014-01-13 21:48 886本人是一名从事java开发2年的小菜,常用MVC框架为 ...
相关推荐
Springboot请求处理源码分析(含源代码详细注释)
总结:首先,SpringMVC框架在启动的时候会遍历Spring容器中的所有bean,对标注了@Controller或并,使用@RequestMapping注解
视频详细讲解,需要的小伙伴自行百度网盘下载,链接见附件,永久有效。 1、课程简介 Spring框架是一系列应用框架的核心,也可以说是整合其他应用框架的基座。...n 源码分析-TransactionSynchronizationManager
第3篇是Spring MVC技术入门,包括Spring MVC的背景介绍、架构整体剖析、环境搭建、处理器与映射器的讲解、前端控制器的源码分析、多种视图解析器的介绍、请求映射与参数绑定的介绍、Validation校验与异常处理和拦截...
负载均衡器源码分析 33 负载均衡器重试机制 33 服务保护机制SpringCloud Hystrix 33 微服务高可用技术 33 服务降级、熔断、限流概念 34 服务学崩效应 34 服务降级 34 服务熔断 35 服务隔离 35 服务限流 36 ...
Spring的源码分析 在分析SpringMVC源码之前我想先回顾一下JavaWeb的知识.JavaWeb的核心是Servlet,一个Servlet对应一个URL, 每次一个Http请求访问,那么对应URL的Servlet就会调用service方法处理。 其实这里我是对...
该思维导入详细描述了SpringMVC的处理request请求的流程,每个流程都描述了方法入口以及具体业务处理步骤,适合spring源码入门新手的参考手册,指引读者一步步阅读源码。
这个名为“图书管理系统(struts+hibernate+spring).rar”的压缩文件,是一个针对计算机专业的JSP源码资料包,它集成了Struts、...通过阅读和分析这些源码,用户可以学习到如何使用Struts处理用户请求和响应,如
Struts2——请求响应 Spring——java对象注入 Spring JDBC——数据库连接 Jquery UI——界面设计 JFreeChart——成绩分析 iText——成绩打印PDF格式 Ajax+Json——表单验证 系统登录用户: 学生用户(学号+密码+...
spring mvc的版本是5.x的,sping mvc的入口是 DispatcherServlet 类 ,在之前的ssm项目中,我们是通过配置在web.xml中的servlet把请求的入口设置为DispatcherServlet,现在的版本官方不提倡编写web.xml,而是如下: ...
关于spring中的注解,采用和spring一样的注解名字,在springMVC方面,采用统一的前端控制器dispatcherServlet,处理统一请求进行分发。 对应 mybatis 是采用代理的方式 对接口进行生成对应的代理对象,加载对应的xml...
* 订单处理:餐厅可以查看、接受或拒绝顾客的订单请求,并实时跟踪订单状态。 * 顾客反馈:餐厅可以查看顾客的评价和反馈,以便改进服务质量。 3. **管理员模块:** * 餐厅审核:管理员可以审核新注册的餐厅,...
Spring MVC教程 教程学习: 本教程完整地讲解了Spring MVC的每个知识点,包括...Spring MVC 核心源码分析 @RequestMapping注解 Spring MVC请求参数封装 Spring MVC 基本类型封装 Spring MVC Post中文乱码 @RequestPara
基于springcloud+Netty+MQ+mysql的分布式即时聊天系统源码+数据库+项目说明.zip # KT-Chat 分布式即时聊天系统 **技术选型**:Java、SpringCloud、Nacos、Sentinel、Netty、MySQL、Redis、RocketMQ 等 **项目描述**...
Spring全家桶源码分析 Tomcat架构原理 Web请求处理原理 数据访问层框架原理 架构与设计思维模式 程序中的数学 数据分析 机器智能算法剖析与应用 云原生 自动化DevOps 流量治理 链路监控 弹性扩容 分布式存储Redis6.0...
java进阶源码分析专题常用设计模式线程与并发锁的使用深度理解synchronized、volatile、cas手写ASQSpring5IOC容器设计原理及高级特性AOP设计原理FactoryBean与BeanFactorySpring事务处理机制Spring JDK动态代理...
4、异步、异步、异步,分析并识别出可以异步处理的逻辑,比如日志,缩短系统响应时间。 5、主备、主备、主备,如果有条件做好主备容灾方案也是非常有必要的(参考某年锤子的活动被攻击)。 6、最后,为了支撑更高的...
1.2 基于Feign的服务调用 1.4 负载均衡 2.2 请求压缩 2.3 日志级别 2.4 源码分析
第三部分是Spring MVC技术入门,包括Spring MVC的背景介绍、架构整体剖析、环境搭建、处理器与映射器的讲解、前端控制器的源码分析、多种视图解析器的介绍、请求映射与参数绑定的介绍、Validation校验与异常处理和...
Spring Cloud Ribbon是一个基于HTTP和TCP的客户端负载均衡工具,它基于Netflix ... 在这一章中,我们将具体介绍如何使用Ribbon来实现客户端的负载均衡,并且通过源码分析来了解Ribbon实现客户端负载均衡的基本原理。