SpringMVC实践之集成Sentinel限流功能

背景 近期收到线上报警,发现个别新接口在同1s内被请求了接近上百次,对系统的稳定造成了一些影响,近期调研了一下通用的限流框架,打算将Sentinel的限流功能集成进新服务 Sentinel前置知识 Sentinel 限流主要涉及以下几方面 Resource 限流的资源,资源支持动态加载,核心类为: AbstractDataSource ,Sentinel 默认提供了市面常见的资源管理服务的实现,也可以自定义实现 Rule 对定义的 Resouce 应用的限流规则,基于QPS、响应时间与系统负载。Sentinel 是通过一系列的功能Slot来实现不同的统计/控制功能,对于限流功能对应的就是 FlowSlot FlowSlot 的功能实现需要依赖前面的 NodeSelectorSlot 和 ClusterBuilderSlot 等统计Slot,有了统计信息才可以针对配置进行限流操作,这里面借用一张官方的实现图 各Slot默认的顺序也可以在代码中找到 核心处理逻辑 核心逻辑是在执行调用chain的捕获Blockexcption然后执行后续操作 集成步骤 借助SpringMVC中提供的 HandlerInterceptor 拦截器功能 与 Sentinel 提供的SpringMVC框架集成类库可以方便快速的实现限流功能 引入Sentinel SpringMVC Adapter <dependency> <groupId>com.alibaba.csp</groupId> <artifactId>sentinel-spring-webmvc-adapter</artifactId> <version>1.8.8</version> </dependency> 配置SentinelWebInterceptor Sentinel SpringMVC Adapter中提供了 SentinelWebInterceptor 来实现限流功能,这个 Interceptor 支持一些自定义的行为,主要通过 SentinelWebMvcConfig 来实现 SentinelWebMvcConfig 常用配置说明 SentinelWebMvcConfig 支持以下配置能力 urlCleaner 自定义对请求url的处理,比如厂商此次需要通过请求的设备id维度进行限流,对于其它的请求参数都进行忽略,最终可定义效果为: /api/v1/test/ping?did=6308103f1026acf274bbcc1b10001291551xxX httpMethodSpecify 是否指定特定的Http请求方法,此设置是对资源的请求方法进行了细粒度的配置,默认为false不开启,配置为true后,会在设定好的资源名前加上请求的方法,类似效果: GET: /api/v1/test/ping?did=6308103f1026acf274bbcc1b10001291551xxX blockExceptionHandler 被Block的请求异常处理器,对Block的请求进行自定义处理,可以设置返回的值,如果未配置则需要服务自己捕获 BlockException,可以通过配置 ExceptionHandler 进行统一处理 originParser 请求来源处理器,针对请求的来源进行处理,在配置资源的规则的可以使用此解析的origin细粒度控制,例如可以解析ip、用户做为请求 origin ...

January 30, 2026 · 1 min · 131 words · tomyli

Spring学习之@Conditional

