我正在尝试从Spring Boot .innerHTML
迁移到1.5.7
这是我的休息控制器:
2.0.0.M4
这是异常处理程序:
@RestController
@RequestMapping("/v1.0/users")
public class UsersController {
@Autowired
private UserService userService;
@RequestMapping(value = "/validate-username/{username}", method = RequestMethod.GET)
@ResponseStatus(value = HttpStatus.OK)
public void validateUsername(@PathVariable String username) {
throw new EntityAlreadyExistsException();
}
...
}
如果是以下用户名,例如:@ControllerAdvice
public class GlobalControllerExceptionHandler {
@ExceptionHandler
@ResponseBody
@ResponseStatus(HttpStatus.CONFLICT)
public Map<String, ResponseError> handleEntityAlreadyExistsException(EntityAlreadyExistsException e, HttpServletRequest request, HttpServletResponse response) throws IOException {
logger.debug("API error", e);
return createResponseError(HttpStatus.CONFLICT.value(), e.getMessage());
}
}
一切正常,我收到的alex
状态代码为409
作为内容类型,但如果出现以下情况用户名,例如application/json; charset=UTF-8
我的端点返回alex@test.com
状态代码和非JSON内容类型,如下所示:
当用户名PathVariable最后包含500
时,我可以重现此问题。
我使用嵌入式Tomcat作为应用程序服务器。 Woth Spring Boot 1.5.7相同的功能工作正常。如何使用Spring Boot 2.0.0.M4?
P.S。
我知道将电子邮件地址作为网址参数发送是一种不好的做法。我只是对这个特殊情况感兴趣。
答案 0 :(得分:1)
试试这个好友,{username:.+}
而不是{username}
编辑:我发现@是URL的保留字符,不应该在URL中使用。您需要对您的网址进行编码。
类似的东西:alex@test.com - &gt;亚历克斯%40test.com
答案 1 :(得分:1)
您正在观察的问题深入到Spring WebMvc内部。
根本原因是Spring正在推测接受的响应类型。
详细地说,在alex@test.com
的情况下实际为接受的响应类型提供答案的策略类是ServletPathExtensionContentNegotiationStrategy,它根据路径中的内容进行猜测。
由于com
是有效的文件扩展名类型(请参阅this),Spring Boot 2.0.0.M4会尝试使用该mime类型转换您{{1}的响应}类到那个mime类型(当然也失败了),因此回到它的默认错误响应。
首先解决此问题的方法是使用值指定HTTP标头ControllerAdvice
Accept
。
不幸的是,Spring 2.0.0.M4仍然不会使用这个mime类型,因为ServletPathExtensionContentNegotiationStrategy策略优先于HeaderContentNegotiationStrategy。
此外使用application/json
或(就像alex
这样的事情),Spring没有猜到mime类型,因此允许常规流程继续进行。
这是有效的原因是Spring Boot 1.5.7.RELEASE是Spring没有尝试将com映射到mime类型,因此使用了一个默认的响应类型,它允许将响应对象转换为JSON的过程继续。
现在出现了更有趣的部分,这当然是修复。 我有两个解决方案,但我只会展示第一个解决方案而只提到第二个解决方案。
这是第一个以我对问题的解释为基础的解决方案。 我承认这个解决方案确实有点干扰,但它的作用就像一个魅力。
我们需要做的是更改自动配置的alex@test.gr
,以便用我们自己的自定义ContentNegotiationManager
替换提供的PathExtensionContentNegotiationStrategy
。这样的操作可以通过BeanPostProcessor
轻松执行。
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanPostProcessor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.MediaType;
import org.springframework.web.accept.ContentNegotiationManager;
import org.springframework.web.accept.ContentNegotiationStrategy;
import org.springframework.web.accept.PathExtensionContentNegotiationStrategy;
import org.springframework.web.context.request.NativeWebRequest;
import java.util.ListIterator;
@Configuration
public class ContentNegotiationManagerConfiguration {
@Bean
public ContentNegotiationManagerBeanPostProcessor contentNegotiationManagerBeanPostProcessor() {
return new ContentNegotiationManagerBeanPostProcessor();
}
private static class ContentNegotiationManagerBeanPostProcessor implements BeanPostProcessor {
@Override
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
return bean; //no op
}
@Override
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
if (!(bean instanceof ContentNegotiationManager)) {
return bean;
}
final ContentNegotiationManager contentNegotiationManager = (ContentNegotiationManager) bean;
ListIterator<ContentNegotiationStrategy> iterator =
contentNegotiationManager.getStrategies().listIterator();
while (iterator.hasNext()) {
ContentNegotiationStrategy strategy = iterator.next();
if (strategy.getClass().getName().contains("OptionalPathExtensionContentNegotiationStrategy")) {
iterator.set(new RemoveHandleNoMatchContentNegotiationStrategy());
}
}
return bean;
}
}
private static class RemoveHandleNoMatchContentNegotiationStrategy
extends PathExtensionContentNegotiationStrategy {
/**
* Don't lookup file extensions to match mime-type
* Effectively reverts to Spring Boot 1.5.7 behavior
*/
@Override
protected MediaType handleNoMatch(NativeWebRequest request, String key) {
return null;
}
}
}
可以实现的第二个解决方案是利用Spring默认使用的OptionalPathExtensionContentNegotiationStrategy类的功能。
基本上,您需要做的是确保对validateUsername
端点的每个HTTP请求都包含名为org.springframework.web.accept.PathExtensionContentNegotiationStrategy.SKIP
的属性,其值为true
答案 2 :(得分:1)
This article显示了如何阻止Spring使用路径扩展进行内容协商(至少对我来说是Spring Boot 1.5.x有用):
@Configuration
public class WebMvcConfig extends WebMvcConfigurerAdapter {
@Override
public void configureContentNegotiation(ContentNegotiationConfigurer configurer) {
configurer.favorPathExtension(false);
}
}
答案 3 :(得分:1)
最短的解决方案是按如下所示修改您的网址
@RequestMapping(value = "/validate-username/{username}/"
注意:我在URL末尾使用斜杠'/'
。因此,该URL可以完美地适用于.com , .us
路径变量中包含文本的任何类型的电子邮件地址或{username}
。
您无需在应用程序中添加任何种类的额外配置。