Java内存马之SpringMVC开发基础
Java内存马之SpringMVC开发基础
Natro92简单Spring项目
开一个简单的Spring项目,设置Server URL
为https://start.aliyun.com/
spring.io
的启动没有java8,记得修改构建模式为Maven。选择一个基础的Spring Web依赖就可以。
然后为其添加一个TestController
1 | package com.natro92.springtest.demos.web; |
运行SpringTestApplication.java
再加一个TestInterceptor
1 | package com.natro92.springtest.demos.web; |
再来一个WebConfig
1 | package com.natro92.springtest.demos.web; |
这样我们访问127.0.0.1:8080/?cmd=whoami
就有会显了:
基于Netty的Spring WebFlux Demo
选择对应的Reactive Web
添加一个软件包,其中放置一会要用的两个java文件:GreetingHandler.java
和GreetingRouter.java
1 | package com.natro92.webfluxdemo.hello; |
1 | package com.natro92.webfluxdemo.hello; |
然后再main/resources
中的application.properties
(没有就新建),修改server.port
控制netty
服务。
1 | server.port=9191 |
然后运行WebFluxDemoApplication
访问即可。
PS: 可以通过以下的这个项目进一步认识Netty
+SpringWebFlux
:
初始Spring MVC
**Spring MVC**
是 Spring 框架的一部分,它提供了一个基于模型-视图-控制器 (MVC) 设计模式的 Web 应用程序开发框架。它简化了 Web 应用程序的开发,并提供了许多强大的功能。
DispatcherServlet
是前端控制器,接受Request
并分配给其他组件。HandlerMapping
负责完成url
到Controller
映射,可以通过它来找到对应的处理Request
的Controller
;Controller
处理Request
,并返回ModelAndVIew
对象,ModelAndView
是封装结果视图的组件;- ④~⑦表示视图解析器解析
ModelAndView
对象并返回对应的视图给客户端。
文章中还提到了IOC
(Inverse of Control
,控制反转)是一种设计模式,它通过将控制权从应用程序代码转移到框架来管理对象之间的依赖关系。在Spring MVC中,IOC容器负责实例化、配置和管理应用程序中的对象,而不是由开发人员来手动管理对象之间的依赖关系。这样可以降低组件之间的耦合度,使代码更加灵活、可维护和可测试。IOC容器在Spring MVC中通过依赖注入(Dependency Injection)来实现,将对象的依赖关系注入到对象中,从而实现控制反转的效果。
以下是两种常见的容器:
BeanFactory
:Spring的最基本的IOC容器,提供了基本的IOC功能,只有在第一次请求时才创建对象。ApplicationContext
:这是BeanFactory
的扩展,提供了更多的企业级功能。ApplicationContext
在容器启动时就预加载并初始化所有的单例对象,这样就可以提供更快的访问速度。
SpringMVC九大组件
DispatcherServlet
(调度器Servlet):是Spring MVC的核心,负责接收客户端的请求并将请求分发给对应的处理器(Controller)进行处理。HandlerMapping
(处理器映射器):负责将请求映射到对应的处理器(Controller)上,确定哪个处理器处理哪个请求。HandlerAdapter
(处理器适配器):负责调用处理器(Controller)的方法来处理请求,并将处理结果返回给DispatcherServlet。Handler
(处理器Controller):处理请求的核心组件,包含处理请求的方法。ViewResolver
(视图解析器):负责根据处理器返回的逻辑视图名解析出具体的视图对象,用于渲染页面。View
(视图):负责将模型数据渲染成最终的页面展示给用户。LocaleResolver
(区域解析器):负责解析客户端的区域信息,用于国际化和本地化。ThemeResolver
(主题解析器):负责解析客户端的主题信息,用于实现页面主题的切换。MultipartResolver
(文件上传解析器):负责处理文件上传请求,将上传的文件转换成MultipartFile对象供处理器使用。
简单源码分析
九大组件初始化
先找到org.springframework.web.servlet.DispatcherServlet
,这其中没有init
函数,而init
函数在父类FrameworkServlet
的父类org.springframework.web.servlet.HttpServletBean
中。
从ServletConfig
中获取参数,创建一个PropertyValues
,设置Bean
属性,然后初始化ServletBean
:
其中为空,应该是被Override
了,可以在org.springframework.web.servlet.FrameworkServlet#initServletBean
中看到,仔细查看530行部分,这里初始化了IOC
容器。
而调用的org.springframework.web.servlet.FrameworkServlet#initWebApplicationContext
其中调用了一个onfresh
方法:
同样是Override
得到,我们查找上一层在org.springframework.web.servlet.DispatcherServlet#onRefresh
这就是SpringMVC
的九大组件的初始化。
url和Controller关系
如何通过注解比如@RequestMapping("/")
与方法关联。
从初始化这里入手吗,进入org.springframework.web.servlet.DispatcherServlet#initHandlerMappings
查找 ApplicationContext
中的所有 HandlerMappings
,包括ancestor contexts
。如果有,将所有machingBeans
排序,并将最后得到的列表传给this.handlerMappings
。
如果没获取到,注释说的很清楚:
Ensure we have at least one HandlerMapping, by registering a default HandlerMapping if no other mappings are found.
那么会用org.springframework.web.servlet.DispatcherServlet#getDefaultStrategies
创建一个默认的HandlerMapping
,并传值给this.handlerMappings
。
进去看看getDefaultStrategies
函数。
首先是从参数文件中加载默认的策略保存在defaultStrategies
中,键值对中的值还要分割成数组,然后对每个类名进行如下操作:
- 先
ClassUtils.forName
加载这个类。 - 实例化,添加策略,并返回这个值。
而这其中的DEFAULT_STRATEGIES_PATH
名字就是DispatcherServlet.properties
,根据注释这个部分定义了默认策略。
Name of the class path resource (relative to the DispatcherServlet class) that defines DispatcherServlet’s default strategy names.
在SpringMVC
的jar包中可以看到配置文件:
而按照要求BeanNameUrlHandlerMapping
、RequestMappingHandlerMapping
、RouterFunctionMapping
,一般使用的是第二个。org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping
我们跳转看下。
它的父类的父类org.springframework.web.servlet.handler.AbstractHandlerMethodMapping
实现了InitializingBean
,这个接口在bean初始化之后会进行一些自定义初始化逻辑。
在AbstractHandlerMapping
中重写内容。
调用了initHandlerMethods
,根据注释,扫描、检测、注册handler methods
。
在org.springframework.web.servlet.handler.AbstractHandlerMethodMapping#initHandlerMethods
这里断点。
进到org.springframework.web.servlet.handler.AbstractHandlerMethodMapping#processCandidateBean
中。
根据注释这个isHandler
是判断类型是不是Handler
,是用来检测给定的beanType
类是否带有Controller
注解或者RequestMapping
注解。
然后调用org.springframework.web.servlet.handler.AbstractHandlerMethodMapping#detectHandlerMethods
部分。
主要是使用ApplicationContext
获取类型,否则返回handler
类型。
获取处理器的用户类,能获取到实际处理请求的类。调用这里的selectMethods
类。然后调用那个getMappingForMethod
方法。
查看子类实现org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping#getMappingForMethod
解析Controller
类方法中的注解,然后生成一个RequestMappingInfo
对象,然后我们进去看下org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping#createRequestMappingInfo(java.lang.reflect.AnnotatedElement)
:
这里info
保存的路由是/user
。
再看org.springframework.web.servlet.handler.AbstractHandlerMethodMapping#detectHandlerMethods
,这里的lambda
表达式的意思是:
意思是,先用selectInvocableMethod
方法根据method
和userType
选择出一个可调用的方法,这样是为了处理可能存在的代理和AOP
的情况,确保获取到的是可直接调用的原始方法;然后把bean
、Method
和RequestMappingInfo
注册进MappingRegistry
。
这样就连接上url
和controller
。