注解说明 @Conditional 是 Spring 提供的用于判断是否对 Bean 注入的条件注解(在4.0后版本引入),只有满足了所有的条件,一个 bean 才会被注册成功并使用。此注解可标注在类上、元注解上、@Bean方法上,通常与 @Configuration 注解配合使用,标识在 @Configuration 注解上的 @Conditional 会影响此配置类的所有 @Bean 注解方法、@Import 注解和@ComponentScan 注解。另需注意 @Conditional 注解不支持继承 对于标注在元注解上的样例,可查看 SpringBoot 项目下的 org.springframework.boot.autoconfigure.condition 包以 ConditionalOn 开头的类 注解属性 此注解只有一个属性 value ,返回类型为 Class 数组,它用来配置所有的 Condition 类信息 Condition 是 Spring 提供的一个接口,它提供了 matches 方法来提供判断的依据逻辑,对于其参数 ConditionContext 提供了丰富的上下文信息 测试代码 @Configuration @ComponentScan(basePackages = "cn.imcompany.bean.condition") public class MyConfiguration { @Bean @ConditionalOnDate public MyBean myBean() { return new MyBean(); } @Bean public MyBean2 myBean2() { return new MyBean2(); } @Bean @Conditional(ConfigPropertyCondition.class) public MyBean5 myBean5() { return new MyBean5(); } } @Configuration @Conditional(ConfigPropertyCondition.class) public class MyConfigConfiguration { @Bean public MyBean3 myBean3() { return new MyBean3(); } @Bean public MyBean4 myBean4() { return new MyBean4(); } } public class MyDateCondition implements Condition { @Override public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) { LocalDateTime now = LocalDateTime.now(); int minute = now.getMinute(); return minute % 2 == 0; // 每隔两分钟满足条件 } } @Retention(java.lang.annotation.RetentionPolicy.RUNTIME) @Target({java.lang.annotation.ElementType.TYPE, java.lang.annotation.ElementType.METHOD}) @Documented @Conditional(MyDateCondition.class) public @interface ConditionalOnDate { } public class ConfigPropertyCondition implements Condition { @Override public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) { return "true".equals(System.getProperty("my.config.property")); } } public class Main { public static void main(String[] args) { System.setProperty("my.config.property", "true"); AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(MyConfiguration.class); for (String beanDefinitionName : context.getBeanDefinitionNames()) { System.out.println("beanDefinitionName = " + beanDefinitionName); } } } 源码阅读 对于 Configuration 类条件处理分为两个阶段: ...

June 18, 2025 · 2 min · 333 words · tomyli

Spring学习之@Bean

注解说明 @Bean 是Spring的一个核心注解,通常与 @Configuration 注解一起使用,用于标记一个方法,方法返回的实体做为Spring管理的一个Bean,通过此注解可以使用JAVA的特性配置对象。默认情况下Bean名字为方法名,也支持通过属性进行配置 属性说明 name 自定义Bean的名称,支持配置多个名称,不配置默认为方法名称 initMethod 配置实例初始化方法,在Bean实例化被调用,默认为空 destroyMethod 配置实例销毁方法,默认值为 (inferred) 即会根据返回的Bean尝试查找识别对象的销毁方法 close 或者 shutdown(限制只查找public无参方法),如返回一个 BasicDataSource 实例,会默认查找 close() 方法进行注册,如果不想Spring进行此操作,需要将此值设置为: destroyMethod="" (此配置对 DisposableBean 回调不生效) 核心逻辑在 DisposableBeanAdapter#inferDestroyMethodsIfNecessary() 方法中 与其它注解的关联 @Bean 与XML配置方式中的 <bean> 标签不同,它使用与其它注解组合的方式来实现类型 Scope 或者懒加载的功能,具体可组合注解如下: @Profile @Scope @Lazy @DepednsOn @Primary @Order @Description(给注解bean详细描述,此注解的值会保存在 BeanDefinition 实例上) @Conditional 接口默认方法定义Bean Spring 支持在接口中使用默认方法的方式进行bean的定义,方便子类配置类直接复用,示例代码: public interface BaseConfig { @Bean default MyBean3 myBean3() { return new MyBean3(); } } @Configuration public class AppConfig implements BaseConfig { } 源码阅读 Bean注解配置实例化 由于@Bean通常与@Configuration 一起使用,核心逻辑在 refresh 阶段,定位代码发现在 ConfigurationClassParser#doProcessConfigurationClass() 方法进行对 @Bean 标识方法的解析 ...

June 17, 2025 · 1 min · 191 words · tomyli

Spring学习之@Import

注解说明 @Import 注解用于标识多个需要导入的 Component 类,支持导入 @Configuration 类, ImportSelector 与 ImportBeanDefinitionRegistrar 实现,与 AnnotationConfigApplicationContext.register() 方法实现的功能类似。此注解常见的实践方式是标注在 EnableXXXX 注解上,用于开启某些功能,在开启功能时就需要通过 @Import 注解来选择导入哪些类的定义 属性说明 此注解只有一个属性 value,返回 Class[] ,用于标识导入的类 Class类型 @Configuration标识的类 对于不同功能的配置使用多个 @Configuration 标识,由一个类来 Import 另一个 类的配置,来达到复用的效果 @Configuration public class MyConfig2 { @Bean public MyBeanD myBeanD() { return new MyBeanD(); } } @Configuration @Import({MyConfig2.class}) public class MyConfig { } ImportSelector实现类 所有的 ImportSelector 实现都需要实现它的 selectImports 方法,来返回要导入的bean类名称集合 public class MyImportSelector implements ImportSelector { @Override public String[] selectImports(AnnotationMetadata importingClassMetadata) { return new String[]{MyBeanA.class.getName()}; } } 在 ImportSelector 可以通过Aware或者构造器注入的方式使用 Spring中的容器组件: ...

June 17, 2025 · 2 min · 245 words · tomyli

Spring学习之@Value

注解说明 @Value 是Spring提供用于处理属性与表达式注入的注解,可标注在字段、方法或构造器的参数上,并且支持动态解析SpringMVC中的方法参数。支持注入方式: 直接注入如: @Value(“test”) ${my.app.myProp} 属性注入 #{systemProperties.myProp} SpEL表达式 它提供了不同的配置源(属性文件、系统属性)的注入 注解属性 @Value 注解只有一个属性 value ,表示要注入的实际值的表达式,支持属性与SpEL表达式两种方式 测试代码 以下测试代码MyController的username的注入值为: tomyli,而不是配置文件中的admin @Controller public class MyController { @Value("${USER}") @Getter private String username; } @Configuration @ComponentScan(basePackages = "cn.imcompany.bean.autowired") @PropertySource("classpath:application.properties") public class MyConfiguration { } public class Main { public static void main(String[] args) { AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(MyConfiguration.class); MyController myController = context.getBean(MyController.class); System.out.println(myController.getUsername()); // tomyi } } application.properties配置文件 USER=admin 源码阅读 @Value 注解的处理由 AutowiredAnnotationBeanPostProcessor 类负责,与处理 @Autowired 注解的类为同一个PostProcessor。主要的处理逻辑实现在 DefaultListableBeanFactory#doResolveDependency 方法,对于 @Value注解的处理主要集中在第2步,解析@Value的操作主要分为3步 ...

June 10, 2025 · 1 min · 170 words · tomyli

Spring学习之@Autowireid

注解说明 @Autowired 是Spring框架的核心注解,用于自动装载构造器、字段、set方法、配置方法上的依赖,它是 JSR-330 @Inject 注解的替代,支持配置 依赖是否必须 选项 属性说明 此注解只有一个属性 required ,默认为 true 表示待注入组件必须存在 注解注入规则说明 构造器注入 如果一个类声明了多个构造器且没有一个显式标注 @Autowired,则Spring会使用默认的构造方法进行装载 如果一个类只声明了一个构造器,不管有没有标注 @Autowired 注解它都会被直接使用 多参数可以配置多个required属性,对于可选注入,可以声明为 Java8 的 Optional 类型 对于可选注入也可以使用 JSR-305 的 javax.annotation.Nullable 注解 字段注入 字段在构造器之后,配置方法注入之前进行自动注入 字段注入支持组件自引用(self references) 方法注入 方法注入不限制方法名与参数个数,所有参数会被Spring进行匹配注入 参数注入 目前只在 spring-test 模块中有效,其它地方使用会被忽略 数组/集合注入 Spring通过匹配数组/集合的值进行注入,Map的key必须为String类型,支持集合中元素的Order配置 不支持 BeanPostProcessor 或者 BeanFactoryPostProcessor 因为真实的注入动作发生在 BeanPostProcessor 阶段,所以不能在 BeanPostProcessor 或者 BeanFactoryPostProcessor 中使用此注解 源码阅读 在Spring中,处理 @Autowired 注解对应的类为 AutowiredAnnotationBeanPostProcessor ,又是一个Bean后置处理器 对于此类,需要关注其实现的两个接口类 MergedBeanDefinitionPostProcessor (运行时合并Bean的定义回调接口)和 InstantiationAwareBeanPostProcessor (在Bean实例化前后进行处理后置处理器) MergedBeanDefinitionPostProcessor 主要是为了在真正bean实例化前准备缓存的bean元数据 InstantiationAwareBeanPostProcessor Bean实例化回调接口,通常用于为bean创建代理 测试代码 @Service public class MyService { } @Controller public class MyController { @Autowired MyService myService; public void showService() { System.out.println("myService = " + myService); } } @Configuration @ComponentScan(basePackages = "cn.imcompany.bean.autowired") public class MyConfiguration { } public class Main { public static void main(String[] args) { AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(MyConfiguration.class); MyController myController = context.getBean(MyController.class); myController.showService(); } } 元数据收集 调用栈信息如下: ...

June 6, 2025 · 2 min · 267 words · tomyli

Spring学习之@CompnentScan

注解说明 @ComponentScan 注解是Spring 提供的对组件进行扫描的注解指令,通常与 @Configuration 注解一起使用,支持在类上重复标注 注解能做什么? 此注解支持以下重要特性: 自定义扫描的包范围 配置包含扫描组件的过滤器 配置排除组件的过滤器 是否使用默认过滤器 是否配置懒加载 源码分析 由于此注解通常与 @Configuration 注解一起使用,其解析的主要逻辑都是在 refresh 阶段的 invokeBeanFactoryPostProcessors 方法中,最终会定位到 ConfigurationClassParser#doProcessConfigurationClass() 方法中,在方法中可以看到对 ComponentScan 注解的操作逻辑了 对注解属性的解析 所有对属性的解析都在 ComponentScanAnnotationParser#parse() 方法中进行,此方法中会创建一个 ClassPathBeanDefinitionScanner 实例scanner,此实例初始化过程中会对默认的Filter进行加载 处理默认Filter主要是 JSR-250 与 JSR-330 API 增加的注解 然后会把配置的 includeFilter 和 excludeFilter 设置到 scanner 中,接着会对 basePackage 扫描包属性进行解析配置,如果配置了就添加,没有配置,默认就使用 @ComponentScan 注解标注类所在的包路径。解析完注解属性后,就开始了真正的扫描操作 扫描操作 扫描操作由 ClassPathBeanDefinitionScanner#doScan() 方法进行处理,此方法会调用 findCandidateComponents() 方法在给定的包下查找符合条件的组件,查找的任务由 scanCandidateComponents() 进行,该方法调用 isCandidateComponent() 方法,根据解析的includeFilter和excludeFilter 来选择满足条件的组件 这样扫描到所有满足条件的Bean后,会接着对这些Bean进行parse操作,直到注解再无其它依赖 注册Bean 扫描完所有Bean后,会调用 checkCandidate() 方法进行检查,检查当前要注册的Bean是否与已经注册过的Bean的定义是否兼容,如果冲突会报 ConflictingBeanDefinitionException 。检查没问题后,会调用 registerBeanDefinition() 方法对Bean进行注册,这个过程就涉及到Spring其它的生命周期了(这个后续专门学习总结),对于@ComponentScan 组件来说已经完成了它的使命 总结 最后以注解解析过程中的涉及的核心类做一下总结 常见问题 调整包结构后一些@Bean配置扫描不到了 调用启动类的包路径为最外层,这样可以扫描当前包及子包的配置 默认只扫描当前标注注解的包,可以通过配置 basePackage 属性指定扫描路径,配置后默认的扫描路径就不生效了 如何将未配置Spring相关组件注解的Bean纳入Spring管理? 可以通过配置 ComponentScan的includeFilter 来实现 ...

May 15, 2025 · 1 min · 99 words · tomyli

Spring学习之@Configuration

@Configuration是什么? @Configuration 是 Spring提供的一个用于配置与声明Bean与相应Bean之间依赖的注解,根据文档描述,此注解通常由 AnnotationConfigApplicationContext 上下文进行启动加载 @Configuration能做什么? 先看一下此注解的声明,在此注解上标识了 @Component ,则证明此注解是一个 Component,拥有 @Component 注解的基础能力(可以被Spring容器自动扫描与加载) 上图标识了注解常用的配置方式,分类为以下几种 属性资源导入 引入其它配置类的配置 Value注入 Spring容器组件注入 支持Profile配置 注解属性 在Spring5.x版本中,只支持两个属性配置 value 配置注解的名称 proxyBeanMethods 是否代理增强(基于CGLIB)标识 @Bean 的方法,默认为true,保证声明的bean是单例共享的,而不是每一次通过方法调用都生成一个新的 此属性的两个值被定义为两种模式 full 模式(值为true) lite 模式(值为false) 源码分析 @Configuration 注解的解析类为 ConfigurationClassPostProcessor ,本次主要关注从 AnnotationConfigApplicationContext 启动后的整个处理过程, AnnotationConfigApplicationContext 启动分为三个阶段 先上一张三个阶段的涉及的主要类与调用关系图 初始化 初始化reader 此过程会实例化 AnnotatedBeanDefinitionReader 类,此类在实例化时会调用 AnnotationConfigUtils.registerAnnotationConfigProcessors 来注册解析配置的Processor,如果registry中没有包含 ConfigurationClassPostProcessor 类的定义,则会新new一个 ConfigurationClassPostProcessor 的Bean定义类 整体调用链路如下: 初始化scanner 此过程会实例化Bean定义扫描器 ClassPathBeanDefinitionScanner , 在此过程中主要会设置 Environment 和 ResourceLoader 相关信息 注册 注册阶段主要是对标注了 @Configuration 类(MyConfig)进行注册,以便Spring可以正常识别,主要逻辑在 AnnotatedBeanDefinitionReader#doRegisterBean 中,此方法会调用 BeanDefinitionReaderUtils.registerBeanDefinition 对此Bean进行注册 至此, @Configuration 类信息就注册到了 Spring 容器中 ...

May 9, 2025 · 1 min · 202 words · tomyli

SpringMVC实践之集成Sentinel限流功能

背景 近期收到线上报警,发现个别新接口在同1s内被请求了接近上百次,对系统的稳定造成了一些影响,近期调研了一下通用的限流框架,打算将Sentinel的限流功能集成进新服务 Sentinel前置知识 Sentinel 限流主要涉及以下几方面 Resource 限流的资源,资源支持动态加载,核心类为: AbstractDataSource ,Sentinel 默认提供了市面常见的资源管理服务的实现,也可以自定义实现 Rule 对定义的 Resouce 应用的限流规则,基于QPS、响应时间与系统负载。Sentinel 是通过一系列的功能Slot来实现不同的统计/控制功能,对于限流功能对应的就是 FlowSlot FlowSlot 的功能实现需要依赖前面的 NodeSelectorSlot 和 ClusterBuilderSlot 等统计Slot,有了统计信息才可以针对配置进行限流操作,这里面借用一张官方的实现图 各Slot默认的顺序也可以在代码中找到 核心处理逻辑 核心逻辑是在执行调用chain的捕获Blockexcption然后执行后续操作 集成步骤 借助SpringMVC中提供的 HandlerInterceptor 拦截器功能 与 Sentinel 提供的SpringMVC框架集成类库可以方便快速的实现限流功能 引入Sentinel SpringMVC Adapter <dependency> <groupId>com.alibaba.csp</groupId> <artifactId>sentinel-spring-webmvc-adapter</artifactId> <version>1.8.8</version> </dependency> 配置SentinelWebInterceptor Sentinel SpringMVC Adapter中提供了 SentinelWebInterceptor 来实现限流功能,这个 Interceptor 支持一些自定义的行为,主要通过 SentinelWebMvcConfig 来实现 SentinelWebMvcConfig 常用配置说明 SentinelWebMvcConfig 支持以下配置能力 urlCleaner 自定义对请求url的处理,比如厂商此次需要通过请求的设备id维度进行限流,对于其它的请求参数都进行忽略,最终可定义效果为: /api/v1/test/ping?did=6308103f1026acf274bbcc1b10001291551xxX httpMethodSpecify 是否指定特定的Http请求方法,此设置是对资源的请求方法进行了细粒度的配置,默认为false不开启,配置为true后,会在设定好的资源名前加上请求的方法,类似效果: GET: /api/v1/test/ping?did=6308103f1026acf274bbcc1b10001291551xxX blockExceptionHandler 被Block的请求异常处理器,对Block的请求进行自定义处理,可以设置返回的值,如果未配置则需要服务自己捕获 BlockException,可以通过配置 ExceptionHandler 进行统一处理 originParser 请求来源处理器,针对请求的来源进行处理,在配置资源的规则的可以使用此解析的origin细粒度控制,例如可以解析ip、用户做为请求 origin ...

April 25, 2025 · 1 min · 131 words · tomyli

SpringMVC学习之HandlerMapping

起因 新服务在处理不存在的url请求时,上报日志报错,具体错误信息: 根据报错信息找到相关代码 代码处做了对参数hanler的强制转换,但是真正传入的参数类型为 ResourceHttpRequestHandler 导致转换失败而报错,梳理一下,handler是在 自定义的日志Interceptor 的afterCompletion方法中由框架传入的 ResourceHttpRequestHandler 是什么? 查阅类文档,ResourceHttpRequestHandler是SpringMVC提供的处理静态资源的Handler,比如常见的js或者css文件。在当前前后端分离的分工下,对于REST服务开发,不会再使用这种方式提供对静态资源的访问,为什么请求了一个不存在的url,会使用静态资源的Hanlder来处理? SpringMVC提供了哪些HandlerMapping? SpringMVC提供的HanlderMapping位于 org.springframework.web.servlet.handler 包下,主要包含 Mapping Desc RequestMappingHandlerMapping 处理@RequestMapping 注解映射关系 SimpleUrlHandlerMapping 使用URL模式匹配映射关系 BeanNameUrlHandlerMapping 基于Bean名匹配映射关系 SpringMVC 路径映射流程回顾 回顾一下SpringMVC的路径映射流程,通常的url请求的编码方式为: @RequestMapping("/ping") public Response<String> ping() { return Response.success("pong"); } 标识了 @RequestMapping 注解的方法会被 SpringMVC 框架的 RequestMappingHandlerMapping 类在启动时进行扫描并根据映射信息来创建 RequestMappingInfo(RequestMappingInfo 类维护请求的映射信息),扫描后的所有Mapping信息在 MappingRegistry(AbstractHandlerMethodMapping内部类) 类进行维护 SpringMVC如何处理request与HandlerMethod的映射关系 AbstractHandlerMethodMapping的getHandler 方法用来通过给定的request来找到一个合适的handler进行处理 getHandler会先调用抽象方法 getHandlerInternal 由子类实现查找逻辑,如果找不到,则查找默认配置的Hanlder,默认Hanlder找不到则返回null SpringMVC默认使用了哪些HandlerMapping 如何查询Handler是在 DispacherServlet的getHandler方法操作的,它会在所有的handlerMappings(有序handlerMapping)中查找出一个可用的handler进行request的处理 如图可知会在7个handlerMapping中依次进行查询,这些handlerMapping是在 DispatcherServlet#initHandlerMappings() 设置的 定位到问题原因 跟踪请求代码发现对于不存在的url会在 SimpleUrlHanlderMapping中找到可用的handler,因为最终请求url匹配上了 /**,而 /** 模式对应的handler就是 ResourceHttpRequestHandler,此时getHandler的结果就是ResourceHttpRequestHandler 了,但是在上报时使用的是 HandlerMethod,执行到此时报了转换异常 WebMvcAutoConfiguration中的SourceHandler在哪注册的? 在启用MVC功能时,会增加注解 @EnableWebMvc,而对应的MVC配置类为 WebMvcConfigurationSupport,在这个类中进行SourceHandler的注册。跟踪代码发现两个ResourceHandler是在 WebMvcAutoConfigurationAdapter中添加的 ...

April 18, 2025 · 1 min · 72 words · tomyli