1.swagger解決了我們什么問題?
傳統開發中,我們在開發完成一個接口后,為了測試我們的接口,我們通常會編寫單元測試,以測試我們的接口的可用性,或者用postman等第三方接口測試工具進行測試,但是這也有一些弊端,我們需要為接口准備測試數據,但有時,數據量較大時,前期准備工作可能相當耗時
2.除了用於接口測試,我們還能利用它做些什么有意義的事呢?
在項目的開發過程中,我們通常會用到大量的定時任務;
a.分布式環境:quartz
b.單機環境:有spring提供的schedule 或者用線程(局限性較大)
因為公司沒有引入調度中心,沒有一個統一的地方對項目中的定時任務進行統一的管理,所以需要每個項目集成自己的調度框架
例如:我現在有一個需求,是每天早上7點的時候,對昨日告警發送一個統計日報,最簡單的做法是引入@schedule(cron="0 0 7 * * ?")
我們經過本地的辛苦開發測試,一切正常,可是當我們將項目發布到QA環境,發現自己無法對這個定時任務的代碼進行有效測試,如果需要觀察我們甚至需要等到明天早上7點
問題的根源在於 Schedule 方法基於時間觸發,如果使其暴露出來,能夠手工觸發,將大大增加靈活性。
我通過對其定制,可以暴露使用到了schedule的地方,並提供手動觸發,
二:集成swagger
1.引入依賴
<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>
2.啟用swagger
在配置類上加上注解@EnableSwagger2
三:為定時調度任務schedule暴露接口
#### 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; } }
2.HandlerAdapter 擴展
andlerAdapter 擴展主要解決 Schedule 方法的執行問題。
帶有 @Scheduled 注解的 Method 方法,需要單獨的 HandlerAdapter 進行處理,並將處理結果通過 json 方式返回。
新建 ScheduleMethodHandlerAdapter
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; } }
3.與 Spring Boot 集成
ScheduleMethodHandlerMapping 和 ScheduleMethodHandlerAdapter 開發完成后,我們需要將其與 Spring Boot 進行集成,並通過參數控制是否啟用。
新建 ScheduleEndpointConfiguration
新建 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}; }