女朋友要我讲解@Controller注解的原理,真是难为我了

简介: 该文章详细解析了Spring MVC中@Controller注解的工作原理,包括Spring如何解析该注解、处理标注的方法以及调度控制器方法以完成HTTP请求的过程。

背景

女朋友被公司裁员一个月了,和我一样作为后端工程师,最近一直在找工作,面试了很多家还是没有找到工作,面试官问@Controller的原理,她表示一脸懵,希望我能给她讲清楚。之前我也没有好好整理这块知识,这次借助这个机会把它彻底搞清楚。 太难了.jpeg

我们知道Controller注解的类能够实现接收并处理Http请求,其实在我看Spring mvc模块的源码之前也和我女朋友目前的状态一样,很疑惑,Spring框架是底层是如何实现的,通过使用Controller注解就简单的完成了http请求的接收与处理。

image.png

有疑问就好啊,因为兴趣是最好的老师,如果有兴趣才有动力去弄懂这个技术点。

看过前面的文章的同学就会知道,学习Spring的所有组件,脑袋里要有一个思路,那就是解析组件和运用组件两个流程,这是Spring团队实现组件的统一套路,大家可以回忆一下是不是这么回事。

image.png

一、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 它就是用来调用我们写的Controller方法,完成请求处理的流程。 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());
         // 底层调用Controller
         ModelAndView m = ha.handle(processedRequest, response, mappedHandler.getHandler());
         // 处理请求结果
         processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
}

DispatcherServlet是Spring mvc的总入口,看到doDispatch方法后,全部都联系起来了。。。 最后我们看看http请求在Spring mvc中的流转流程。

image.png

第一次总结SpringMvc模块,理解不到位的麻烦各位大佬指正。

相关文章
C# 继承类中(父类与子类)构造函数的调用顺序
C# 继承类中(父类与子类)构造函数的调用顺序
|
消息中间件 SpringCloudAlibaba 资源调度
SpringCloudalibaba 与 SpringCloud 区别 | 学习笔记
快速学习 SpringCloudalibaba 与 SpringCloud 区别
3508 0
|
10天前
|
数据可视化 搜索推荐 数据处理
Dify Agent + AntV 实战:从 0 到 1 打造数据可视化解决方案
本文介绍如何结合Dify Agent与AntV,打造高效、灵活的数据可视化解决方案。通过低代码构建数据流程,调用AntV丰富图表工具,实现从数据到多样可视化图表的快速生成,助力开发者兼顾效率与个性化需求。
169 0
Dify Agent + AntV 实战:从 0 到 1 打造数据可视化解决方案
|
XML JSON 前端开发
@RestController和@Controller的区别
【9月更文挑战第18天】@RestController和@Controller的区别
873 5
|
JSON 前端开发 JavaScript
java中post请求调用下载文件接口浏览器未弹窗而是返回一堆json,为啥
客户端调接口需要返回另存为弹窗,下载文件,但是遇到的问题是接口调用成功且不报错,浏览器F12查看居然返回一堆json,而没有另存为弹窗; > 正确的效果应该是:接口调用成功且浏览器F12不返回任何json,而是弹窗另存为窗口,直接保存文件即可。
447 2
IEC104初学者教程,第四章:IEC 104 开发环境搭建
搭建IEC104开发环境涉及两款模拟器:[主站模拟器](https://www.redisant.cn/iec104client) 和 [从站模拟器](https://www.redisant.cn/iec104server)。从站模拟器中,创建连接后添加从站,配置信息对象;主站模拟器同样新建连接并开启,向从站发送总召唤以获取数据。每个步骤配有图示指导操作。
900 14
IEC104初学者教程,第四章:IEC 104 开发环境搭建
|
Java 索引
Java中的for循环:深度解析
Java中的for循环:深度解析
351 1
|
XML JSON Java
万字SpringBoot学习笔记|菜鸟版
Spring Boot是Pivotal团队在Spring的基础上提供的一套全新的开源框架,其目的是为了简化Spring应用的搭建和开发过程。Spring Boot去除了大量的XML配置文件,简化了复杂的依赖管理。 官网地址:spring.io/projects/sp… Spring Boot入门 简介 Spring Boot是简化Spring应用开发的一个框架、整个Spring技术栈的一个大整合(Spring全家桶时代)、J2EE开发的一站式解决方案(Spring Cloud是分布式整体解决方案)。 优点: – 快速创建独立运行的Spring项目以及与主流框架集成 – 使用嵌入式的Serv
343 0
|
Java Apache
Java将一个对象的属性复制到另一个对象,如何编码
【6月更文挑战第15天】Java将一个对象的属性复制到另一个对象,如何编码
593 3
|
敏捷开发 中间件 测试技术
微服务和SOA的区别是什么?
SOA的服务粒度相对较粗。在SOA中,一个服务可能是一个相对较大的功能模块,如“员工管理系统”。 微服务的服务粒度更细。在微服务架构中,同样的“员工管理系统”可能会被拆分为“员工信息管理”、“员工考勤管理”、“员工假期管理”等多个独立的服务。
242 3