我在Spring Boot(1.5.2)中有许多类可以通过自定义mediatype标签进行版本控制。 mediatype的格式为application/vnd.<application>.<version>+json
。
ApiVersionedResource ,
@RequestMapping
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface ApiVersionedResource {
/**
* Media type without the version for e.g. application/vnd.orchestral.onboarding
*
* @return
*/
String media() default Constants.DEFAULT_API_MEDIA_TYPE;
/**
* Version of the API as defined by {@link Version}
*
* @return
*/
String version() default Constants.DEFAULT_API_VERSION;
}
ApiVersionedResourceRequestCondition ,
public class ApiVersionedResourceRequestCondition extends AbstractRequestCondition<ApiVersionedResourceRequestCondition> {
private final static Logger log = LoggerFactory.getLogger(ApiVersionedResourceRequestCondition.class);
private final Set<Version> versions;
private final String acceptedMediaType;
private final static Version MAX_API_VERSION = new Version(Constants.MAX_API_VERSION);
public ApiVersionedResourceRequestCondition(String acceptedMediaType, String version) {
this(acceptedMediaType, versionGenerator(version));
}
public ApiVersionedResourceRequestCondition(String acceptedMediaType, Collection<Version> versions) {
this.acceptedMediaType = acceptedMediaType;
this.versions = Collections.unmodifiableSet(new HashSet<>(versions));
}
@Override
public ApiVersionedResourceRequestCondition combine(ApiVersionedResourceRequestCondition other) {
log.debug("Combining:\n{}\n{}", this, other);
Set<Version> newVersions = new LinkedHashSet<>(this.versions);
newVersions.addAll(other.versions);
String newMediaType;
if (StringUtils.hasText(this.acceptedMediaType) && StringUtils.hasText(other.acceptedMediaType)
&& !this.acceptedMediaType.equals(other.acceptedMediaType)) {
throw new IllegalArgumentException(String.format("Both conditions should have the same media type however its %s != %s",
this.acceptedMediaType, other.acceptedMediaType));
} else if (StringUtils.hasText(this.acceptedMediaType)) {
newMediaType = this.acceptedMediaType;
} else {
newMediaType = other.acceptedMediaType;
}
return new ApiVersionedResourceRequestCondition(newMediaType, newVersions);
}
@Override
public ApiVersionedResourceRequestCondition getMatchingCondition(HttpServletRequest request) {
final String accept = request.getHeader("Accept");
log.debug("Accept header = {}", accept);
if (StringUtils.hasText(accept)) {
final Pattern regexPattern = Pattern.compile("(.*)\\.(\\d+\\.\\d+).*");
final Matcher matcher = regexPattern.matcher(accept);
if (matcher.matches()) {
final String actualMediaType = matcher.group(1);
final Version version = new Version(matcher.group(2));
log.debug("Version={}", version);
if (acceptedMediaType.startsWith(actualMediaType)) {
for (Version definedVersion : versions) {
if (definedVersion.compareTo(version) == 0) {
return this;
}
}
log.debug("Unable to find a matching version");
} else {
log.debug("Unable to find a valid media type {}", acceptedMediaType);
}
}
}
return null;
}
@Override
protected Collection<?> getContent() {
return versions;
}
@Override
protected String getToStringInfix() {
return " && ";
}
@Override
public int compareTo(ApiVersionedResourceRequestCondition other, HttpServletRequest request) {
return 0;
}
/**
* Converts a given version string to {@link Version}
*
* @param commaDelimitedVersioning
* @return
*/
private static Set<Version> versionGenerator(final String commaDelimitedVersioning) {
HashSet<Version> versionRanges = new HashSet<>();
if (StringUtils.hasText(commaDelimitedVersioning)) {
final String[] versions = StringUtils.tokenizeToStringArray(commaDelimitedVersioning.trim(), ",");
Arrays.stream(versions).forEach(i -> {
Version v = new Version(i);
if (v.compareTo(MAX_API_VERSION) > 1) {
throw new IllegalArgumentException(
String.format("Specified version %s is highest than the max version %s", v, MAX_API_VERSION));
}
versionRanges.add(v);
});
}
return versionRanges;
}
}
WebConfiguration ,
@Configuration
@ConditionalOnClass({ ApiVersionedResource.class })
public class WebConfiguration extends WebMvcConfigurerAdapter {
@Override
public void configureContentNegotiation(final ContentNegotiationConfigurer configurer) {
configurer.favorPathExtension(true)
.favorParameter(false)
.ignoreAcceptHeader(false)
.useJaf(false)
.defaultContentType(MediaType.APPLICATION_JSON)
.mediaType("json", MediaType.APPLICATION_JSON);
}
@Bean
public WebMvcRegistrationsAdapter webMvcRegistrationsHandlerMapping() {
return new WebMvcRegistrationsAdapter() {
@Override
public RequestMappingHandlerMapping getRequestMappingHandlerMapping() {
return new CustomRequestMappingHandlerMapping();
}
};
}
private static final class CustomRequestMappingHandlerMapping extends RequestMappingHandlerMapping {
@Override
protected RequestCondition<?> getCustomTypeCondition(Class<?> handlerType) {
ApiVersionedResource typeAnnotation = AnnotationUtils.findAnnotation(handlerType, ApiVersionedResource.class);
return createCondition(typeAnnotation);
}
@Override
protected RequestCondition<?> getCustomMethodCondition(Method method) {
ApiVersionedResource methodAnnotation = AnnotationUtils.findAnnotation(method, ApiVersionedResource.class);
return createCondition(methodAnnotation);
}
@Override
protected boolean isHandler(Class<?> beanType) {
return super.isHandler(beanType) && (AnnotationUtils.findAnnotation(beanType, ApiVersionedResource.class) != null);
}
private RequestCondition<?> createCondition(ApiVersionedResource versionMapping) {
if (versionMapping != null) {
return new ApiVersionedResourceRequestCondition(versionMapping.media(), versionMapping.version());
}
return null;
}
}
}
在我的 ResourceController 中,我将注释设置为,
@RestController
@RequestMapping(Constants.DEFAULT_API_PREFIX)
@ApiVersionedResource
@Api(description = "Operations to onboard Organizations",
tags = {"organizations"},
produces = "application/json")
public class OrganizationResource {
.....
}
如果我将 接受 标头指定为未知的媒体类型,例如application/vnd.test.blah+json
,我希望服务器返回 406 错误回到客户端。
不幸的是,返回的是 404 错误。
我需要更正此问题才能返回 406 错误?
答案 0 :(得分:1)
您可以添加控制器建议:
@ControllerAdvice
public class MediaNotSupportedExceptionHandler extends ResponseEntityExceptionHandler {
@ExceptionHandler(value = { HttpMediaTypeNotSupportedException.class })
protected ResponseEntity<Void> handleMediaNotSupported(RuntimeException ex, WebRequest request) {
return new ResponseEntity<Void>(HttpStatus.NOT_ACCEPTABLE);
}
加上拦截器:
public class MediaInterceptor extends HandlerInterceptorAdapter {
@Override
public boolean preHandle(HttpServletRequest request,
HttpServletResponse response, Object handler) throws Exception {
if(request.getHeader("Accept") == null || compare against your desired media type)
throw new HttpMediaTypeNotSupportedException();
return super.preHandle(request, response, handler);
}
@Override
public void postHandle(HttpServletRequest request,
HttpServletResponse response, Object handler,
ModelAndView modelAndView) throws Exception {
super.postHandle(request, response, handler, modelAndView);
}
@Override
public void afterCompletion(HttpServletRequest request,
HttpServletResponse response, Object handler, Exception ex)
throws Exception {
super.afterCompletion(request, response, handler, ex);
}
}
然而,抛出的404令人困惑。