Swagger2的/v2/api-docs接口是如何对第三方项目暴露的呢?也就是说jar包中如何暴露接口给第三方应用?
答案是:HandlerMapping
swagger2实现了自己的HandlerMapping,在实现类PropertySourcedRequestMappingHandlerMapping中,把/v2/api-docs接口保存到了handlerMethods
集合。然后提供了访问/v2/api-docs接口的方法lookupHandlerMethod(String urlPath, HttpServletRequest request),这样请求过来的时候,就可以
根据接口路径找到对应的控制器Swagger2Ctroller.class
OK,接下来,我们细看。
Swagger2的实现类是PropertySourcedRequestMappingHandlerMapping,其实就是HandlerMapping的实现。
PropertySourcedRequestMappingHandlerMapping的核心是2个方法:initHandlerMethods和lookupHandlerMethod
initHandlerMethods:把接口路径和HandlerMethod存放到spring boot webmvc的集合。
lookupHandlerMethod:提供根据接口路径查找处理类controller的功能。
package springfox.documentation.spring.web;
import com.google.common.base.Function;
import com.google.common.base.Optional;
import java.lang.reflect.Method;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.Map;
import javax.servlet.http.HttpServletRequest;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.core.env.Environment;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.HandlerMapping;
import org.springframework.web.servlet.mvc.method.RequestMappingInfo;
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping;
import org.springframework.web.util.UriTemplate;
public class PropertySourcedRequestMappingHandlerMapping extends RequestMappingHandlerMapping {
private final Map<String, HandlerMethod> handlerMethods = new LinkedHashMap();
private final Environment environment;
private final Object handler;
public PropertySourcedRequestMappingHandlerMapping(Environment environment, Object handler) {
this.environment = environment;
this.handler = handler;
}
protected void initHandlerMethods() {
this.logger.debug("initialising the handler methods");
this.setOrder(-2147482648);
Class<?> clazz = this.handler.getClass();
if (this.isHandler(clazz)) {
Method[] var2 = clazz.getMethods();
int var3 = var2.length;
for(int var4 = 0; var4 < var3; ++var4) {
Method method = var2[var4];
PropertySourcedMapping mapper = (PropertySourcedMapping)AnnotationUtils.getAnnotation(method, PropertySourcedMapping.class);
if (mapper != null) {
RequestMappingInfo mapping = this.getMappingForMethod(method, clazz);
HandlerMethod handlerMethod = this.createHandlerMethod(this.handler, method);
String mappingPath = this.mappingPath(mapper);
if (mappingPath != null) {
this.logger.info(String.format("Mapped URL path [%s] onto method [%s]", mappingPath, handlerMethod.toString()));
this.handlerMethods.put(mappingPath, handlerMethod);
} else {
Iterator var10 = mapping.getPatternsCondition().getPatterns().iterator();
while(var10.hasNext()) {
String path = (String)var10.next();
this.logger.info(String.format("Mapped URL path [%s] onto method [%s]", path, handlerMethod.toString()));
this.handlerMethods.put(path, handlerMethod);
}
}
}
}
}
}
private String mappingPath(PropertySourcedMapping mapper) {
final String key = mapper.propertyKey();
final String target = mapper.value();
return (String)Optional.fromNullable(this.environment.getProperty(key)).transform(new Function<String, String>() {
public String apply(String input) {
return target.replace(String.format("${%s}", key), input);
}
}).orNull();
}
protected boolean isHandler(Class<?> beanType) {
return AnnotationUtils.findAnnotation(beanType, Controller.class) != null || AnnotationUtils.findAnnotation(beanType, RequestMapping.class) != null;
}
protected HandlerMethod lookupHandlerMethod(String urlPath, HttpServletRequest request) {
this.logger.debug("looking up handler for path: " + urlPath);
HandlerMethod handlerMethod = (HandlerMethod)this.handlerMethods.get(urlPath);
if (handlerMethod != null) {
return handlerMethod;
} else {
Iterator var4 = this.handlerMethods.keySet().iterator();
String path;
UriTemplate template;
do {
if (!var4.hasNext()) {
return null;
}
path = (String)var4.next();
template = new UriTemplate(path);
} while(!template.matches(urlPath));
request.setAttribute(HandlerMapping.URI_TEMPLATE_VARIABLES_ATTRIBUTE, template.match(urlPath));
return (HandlerMethod)this.handlerMethods.get(path);
}
}
}
装配PropertySourcedRequestMappingHandlerMapping到IOC容器。这里很好理解,因为是在jar包中对外部第三方应用提供服务,需要先把我们的
service注入到本地项目的Spring IOC容器中。
package springfox.documentation.swagger2.configuration;
import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.core.env.Environment;
import org.springframework.web.servlet.HandlerMapping;
import springfox.documentation.spring.web.DocumentationCache;
import springfox.documentation.spring.web.PropertySourcedRequestMappingHandlerMapping;
import springfox.documentation.spring.web.SpringfoxWebMvcConfiguration;
import springfox.documentation.spring.web.json.JacksonModuleRegistrar;
import springfox.documentation.spring.web.json.JsonSerializer;
import springfox.documentation.swagger.configuration.SwaggerCommonConfiguration;
import springfox.documentation.swagger2.mappers.ServiceModelToSwagger2Mapper;
import springfox.documentation.swagger2.web.Swagger2Controller;
@Configuration
@Import({SpringfoxWebMvcConfiguration.class, SwaggerCommonConfiguration.class})
@ComponentScan(
basePackages = {"springfox.documentation.swagger2.mappers"}
)
@ConditionalOnWebApplication
public class Swagger2DocumentationConfiguration {
public Swagger2DocumentationConfiguration() {
}
@Bean
public JacksonModuleRegistrar swagger2Module() {
return new Swagger2JacksonModule();
}
@Bean
public HandlerMapping swagger2ControllerMapping(Environment environment, DocumentationCache documentationCache, ServiceModelToSwagger2Mapper mapper, JsonSerializer jsonSerializer) {
return new PropertySourcedRequestMappingHandlerMapping(environment, new Swagger2Controller(environment, documentationCache, mapper, jsonSerializer));
}
}
接下来,就是把Swagger2DocumentationConfiguration配置类作为starter,提供给第三方应用了。这里不再赘述,如果我们开发自己的jar包,没有maven远程仓库支持的话,
也可以通过spring.factories的方式,把配置类提供给本地应用使用。
好了,看完这篇文章,我们就可以轻松实现在jar包中暴露接口的功能了。后续会写一篇我自己的一个demo,是我在CR949开源项目中的一个实现。目前这个项目已经贡献给了
中国的开源社区,gitee,大家直接搜索CR949,就可以找到我的开源项目。欢迎大家关注交流,一起技术进步。
欢迎大家关注后续文章。