Sky*_*fer 3 kotlin spring-boot spring-webflux
我使用 Webflux 在 Spring Boot 2.1.8 应用程序上测试了 BlockHound,我在 bean 验证中遇到了阻塞调用。为了确保这不是由我们的逻辑引起的,我创建了一个带有一个端点的简单 Webflux 应用程序。
这是应用程序中的一个简单控制器:
@RestController
@RequestMapping("/v1/test")
@Validated
class TestController {
@PostMapping("/{type}", consumes = [MediaType.APPLICATION_JSON_VALUE])
fun testPost(@PathVariable type: String, @Valid @RequestBody entry: TestEntry): Mono<TestEntry> {
return Mono.just(TestEntry("${entry.data} - $type"))
}
}
@JsonInclude(JsonInclude.Include.NON_NULL)
data class TestEntry(
@field:NotNull val data: String?
)
Run Code Online (Sandbox Code Playgroud)
在主要方法中,我运行 Block Hound JVM 代理:
@SpringBootApplication
class DemoApplication
fun main(args: Array<String>) {
BlockHound.install()
runApplication<DemoApplication>(*args)
}
Run Code Online (Sandbox Code Playgroud)
向我的端点发送请求后,我收到此异常:
java.lang.Error: Blocking call! java.io.RandomAccessFile#readBytes
at reactor.blockhound.BlockHound$Builder.lambda$new$0(BlockHound.java:196) ~[blockhound-1.0.1.RELEASE.jar:na]
Suppressed: reactor.core.publisher.FluxOnAssembly$OnAssemblyException:
Error has been observed at the following site(s):
|_ checkpoint ? HTTP POST "/v1/test/type1" [ExceptionHandlingWebHandler]
Stack trace:
at reactor.blockhound.BlockHound$Builder.lambda$new$0(BlockHound.java:196) ~[blockhound-1.0.1.RELEASE.jar:na]
at reactor.blockhound.BlockHound$Builder.lambda$install$6(BlockHound.java:318) ~[blockhound-1.0.1.RELEASE.jar:na]
at reactor.blockhound.BlockHoundRuntime.checkBlocking(BlockHoundRuntime.java:46) ~[na:na]
at java.base/java.io.RandomAccessFile.readBytes(RandomAccessFile.java) ~[na:na]
at java.base/java.io.RandomAccessFile.read(RandomAccessFile.java:406) ~[na:na]
at java.base/java.io.RandomAccessFile.readFully(RandomAccessFile.java:470) ~[na:na]
at java.base/java.util.zip.ZipFile$Source.readFullyAt(ZipFile.java:1298) ~[na:na]
at java.base/java.util.zip.ZipFile$ZipFileInputStream.initDataOffset(ZipFile.java:997) ~[na:na]
at java.base/java.util.zip.ZipFile$ZipFileInputStream.read(ZipFile.java:1012) ~[na:na]
at java.base/java.util.zip.ZipFile$ZipFileInflaterInputStream.fill(ZipFile.java:467) ~[na:na]
at java.base/java.util.zip.InflaterInputStream.read(InflaterInputStream.java:159) ~[na:na]
at java.base/java.io.InputStream.readNBytes(InputStream.java:490) ~[na:na]
at java.base/java.util.jar.JarFile.getBytes(JarFile.java:805) ~[na:na]
at java.base/java.util.jar.JarFile.checkForSpecialAttributes(JarFile.java:1005) ~[na:na]
at java.base/java.util.jar.JarFile.isMultiRelease(JarFile.java:388) ~[na:na]
at java.base/java.util.jar.JarFile.getEntry(JarFile.java:507) ~[na:na]
at java.base/sun.net.www.protocol.jar.URLJarFile.getEntry(URLJarFile.java:131) ~[na:na]
at java.base/sun.net.www.protocol.jar.JarURLConnection.connect(JarURLConnection.java:137) ~[na:na]
at java.base/sun.net.www.protocol.jar.JarURLConnection.getInputStream(JarURLConnection.java:155) ~[na:na]
at java.base/java.net.URL.openStream(URL.java:1117) ~[na:na]
at java.base/java.lang.ClassLoader.getResourceAsStream(ClassLoader.java:1738) ~[na:na]
at java.base/java.lang.Class.getResourceAsStream(Class.java:2651) ~[na:na]
at org.springframework.core.LocalVariableTableParameterNameDiscoverer.inspectClass(LocalVariableTableParameterNameDiscoverer.java:94) ~[spring-core-5.2.2.RELEASE.jar:5.2.2.RELEASE]
at java.base/java.util.concurrent.ConcurrentHashMap.computeIfAbsent(ConcurrentHashMap.java:1705) ~[na:na]
at org.springframework.core.LocalVariableTableParameterNameDiscoverer.doGetParameterNames(LocalVariableTableParameterNameDiscoverer.java:84) ~[spring-core-5.2.2.RELEASE.jar:5.2.2.RELEASE]
at org.springframework.core.LocalVariableTableParameterNameDiscoverer.getParameterNames(LocalVariableTableParameterNameDiscoverer.java:72) ~[spring-core-5.2.2.RELEASE.jar:5.2.2.RELEASE]
at org.springframework.core.PrioritizedParameterNameDiscoverer.getParameterNames(PrioritizedParameterNameDiscoverer.java:55) ~[spring-core-5.2.2.RELEASE.jar:5.2.2.RELEASE]
at org.springframework.validation.beanvalidation.LocalValidatorFactoryBean$1.getParameterNames(LocalValidatorFactoryBean.java:325) ~[spring-context-5.2.2.RELEASE.jar:5.2.2.RELEASE]
at org.hibernate.validator.internal.util.ExecutableParameterNameProvider.getParameterNames(ExecutableParameterNameProvider.java:37) ~[hibernate-validator-6.0.18.Final.jar:6.0.18.Final]
at org.hibernate.validator.internal.metadata.aggregated.ParameterMetaData$Builder.build(ParameterMetaData.java:169) ~[hibernate-validator-6.0.18.Final.jar:6.0.18.Final]
at org.hibernate.validator.internal.metadata.aggregated.ExecutableMetaData$Builder.findParameterMetaData(ExecutableMetaData.java:435) ~[hibernate-validator-6.0.18.Final.jar:6.0.18.Final]
at org.hibernate.validator.internal.metadata.aggregated.ExecutableMetaData$Builder.build(ExecutableMetaData.java:388) ~[hibernate-validator-6.0.18.Final.jar:6.0.18.Final]
at org.hibernate.validator.internal.metadata.aggregated.BeanMetaDataImpl$BuilderDelegate.build(BeanMetaDataImpl.java:788) ~[hibernate-validator-6.0.18.Final.jar:6.0.18.Final]
at org.hibernate.validator.internal.metadata.aggregated.BeanMetaDataImpl$BeanMetaDataBuilder.build(BeanMetaDataImpl.java:648) ~[hibernate-validator-6.0.18.Final.jar:6.0.18.Final]
at org.hibernate.validator.internal.metadata.BeanMetaDataManager.createBeanMetaData(BeanMetaDataManager.java:204) ~[hibernate-validator-6.0.18.Final.jar:6.0.18.Final]
at org.hibernate.validator.internal.metadata.BeanMetaDataManager.getBeanMetaData(BeanMetaDataManager.java:166) ~[hibernate-validator-6.0.18.Final.jar:6.0.18.Final]
at org.hibernate.validator.internal.engine.ValueContext.getLocalExecutionContext(ValueContext.java:78) ~[hibernate-validator-6.0.18.Final.jar:6.0.18.Final]
at org.hibernate.validator.internal.engine.ValidatorImpl.validateReturnValueInContext(ValidatorImpl.java:1060) ~[hibernate-validator-6.0.18.Final.jar:6.0.18.Final]
at org.hibernate.validator.internal.engine.ValidatorImpl.validateReturnValue(ValidatorImpl.java:306) ~[hibernate-validator-6.0.18.Final.jar:6.0.18.Final]
at org.hibernate.validator.internal.engine.ValidatorImpl.validateReturnValue(ValidatorImpl.java:257) ~[hibernate-validator-6.0.18.Final.jar:6.0.18.Final]
at org.springframework.validation.beanvalidation.MethodValidationInterceptor.invoke(MethodValidationInterceptor.java:122) ~[spring-context-5.2.2.RELEASE.jar:5.2.2.RELEASE]
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186) ~[spring-aop-5.2.2.RELEASE.jar:5.2.2.RELEASE]
at org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.proceed(CglibAopProxy.java:747) ~[spring-aop-5.2.2.RELEASE.jar:5.2.2.RELEASE]
at org.springframework.aop.framework.CglibAopProxy$DynamicAdvisedInterceptor.intercept(CglibAopProxy.java:689) ~[spring-aop-5.2.2.RELEASE.jar:5.2.2.RELEASE]
at com.example.demo.TestController$$EnhancerBySpringCGLIB$$ae3498ec.testPost(<generated>) ~[classes/:na]
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ~[na:na]
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) ~[na:na]
at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) ~[na:na]
at java.base/java.lang.reflect.Method.invoke(Method.java:566) ~[na:na]
at org.springframework.web.reactive.result.method.InvocableHandlerMethod.lambda$invoke$0(InvocableHandlerMethod.java:147) ~[spring-webflux-5.2.2.RELEASE.jar:5.2.2.RELEASE]
at reactor.core.publisher.MonoFlatMap$FlatMapMain.onNext(MonoFlatMap.java:118) ~[reactor-core-3.3.1.RELEASE.jar:3.3.1.RELEASE]
at reactor.core.publisher.Operators$MonoSubscriber.complete(Operators.java:1630) ~[reactor-core-3.3.1.RELEASE.jar:3.3.1.RELEASE]
at reactor.core.publisher.MonoZip$ZipCoordinator.signal(MonoZip.java:247) ~[reactor-core-3.3.1.RELEASE.jar:3.3.1.RELEASE]
at reactor.core.publisher.MonoZip$ZipInner.onNext(MonoZip.java:329) ~[reactor-core-3.3.1.RELEASE.jar:3.3.1.RELEASE]
at reactor.core.publisher.MonoPeekTerminal$MonoTerminalPeekSubscriber.onNext(MonoPeekTerminal.java:173) ~[reactor-core-3.3.1.RELEASE.jar:3.3.1.RELEASE]
at reactor.core.publisher.FluxDefaultIfEmpty$DefaultIfEmptySubscriber.onNext(FluxDefaultIfEmpty.java:92) ~[reactor-core-3.3.1.RELEASE.jar:3.3.1.RELEASE]
at reactor.core.publisher.FluxPeek$PeekSubscriber.onNext(FluxPeek.java:192) ~[reactor-core-3.3.1.RELEASE.jar:3.3.1.RELEASE]
at reactor.core.publisher.FluxSwitchIfEmpty$SwitchIfEmptySubscriber.onNext(FluxSwitchIfEmpty.java:67) ~[reactor-core-3.3.1.RELEASE.jar:3.3.1.RELEASE]
at reactor.core.publisher.FluxOnErrorResume$ResumeSubscriber.onNext(FluxOnErrorResume.java:73) ~[reactor-core-3.3.1.RELEASE.jar:3.3.1.RELEASE]
at reactor.core.publisher.Operators$MonoSubscriber.complete(Operators.java:1630) ~[reactor-core-3.3.1.RELEASE.jar:3.3.1.RELEASE]
at reactor.core.publisher.MonoFlatMap$FlatMapMain.onNext(MonoFlatMap.java:144) ~[reactor-core-3.3.1.RELEASE.jar:3.3.1.RELEASE]
at reactor.core.publisher.FluxContextStart$ContextStartSubscriber.onNext(FluxContextStart.java:103) ~[reactor-core-3.3.1.RELEASE.jar:3.3.1.RELEASE]
at reactor.core.publisher.FluxContextStart$ContextStartSubscriber.onNext(FluxContextStart.java:103) ~[reactor-core-3.3.1.RELEASE.jar:3.3.1.RELEASE]
at reactor.core.publisher.FluxMapFuseable$MapFuseableConditionalSubscriber.onNext(FluxMapFuseable.java:287) ~[reactor-core-3.3.1.RELEASE.jar:3.3.1.RELEASE]
at reactor.core.publisher.FluxFilterFuseable$FilterFuseableConditionalSubscriber.onNext(FluxFilterFuseable.java:330) ~[reactor-core-3.3.1.RELEASE.jar:3.3.1.RELEASE]
at reactor.core.publisher.Operators$MonoSubscriber.complete(Operators.java:1630) ~[reactor-core-3.3.1.RELEASE.jar:3.3.1.RELEASE]
at reactor.core.publisher.MonoCollect$CollectSubscriber.onComplete(MonoCollect.java:145) ~[reactor-core-3.3.1.RELEASE.jar:3.3.1.RELEASE]
at reactor.core.publisher.FluxMap$MapSubscriber.onComplete(FluxMap.java:136) ~[reactor-core-3.3.1.RELEASE.jar:3.3.1.RELEASE]
at reactor.core.publisher.FluxPeek$PeekSubscriber.onComplete(FluxPeek.java:252) ~[reactor-core-3.3.1.RELEASE.jar:3.3.1.RELEASE]
at reactor.core.publisher.FluxMap$MapSubscriber.onComplete(FluxMap.java:136) ~[reactor-core-3.3.1.RELEASE.jar:3.3.1.RELEASE]
at reactor.netty.channel.FluxReceive.terminateReceiver(FluxReceive.java:419) ~[reactor-netty-0.9.2.RELEASE.jar:0.9.2.RELEASE]
at reactor.netty.channel.FluxReceive.drainReceiver(FluxReceive.java:209) ~[reactor-netty-0.9.2.RELEASE.jar:0.9.2.RELEASE]
at reactor.netty.channel.FluxReceive.onInboundComplete(FluxReceive.java:367) ~[reactor-netty-0.9.2.RELEASE.jar:0.9.2.RELEASE]
at reactor.netty.channel.ChannelOperations.onInboundComplete(ChannelOperations.java:363) ~[reactor-netty-0.9.2.RELEASE.jar:0.9.2.RELEASE]
at reactor.netty.http.server.HttpServerOperations.onInboundNext(HttpServerOperations.java:461) ~[reactor-netty-0.9.2.RELEASE.jar:0.9.2.RELEASE]
at reactor.netty.channel.ChannelOperationsHandler.channelRead(ChannelOperationsHandler.java:89) ~[reactor-netty-0.9.2.RELEASE.jar:0.9.2.RELEASE]
at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:374) ~[netty-transport-4.1.43.Final.jar:4.1.43.Final]
at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:360) ~[netty-transport-4.1.43.Final.jar:4.1.43.Final]
at io.netty.channel.AbstractChannelHandlerContext.fireChannelRead(AbstractChannelHandlerContext.java:352) ~[netty-transport-4.1.43.Final.jar:4.1.43.Final]
at reactor.netty.http.server.HttpTrafficHandler.channelRead(HttpTrafficHandler.java:211) ~[reactor-netty-0.9.2.RELEASE.jar:0.9.2.RELEASE]
at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:374) ~[netty-transport-4.1.43.Final.jar:4.1.43.Final]
at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:360) ~[netty-transport-4.1.43.Final.jar:4.1.43.Final]
at io.netty.channel.AbstractChannelHandlerContext.fireChannelRead(AbstractChannelHandlerContext.java:352) ~[netty-transport-4.1.43.Final.jar:4.1.43.Final]
at io.netty.channel.CombinedChannelDuplexHandler$DelegatingChannelHandlerContext.fireChannelRead(CombinedChannelDuplexHandler.java:438) ~[netty-transport-4.1.43.Final.jar:4.1.43.Final]
at io.netty.handler.codec.ByteToMessageDecoder.fireChannelRead(ByteToMessageDecoder.java:326) ~[netty-codec-4.1.43.Final.jar:4.1.43.Final]
at io.netty.handler.codec.ByteToMessageDecoder.channelRead(ByteToMessageDecoder.java:300) ~[netty-codec-4.1.43.Final.jar:4.1.43.Final]
at io.netty.channel.CombinedChannelDuplexHandler.channelRead(CombinedChannelDuplexHandler.java:253) ~[netty-transport-4.1.43.Final.jar:4.1.43.Final]
at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:374) ~[netty-transport-4.1.43.Final.jar:4.1.43.Final]
at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:360) ~[netty-transport-4.1.43.Final.jar:4.1.43.Final]
at io.netty.channel.AbstractChannelHandlerContext.fireChannelRead(AbstractChannelHandlerContext.java:352) ~[netty-transport-4.1.43.Final.jar:4.1.43.Final]
at io.netty.channel.DefaultChannelPipeline$HeadContext.channelRead(DefaultChannelPipeline.java:1422) ~[netty-transport-4.1.43.Final.jar:4.1.43.Final]
at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:374) ~[netty-transport-4.1.43.Final.jar:4.1.43.Final]
at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:360) ~[netty-transport-4.1.43.Final.jar:4.1.43.Final]
at io.netty.channel.DefaultChannelPipeline.fireChannelRead(DefaultChannelPipeline.java:931) ~[netty-transport-4.1.43.Final.jar:4.1.43.Final]
at io.netty.channel.nio.AbstractNioByteChannel$NioByteUnsafe.read(AbstractNioByteChannel.java:163) ~[netty-transport-4.1.43.Final.jar:4.1.43.Final]
at io.netty.channel.nio.NioEventLoop.processSelectedKey(NioEventLoop.java:700) ~[netty-transport-4.1.43.Final.jar:4.1.43.Final]
at io.netty.channel.nio.NioEventLoop.processSelectedKeysOptimized(NioEventLoop.java:635) ~[netty-transport-4.1.43.Final.jar:4.1.43.Final]
at io.netty.channel.nio.NioEventLoop.processSelectedKeys(NioEventLoop.java:552) ~[netty-transport-4.1.43.Final.jar:4.1.43.Final]
at io.netty.channel.nio.NioEventLoop.run(NioEventLoop.java:514) ~[netty-transport-4.1.43.Final.jar:4.1.43.Final]
at io.netty.util.concurrent.SingleThreadEventExecutor$6.run(SingleThreadEventExecutor.java:1050) ~[netty-common-4.1.43.Final.jar:4.1.43.Final]
at io.netty.util.internal.ThreadExecutorMap$2.run(ThreadExecutorMap.java:74) ~[netty-common-4.1.43.Final.jar:4.1.43.Final]
at io.netty.util.concurrent.FastThreadLocalRunnable.run(FastThreadLocalRunnable.java:30) ~[netty-common-4.1.43.Final.jar:4.1.43.Final]
at java.base/java.lang.Thread.run(Thread.java:834) ~[na:na]
Run Code Online (Sandbox Code Playgroud)
有没有办法以某种方式解除这种验证?
编辑:
似乎休眠 bean 验证正在阻塞。我发现它的解决方案之一是创建自定义spring Validator。在我的情况下,它会是这样的:
@Component
class TestEntryValidator: Validator {
override fun validate(target: Any, errors: Errors) {
ValidationUtils.rejectIfEmptyOrWhitespace(
errors, "data", "field.required")
}
override fun supports(clazz: Class<*>): Boolean {
return TestEntry::class.javaObjectType.isAssignableFrom(clazz)
}
}
Run Code Online (Sandbox Code Playgroud)
它的用法可能如下所示:
@RestController
@RequestMapping("/v1/test")
class TestController(val testSpringValidator: TestEntryValidator) {
@PostMapping("/{type}", consumes = [MediaType.APPLICATION_JSON_VALUE])
fun testPost(@PathVariable type: String, @RequestBody entry: TestEntry): Mono<TestEntry> {
return Mono.fromCallable {
val errors = BeanPropertyBindingResult(
entry,
TestEntry::class.java.name
)
testSpringValidator.validate(entry, errors)
if (errors.allErrors.isEmpty()) {
TestEntry("${entry.data} - $type")
} else {
throw ResponseStatusException(
HttpStatus.BAD_REQUEST,
errors.allErrors.toString()
)
}
}
}
}
data class TestEntry(
val data: String?
)
Run Code Online (Sandbox Code Playgroud)
不幸的是,这迫使您添加一些额外的样板代码,但我现在还没有找到更好的东西。
我在 Spring Boot 应用程序中尝试了相同的行为。好消息是它很容易解决。
当您使用验证注释时,当某个字段未通过约束时,会从文件中加载通知消息。那就是阻塞!
因此,您唯一要做的就是提供要显示的消息,而不是从文件中读取默认值。
例如,如果你想限制一个非空值的字符串字段,使用这个
@NotBlank(message = "Field must not be blank")
private String mandatoryField;
Run Code Online (Sandbox Code Playgroud)
而不是这个
@NotBlank
private String mandatoryField;
Run Code Online (Sandbox Code Playgroud)
就这样!
归档时间: |
|
查看次数: |
1341 次 |
最近记录: |