dSpring源码版本 5.3.5
总体流程:
1.Spring容器初始化时,保存RequestMapping请求路径与方法的映射
2.根据Spring初始化的映射信息,找到Http请求路径对应的Controller内部的方法
3.调用方法前,使用参数解析器获取方法的参数名称
1 容器初始化阶段的主要操作
1.1 从Spring启动入口进入AbstractApplicationContext
类的refresh()
方法
// Instantiate all remaining (non-lazy-init) singletons.
初始化所有非懒加载的单例bean
finishBeanFactoryInitialization(beanFactory);
beanFactory.preInstantiateSingletons();
1.2 DefaultListableBeanFactory
的preInstantiateSingletons()
方法遍历所有beanName进行实例化
public void preInstantiateSingletons(){
List<String> beanNames = new ArrayList<>(this.beanDefinitionNames);
for(beanName:beanNames) {
if(非抽象&&单例&&非懒加载){
if(FactoryBean) {
//FactoryBean实例化逻辑
} else{
getBean(beanName);
}
}
}
}
1.3 实例化requestMappingHandlerMapping之后的afterPropertiesSet()方法
RequestMappingHandlerMapping
实现了接口InitializingBean
,在设置完所有属性后会调用afterPropertiesSet()
方法
public void afterPropertiesSet() {
super.afterPropertiesSet();
}
执行父类AbstractHandlerMethodMapping
的afterPropertiesSet()
方法
public void afterPropertiesSet() {
initHandlerMethods();
}
protected void initHandlerMethods() {
for (String beanName : getCandidateBeanNames()) {
if (!beanName.startsWith(SCOPED_TARGET_NAME_PREFIX)) {
processCandidateBean(beanName);
}
}
handlerMethodsInitialized(getHandlerMethods());
}
processCandidateBean()
对Handler类型的bean处理
protected void processCandidateBean(String beanName) {
Class<?> beanType = null;
try {
beanType = obtainApplicationContext().getType(beanName);
} catch (Throwable ex) {
}
if (beanType != null && isHandler(beanType)) {
detectHandlerMethods(beanName);
}
}
isHandler()
判断bean是否有@Controller
注解或者@RequestMapping
注解
protected boolean isHandler(Class<?> beanType) {
return (AnnotatedElementUtils.hasAnnotation(beanType, Controller.class) ||
AnnotatedElementUtils.hasAnnotation(beanType, RequestMapping.class));
}
1.4 AbstractHandlerMethodMapping
的detectHandlerMethods()
构建了Map<Method,RequestMappigInfo>,然后注册到mappingRegistry
中,包含了请求路径与方法信息的映射
protected void detectHandlerMethods(Object handler) {
Class<?> handlerType = (handler instanceof String ?
obtainApplicationContext().getType((String) handler) : handler.getClass());
if (handlerType != null) {
Class<?> userType = ClassUtils.getUserClass(handlerType);
Map<Method, T> methods = MethodIntrospector.selectMethods(userType,
(MethodIntrospector.MetadataLookup<T>) method -> {
try {
//获取方法信息与请求路径的映射
return getMappingForMethod(method, userType);
}
catch (Throwable ex) {
throw new IllegalStateException("Invalid mapping on handler class [" +
userType.getName() + "]: " + method, ex);
}
});
methods.forEach((method, mapping) -> {
Method invocableMethod = AopUtils.selectInvocableMethod(method, userType);
//注册请求路径与方法信息的映射
registerHandlerMethod(handler, invocableMethod, mapping);
});
}
}
//注册请求路径与方法信息的映射
protected void registerHandlerMethod(Object handler, Method method, T mapping){
this.mappingRegistry.register(mapping, handler, method);
}
注册信息如下
{ [/goods/chart_data], produces [application/json]}
->
com.goblet.goods.controller.GoodsPurchaseInfoController#chartData(HttpServletRequest, String, String)
遍历完一个Controller方法后,其请求路径与方法信息的映射就已经放在Spring容器中了,可以查看一下现在Spring容器中是否已经存在这些注册信息
问题:如果不在Spring初始化过程中保存映射信息会怎么样?
答:如果不在Spring初始化过程中保存映射信息,对于每个请求:
1)Spring都要遍历所有bean,判断其是否是Handler类型的bean;
2)再遍历其中的方法,用反射获取其注解,判断是否含有@requestMapping
注解;
3)再判断该注解的路径是否与请求的路径一致
每个服务都有大量接口会被大量用户调用,每个服务的Spring容器中随随便便就有上百个bean,每个handler类型的bean(即Controller)有多个方法,如果走上面这个流程,性能无疑是极差的。
在Spring初始化过程中就保存好路径与方法的映射信息,可以大大提高服务运行过程中寻找请求所对应的方法的速度。
2 根据Spring初始化的映射信息,找到Http请求路径对应的Controller内部的方法
请求如何通过ip、端口找到tomcat,如何再转到Spring中可以参考下面两位大佬的文章(respect!):
分析http请求从浏览器到tomcat全过程
请求如何从Tomcat到Spring
这里主要关注Http请求到达Spring之后,Spring寻找对应方法的处理流程
2.1 DispatcherServlet
的protected void doDispatch(HttpServletRequest request, HttpServletResponse response)
为处理请求的主要方法
// 找到请求对应的方法
// Determine handler for the current request.
mappedHandler = getHandler(processedRequest);
// 实际调用handler方法
// Actually invoke the handler.
mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
继续向getHandler(processedRequest)
内部debug,可以看到:
protected HandlerMethod getHandlerInternal(HttpServletRequest request) throws Exception {
String lookupPath = initLookupPath(request);
this.mappingRegistry.acquireReadLock();
try {
HandlerMethod handlerMethod = lookupHandlerMethod(lookupPath, request);
return (handlerMethod != null ? handlerMethod.createWithResolvedBean() : null);
}
finally {
this.mappingRegistry.releaseReadLock();
}
}
2.2 getHandlerInternal()
方法中的lookupHandlerMethod()
方法
this.mappingRegistry
就是Spring初始化时存放映射信息的mappingRegistry
,根据当前请求路径就能从中获取到对应的方法。
protected HandlerMethod lookupHandlerMethod(String lookupPath, HttpServletRequest request) throws Exception {
List<Match> matches = new ArrayList<>();
List<T> directPathMatches = this.mappingRegistry.getMappingsByDirectPath(lookupPath);
if (directPathMatches != null) {
addMatchingMappings(directPathMatches, matches, request);
}
Match bestMatch = matches.get(0);
return bestMatch.getHandlerMethod();
}
3 调用方法前,使用参数解析器获取方法的参数名称
// 找到请求对应的方法
// Determine handler for the current request.
mappedHandler = getHandler(processedRequest);
// 实际调用handler方法
// Actually invoke the handler.
mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
3.1 RequestMappingHandlerAdapter
的handleInternal()
方法
protected ModelAndView handleInternal(HttpServletRequest request,
HttpServletResponse response, HandlerMethod handlerMethod) throws Exception {
ModelAndView mav;
mav = invokeHandlerMethod(request, response, handlerMethod);
return mav;
}
设置argumentResolvers
参数解析器
protected ModelAndView invokeHandlerMethod(HttpServletRequest request,
HttpServletResponse response, HandlerMethod handlerMethod) throws Exception {
ServletInvocableHandlerMethod invocableMethod = createInvocableHandlerMethod(handlerMethod);
if (this.argumentResolvers != null) {
invocableMethod.setHandlerMethodArgumentResolvers(this.argumentResolvers);
}
invocableMethod.invokeAndHandle(webRequest, mavContainer);
}
3.2 ServletInvocableHandlerMethod
的invokeAndHandle()
方法
public void invokeAndHandle(ServletWebRequest webRequest, ModelAndViewContainer mavContainer,
Object... providedArgs) throws Exception {
Object returnValue = invokeForRequest(webRequest, mavContainer, providedArgs);
}
public Object invokeForRequest(NativeWebRequest request, @Nullable ModelAndViewContainer mavContainer,
Object... providedArgs) throws Exception {
//获取方法参数
Object[] args = getMethodArgumentValues(request, mavContainer, providedArgs);
if (logger.isTraceEnabled()) {
logger.trace("Arguments: " + Arrays.toString(args));
}
return doInvoke(args);
}
遍历方法参数
protected Object[] getMethodArgumentValues(NativeWebRequest request, @Nullable 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];
//获取参数名称及其值
args[i] = this.resolvers.resolveArgument(parameter, mavContainer, request, this.dataBinderFactory);
}
return args;
}
3.4 AbstractNamedValueMethodArgumentResolver
的resolveArgument()
方法
public final Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer,
NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) throws Exception {
//获取参数名称
NamedValueInfo namedValueInfo = getNamedValueInfo(parameter);
//根据参数名称从请求信息中获取参数的值
Object arg = resolveName(resolvedName.toString(), nestedParameter, webRequest);
return arg;
}
获取参数名称
private NamedValueInfo getNamedValueInfo(MethodParameter parameter) {
//两个缓存对象,来自同一个类,useDefaultResolution为true或者false,分别处理有无注解的情况
NamedValueInfo namedValueInfo = this.namedValueInfoCache.get(parameter);
if (namedValueInfo == null) {
//根据@RequestParam注解获取参数名称
namedValueInfo = createNamedValueInfo(parameter);
//没有@RequestParam时获取参数名称
namedValueInfo = updateNamedValueInfo(parameter, namedValueInfo);
this.namedValueInfoCache.put(parameter, namedValueInfo);
}
return namedValueInfo;
}
两个RequestParamMethodArgumentResolver,useDefaultResolution属性为true或者false,分别处理有无RequestParam注解的情况
根据
@RequestParam
注解获取参数名称
protected NamedValueInfo createNamedValueInfo(MethodParameter parameter) {
RequestParam ann = parameter.getParameterAnnotation(RequestParam.class);
return (ann != null ? new RequestParamNamedValueInfo(ann) : new RequestParamNamedValueInfo());
}
没有@RequestParam
时获取参数名称
private NamedValueInfo updateNamedValueInfo(MethodParameter parameter, NamedValueInfo info) {
String name = info.name;
if (info.name.isEmpty()) {
//获取参数名称
name = parameter.getParameterName();
}
String defaultValue = (ValueConstants.DEFAULT_NONE.equals(info.defaultValue) ? null : info.defaultValue);
return new NamedValueInfo(name, info.required, defaultValue);
}
getParameterName()
public String getParameterName() {
ParameterNameDiscoverer discoverer = this.parameterNameDiscoverer;
if (discoverer != null) {
String[] parameterNames = null;
if (this.executable instanceof Method) {
//方法获取参数名称
parameterNames = discoverer.getParameterNames((Method) this.executable);
}
//返回当前index的参数名称
if (parameterNames != null) {
this.parameterName = parameterNames[this.parameterIndex];
}
this.parameterNameDiscoverer = null;
}
return this.parameterName;
}
3.5 参数解析器的getParameterNames()
方法
public String[] getParameterNames(Method method) {
for (ParameterNameDiscoverer pnd : this.parameterNameDiscoverers) {
String[] result = pnd.getParameterNames(method);
if (result != null) {
return result;
}
}
return null;
}
两个参数解析器,第一个解析不到就用第二个
StandardReflectionParameterNameDiscoverer
的getParameterNames()
public String[] getParameterNames(Method method) {
return getParameterNames(method.getParameters());
}
method.getParameters()
即为Executable
的getParameters()
public Parameter[] getParameters() {
// TODO: This may eventually need to be guarded by security
// mechanisms similar to those in Field, Method, etc.
//
// Need to copy the cached array to prevent users from messing
// with it. Since parameters are immutable, we can
// shallow-copy.
return privateGetParameters().clone();
}
Executable的privateGetParameters()
中的本地方法getParameters0()
获取到了参数名称
private native Parameter[] getParameters0()
private Parameter[] privateGetParameters() {
// Use tmp to avoid multiple writes to a volatile.
Parameter[] tmp = parameters;
if (tmp == null) {
// Otherwise, go to the JVM to get them
try {
tmp = getParameters0();
} catch(IllegalArgumentException e) {
// Rethrow ClassFormatErrors
throw new MalformedParametersException("Invalid constant pool index");
}
// If we get back nothing, then synthesize parameters
if (tmp == null) {
hasRealParameterData = false;
tmp = synthesizeAllParams();
} else {
hasRealParameterData = true;
verifyParameters(tmp);
}
parameters = tmp;
}
return tmp;
}
注意:
--Java8之前不支持反射获取参数名称,Java7的reflect包下面根本就没有Parameter
这个类,只能通过Method类的getParameterTypes()
获取参数类型。
--Java8才出现了Parameter
类,并且需要在javac编译时添加参数-parameters
才可以通过反射获取到真正的参数名称,否则获取到的是arg0、arg1这种形式。
--对于Java8之前的版本或者Java8未配置编译参数-parameters
时,Spring使用的是上面提到的另一个参数解析器LocalVariableTableParameterNameDiscoverer
,该类使用了ObjectWeb的ASM库获取参数名称
从下面的编译配置可以看到,编译参数中的确是存在-parameters
的
(不过我没有手动添加该参数,如果有同学知道这是怎么自动添加上去的可以评论分享一下^-^)
问题:为什么Spring不在初始化mappingRegistry的时候就把参数名称也保存进去呢?
有知道答案的同学可以分享一下❤❤❤