背景
女朋友被公司裁员一个月了,和我一样作为后端工程师,最近一直在找工作,面试了很多家还是没有找到工作,面试官问 @Controller 的原理,她表示一脸懵,希望我能给她讲清楚。之前我也没有好好整理这块知识,这次借助这个机会把它彻底搞清楚。
我们知道 Controller 注解的类能够实现接收并处理 Http 请求,其实在我看 Spring mvc 模块的源码之前也和我女朋友目前的状态一样,很疑惑,Spring 框架是底层是如何实现的,通过使用 Controller 注解就简单的完成了 http 请求的接收与处理。
有疑问就好啊,因为兴趣是最好的老师,如果有兴趣才有动力去弄懂这个技术点。
看过前面的文章的同学就会知道,学习 Spring 的所有组件,脑袋里要有一个思路,那就是解析组件和运用组件两个流程,这是 Spring 团队实现组件的统一套路,大家可以回忆一下是不是这么回事。
一、Spring 解析 Controller 注解
首先我们看看 Spring 是如何解析 Controller 注解的,打开源码看看他长啥样??
@Target({ElementType.TYPE})
@Component
public @interface Controller {
String value() default "";
}
复制代码
发现 Controller 注解打上了 Component 的注解,这样 Spring 做类扫描的时候,发现了 @Controller 标记的类也会当作 Bean 解析并注册到 Spring 容器。 我们可以看到 Spring 的类扫描器,第一个就注册了 Component 注解的扫描
//org.springframework.context.annotation.ClassPathScanningCandidateComponentProvider
protected void registerDefaultFilters() {
this.includeFilters.add(new AnnotationTypeFilter(Component.class));
}
复制代码
这样 Spring 容器启动完成之后,bean 容器中就有了被 Controller 注解标记的 bean 实例了。 到这里只是单纯的把 Controller 标注的类实例化注册到 Spring 容器,和 Http 请求接收处理没半毛钱关系,那么他们是怎么关联起来的呢?
二、Spring 解析 Controller 注解标注的类方法
这个时候 Springmvc 组件中的另外一个组件就闪亮登场了
RequestMappingHandlerMapping
RequestMappingHandlerMapping 看这个名就可以知道他的意思,请求映射处理映射器。 这里就是重点了,该类间接实现了 InitializingBean 方法,bean 初始化后执行回调 afterPropertiesSet 方法,里面调用 initHandlerMethods 方法进行初始化 handlermapping。
//类有没有加Controller的注解
protected boolean isHandler(Class<?> beanType) {
return (AnnotatedElementUtils.hasAnnotation(beanType, Controller.class) ||
AnnotatedElementUtils.hasAnnotation(beanType, RequestMapping.class));
}
protected void initHandlerMethods() {
//所有的bean
String[] beanNames= applicationContext().getBeanNamesForType(Object.class);
for (String beanName : beanNames) {
Class<?> beanType = obtainApplicationContext().getType(beanName);
//有Controller注解的bean
if (beanType != null && isHandler(beanType)) {
detectHandlerMethods(beanName);
}
}
handlerMethodsInitialized(getHandlerMethods());
}
复制代码
这里把标注了 Controller 注解的实例全部找到了,然后调用 detectHandlerMethods 方法,检测 handler 方法,也就是解析 Controller 标注类的方法。
private final Map<T, MappingRegistration<T>> registry = new HashMap<>();
protected void detectHandlerMethods(final Object handler) {
Class<?> handlerType = (handler instanceof String ?
obtainApplicationContext().getType((String) handler) : handler.getClass());
if (handlerType != null) {
final Class<?> userType = ClassUtils.getUserClass(handlerType);
//查找Controller的方法
Map<Method, T> methods = MethodIntrospector.selectMethods(userType,
(MethodIntrospector.MetadataLookup<T>) method -> getMappingForMethod(method, userType));
methods.forEach((method, mapping) -> {
//注册
this.registry.put(mapping,new MappingRegistration<>(mapping,method));
});
}
复制代码
到这里为止,Spring 将 Controller 标注的类和类方法已经解析完成。现在再来看 RequestMappingHandlerMapping 这个类的作用,他就是用来注册所有 Controller 类的方法。
三、Spring 调用 Controller 注解标注的方法
接着还有一个重要的组件 RequestMappingHandlerAdapter 它就是用来将请求转换成 HandlerMethod,并且完成请求处理的流程。org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter
@Override
public boolean supports(Object handler) {
return handler instanceof HandlerMethod;
}
protected ModelAndView handleInternal(HttpServletRequest request,
HttpServletResponse response, HandlerMethod handlerMethod) throws Exception {
//请求check
checkRequest(request);
//调用handler方法
mav = invokeHandlerMethod(request, response, handlerMethod);
//返回
return mav;
}
复制代码
看到这里,就知道 http 请求是如何被处理的了,我们找到 DispatcherServlet 的 doDispatch 方法看看,确实是如此!!
四、DispatcherServlet 调度 Controller 方法完成 http 请求
protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
// 从注册表查找handler
HandlerExecutionChain mappedHandler = getHandler(request);
HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());
// 调用
ModelAndView m = ha.handle(processedRequest, response, mappedHandler.getHandler());
//
processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
}
复制代码
DispatcherServlet 是 Spring mvc 的总入口,看到 doDispatch 方法后,全部都联系起来了。。。 最后我们看看 http 请求在 Spring mvc 中的流转流程。
第一次总结 SpringMvc 模块,理解不到位的麻烦各位大佬指正。
评论