- 浏览: 79668 次
- 性别:
- 来自: 上海
-
最新评论
-
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 1872论integer是地址传递还是 ... -
【mybatis】多次查询缓存的问题
2014-02-25 15:17 4905最近在使用mybatis的过程中,发现一个问题。如果在 ... -
【spring】@async原理
2014-02-22 19:27 6173在我们使用spring框架的过程中,在很多时候我们会使 ... -
【spring】Placeholder的坑
2014-01-27 15:22 1242今天自己在搭建项目的时候,抛出了如下异常。 org.sprin ... -
【mybatis】IF判断的坑
2014-01-22 14:06 50702最近在项目使用mybatis中碰到个问题 <if t ... -
【坑】自己山寨缓存出现的坑自己踩
2014-01-18 09:12 734就在前两天,在编写一段业务逻辑的时候,考虑到数据库中的 ... -
【代码规范】我所理解的代码规范
2014-01-13 21:48 923本人是一名从事java开发2年的小菜,常用MVC框架为 ...
相关推荐
Spring 源代码分析系列涵盖了多个关键模块,包括事务处理、IoC容器、JDBC、MVC、AOP以及与Hibernate和Acegi安全框架的集成。以下是对这些知识点的详细阐述: 1. **Spring 事务处理**:Spring 提供了声明式事务管理...
Spring 源码分析 Spring 框架是 Java 语言中最流行的开源框架之一,它提供了一个强大且灵活的基础设施来构建企业级应用程序。在 Spring 框架中,IOC 容器扮演着核心角色,本文将深入分析 Spring 源码,了解 IOC ...
SpringBoot是一个基于Spring框架的高度模块化和...以上就是SpringBoot请求处理的源码分析,涉及的主要组件和它们的角色。理解这些组件的工作原理有助于开发者深入理解SpringBoot的请求处理流程,提高开发和调试的效率。
源码分析会涉及SpringBoot的启动流程,包括`SpringApplication.run()`方法的执行细节,自动配置的实现机制,以及如何通过条件注解选择合适的bean。理解这些将帮助你自定义启动配置,优化项目结构。 接着,进入...
《Spring5 源码分析(第 2 版)》是针对Spring框架第五个主要版本的深度解析著作,由知名讲师倾心打造,旨在帮助读者深入理解Spring框架的内部工作机制,提升对Java企业级应用开发的专业技能。本书涵盖了Spring框架的...
《Spring源码分析》这份资料深入探讨了Spring框架的核心机制,尤其聚焦于Spring5版本。Spring作为Java领域中最重要的轻量级应用框架之一,它的设计理念、实现方式以及工作原理对于任何想成为优秀Java开发者的人都至...
学习Spring源码有助于深入理解其内部工作原理,例如bean的生命周期管理、AOP的实现、以及MVC的请求处理流程。这将有助于开发者更高效地利用Spring框架,编写出高质量、高性能的Java应用。通过分析源码,开发者还可以...
在源码分析的过程中,读者会深入理解Spring的内部工作机制,例如如何解析配置、如何创建bean实例、如何实现AOP代理等。这将有助于开发者编写更高效、更健壮的代码,也能为参与Spring的扩展或定制打下坚实基础。 总...
通过阅读和分析这些源代码,开发者可以学习到Spring如何实现依赖注入、AOP代理、事件传播、异常处理、国际化和本地化、数据绑定、类型转换等机制。同时,测试代码可以帮助理解Spring如何测试自己的组件,这对于编写...
你可以通过分析这些文件,了解如何在实际项目中应用Spring 3.2的异步处理特性。 通过上述讲解,我们已经对Spring 3.2中的异步处理有了深入理解。正确地使用异步处理可以显著提高应用的性能,特别是在处理大量并发...
通过对这些模块的源码分析,我们可以深入了解Spring如何实现其强大的功能,并能更好地运用到实际项目中,提升代码质量和可维护性。无论是新手还是经验丰富的开发者,理解Spring的源码都将是一次宝贵的进阶之旅。
4. Zuul或Spring Cloud Gateway源码:了解它们如何处理请求转发,实现API路由和过滤器,以及安全策略的集成。 四、实战应用 1. 分析配置中心Config Server和Client的交互过程,理解如何实现配置的实时更新。 2. ...
以上只是Spring源码分析的部分内容,实际源码中还包括Spring的其他模块,如Spring Batch(批处理)、Spring Security(安全)、Spring Integration(集成)等。理解并掌握Spring源码,有助于我们更好地利用Spring...
通过阅读和分析Spring 1.2.6的源码,不仅可以学习到Spring的核心设计原则,还能了解到设计模式的运用,例如工厂模式、单例模式、观察者模式等。同时,这也是提升Java编程技巧和理解框架底层运作的好机会。在实际的...
总的来说,Spring MVC的DispatcherServlet通过一系列步骤,从初始化时的bean扫描到请求到达时的分发处理,确保了请求能够准确地传递到正确的Controller方法。这一过程涉及到bean的扫描、请求映射的建立、拦截器的...
7. **源码分析** 深入源码层面,我们可以看到DispatcherServlet是如何通过HandlerExecutionChain找到处理请求的方法,以及HandlerAdapter是如何执行这个方法的。这对于理解Spring MVC的工作原理和优化性能非常重要...
总结,Spring Security 3的源码分析是一个深度学习的过程,涵盖了安全领域的多个方面。通过理解其内部工作机制,开发者可以更好地利用这一强大的框架,为应用程序提供安全的保障。同时,源码分析也能帮助开发者解决...
《Spring源码分析》 Spring框架作为Java领域中不可或缺的一部分,其强大之处在于它提供了丰富的功能,包括依赖注入(Dependency Injection,简称DI)、面向切面编程(Aspect-Oriented Programming,简称AOP)、事务...
本篇文章将针对“Tom_深入分析Spring源码doc”中的关键知识点进行详细的阐述。 1. **依赖注入(Dependency Injection,DI)** Spring的核心特性之一就是依赖注入,它使得组件间的依赖关系由Spring容器管理,而不是...
Spring源码分析不仅仅是对具体实现的探究,更是对设计模式和软件工程原则的深入理解。通过对Spring源码的学习,开发者可以提升自己的设计能力,更好地理解和应用这些模式于实际项目中,提高代码质量和可维护性。因此...