传统开发中,我们在开发完成一个接口后,为了测试我们的接口,我们通常会编写单元测试,以测试我们的接口的可用性,或者用postman等第三方接口测试工具进行测试,但是这也有一些弊端,我们需要为接口准备测试数据,但有时,数据量较大时,前期准备工作可能相当耗时
?
?
在项目的开发过程中,我们通常会用到大量的定时任务;
a.分布式环境:quartz
b.单机环境:有spring提供的schedule 或者用线程(局限性较大)
因为公司没有引入调度中心,没有一个统一的地方对项目中的定时任务进行统一的管理,所以需要每个项目集成自己的调度框架
?
例如:我现在有一个需求,是每天早上7点的时候,对昨日告警发送一个统计日报,最简单的做法是引入@schedule(cron="0 0 7 * * ?")
问题的根源在于 Schedule 方法基于时间触发,如果使其暴露出来,能够手工触发,将大大增加灵活性。
我通过对其定制,可以暴露使用到了schedule的地方,并提供手动触发,
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger2</artifactId>
<version>2.7.0</version>
</dependency>
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger-ui</artifactId>
<version>2.7.0</version>
</dependency>
在配置类上加上注解@EnableSwagger2
#### 1.HandlerMapping 扩展
新建 ScheduleMethodHandlerMapping 继承自 RequestMappingInfoHandlerMapping ,实现 HandlerMapping 接口。
?
public class ScheduleMethodHandlerMapping extends RequestMappingInfoHandlerMapping implements HandlerMapping{ /** * 包含@Scheduled注解方法的bean为handler * @param beanType * @return */ @Override protected boolean isHandler(Class<?> beanType) { return Arrays.asList(MethodUtil.getMethods(beanType)).stream() .anyMatch(method -> method.getAnnotation(Scheduled.class) != null); } ? /** * 处理@Scheduled标注方法,生成对于的RequestMappingInfo * @param method * @param handlerType * @return */ @Override protected RequestMappingInfo getMappingForMethod(Method method, Class<?> handlerType) { if (method.getAnnotation(Scheduled.class) == null){ return null; } return RequestMappingInfo.paths("/schedule/" + handlerType.getSimpleName() + "/" + method.getName()) .methods(RequestMethod.GET) .build(); } ? /** * 提高该HandlerMapping在HandlerMapping中的顺序,使其能优先处理 * @return */ @Override public int getOrder() { return 1; } }
andlerAdapter 扩展主要解决 Schedule 方法的执行问题。
带有 @Scheduled 注解的 Method 方法,需要单独的 HandlerAdapter 进行处理,并将处理结果通过 json 方式返回。
ScheduleMethodHandlerAdapter 继承自 AbstractHandlerMethodAdapter ,实现 HandlerAdapter 接口。
public class ScheduleMethodHandlerAdapter extends AbstractHandlerMethodAdapter implements HandlerAdapter { ? /** * 支持使用@Scheduled标准的handlerMethod对象 * @param handlerMethod * @return */ @Override protected boolean supportsInternal(HandlerMethod handlerMethod) { return handlerMethod.hasMethodAnnotation(Scheduled.class); } ? /** * 调用HandlerMethod方法,并返回json视图 * @param request * @param response * @param handlerMethod * @return * @throws Exception */ @Override protected ModelAndView handleInternal(HttpServletRequest request, HttpServletResponse response, HandlerMethod handlerMethod) throws Exception { ? Map<String, Object> data = Maps.newHashMap(); data.put("service", handlerMethod.getBeanType().getSimpleName()); data.put("method", handlerMethod.getMethod().getName()); Stopwatch stopwatch = Stopwatch.createStarted(); try { // 调用HandleMethod方法 handlerMethod.getMethod().invoke(handlerMethod.getBean()); stopwatch.stop(); data.put("result", "success"); }catch (Exception e){ data.put("result", "error"); data.put("exception", e.toString()); }finally { data.put("cost", stopwatch.elapsed(TimeUnit.MILLISECONDS) + "ms"); ? } // 返回Json视图 return new ModelAndView(new MappingJackson2JsonView(), data); } ? @Override protected long getLastModifiedInternal(HttpServletRequest request, HandlerMethod handlerMethod) { return 0; } ? /** * 提高在HandlerAdapter列表中的顺序,以优先处理 * @return */ @Override public int getOrder() { return 0; } }
ScheduleMethodHandlerMapping 和 ScheduleMethodHandlerAdapter 开发完成后,我们需要将其与 Spring Boot 进行集成,并通过参数控制是否启用。
新建 ScheduleEndpointConfiguration 配置类,对 ScheduleMethodHandlerMapping 和 ScheduleMethodHandlerAdapter 进行注册。
/** * 根据配置属性控制是否启用该配置。 * <br /> * spring.schedule.endpoint.enabled = true 启用配置 <br /> * spring.schedule.endpoint.enabled = false 禁用配置 <br /> */ @ConditionalOnProperty(prefix = "spring.schedule.endpoint", value = "enabled", havingValue = "true", matchIfMissing = false) @Configuration public class ScheduleEndpointConfiguration { /** * 注册 ScheduleMethodHandlerAdapter Bean * @return */ @Bean public ScheduleMethodHandlerAdapter scheduleMethodHandlerAdapter(){ return new ScheduleMethodHandlerAdapter(); } ? /** * 注册ScheduleMethodHandlerMapping Bean * @return */ @Bean public ScheduleMethodHandlerMapping scheduleMethodHandlerMapping(){ return new ScheduleMethodHandlerMapping(); } }
在 resources 目录新建 META-INF 目录,并新建 spring.factories 文件,文件内声明 ScheduleEndpointConfiguration 为自动配置项。
org.springframework.boot.autoconfigure.EnableAutoConfiguration=com.fudax.sqcs.common.schedule.ScheduleEndpointConfiguration
访问接口:/swagger-ui.html
这谁ChangeController下的所有接口
查看暴露的定时任务接口
在类MessageServer我有两个定时任务:pushAll()方法 clear()方法
点击 try it out即可立即触发
这种端点暴露只能在本地测试,开发环境使用,上到生产应该是严格禁止操作的
所以,我写环境校验,以保证生产的安全性
package com.fudax.sqcs.common.schedule.condition; import com.fudax.sqcs.common.constants.Env; import com.google.common.collect.Sets; import org.springframework.context.annotation.Condition; import org.springframework.context.annotation.ConditionContext; import org.springframework.core.type.AnnotatedTypeMetadata; import java.util.Map; public class OnEnvironmentCondition implements Condition { @Override public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) { Map<String, Object> attributes = metadata.getAnnotationAttributes(ConditionalOnEnvironment.class.getName()); Env[] envs = (Env[]) attributes.get("values"); Env currentEnv = Env.getCurrentEnv(); return Sets.newHashSet(envs).contains(currentEnv); } }
@Retention(RetentionPolicy.RUNTIME) @Target({ElementType.TYPE, ElementType.METHOD}) @Documented @Conditional(OnEnvironmentCondition.class) public @interface ConditionalOnEnvironment { Env[] values() default {Env.LOCAL}; }
原文:https://www.cnblogs.com/clovejava/p/11134750.html