Skip to content
体验新版
项目
组织
正在加载...
登录
切换导航
打开侧边栏
爱吃血肠
spring-framework
提交
d8a7b96b
S
spring-framework
项目概览
爱吃血肠
/
spring-framework
通知
1
Star
0
Fork
0
代码
文件
提交
分支
Tags
贡献者
分支图
Diff
Issue
0
列表
看板
标记
里程碑
合并请求
0
Wiki
0
Wiki
分析
仓库
DevOps
项目成员
Pages
S
spring-framework
项目概览
项目概览
详情
发布
仓库
仓库
文件
提交
分支
标签
贡献者
分支图
比较
Issue
0
Issue
0
列表
看板
标记
里程碑
合并请求
0
合并请求
0
Pages
分析
分析
仓库分析
DevOps
Wiki
0
Wiki
成员
成员
收起侧边栏
关闭侧边栏
动态
分支图
创建新Issue
提交
Issue看板
前往新版Gitcode,体验更适合开发者的 AI 搜索 >>
提交
d8a7b96b
编写于
10月 18, 2017
作者:
R
Rossen Stoyanchev
浏览文件
操作
浏览文件
下载
电子邮件补丁
差异文件
WebFlux support for "request handled" in controller
Issue: SPR-16087
上级
a3eeda99
变更
5
隐藏空白更改
内联
并排
Showing
5 changed file
with
205 addition
and
13 deletion
+205
-13
spring-webflux/src/main/java/org/springframework/web/reactive/result/method/InvocableHandlerMethod.java
...rk/web/reactive/result/method/InvocableHandlerMethod.java
+61
-1
spring-webflux/src/main/java/org/springframework/web/reactive/result/method/annotation/ControllerMethodResolver.java
...ve/result/method/annotation/ControllerMethodResolver.java
+5
-0
spring-webflux/src/test/java/org/springframework/web/reactive/result/method/InvocableHandlerMethodTests.java
...b/reactive/result/method/InvocableHandlerMethodTests.java
+124
-7
src/docs/asciidoc/web/webflux.adoc
src/docs/asciidoc/web/webflux.adoc
+8
-2
src/docs/asciidoc/web/webmvc.adoc
src/docs/asciidoc/web/webmvc.adoc
+7
-3
未找到文件。
spring-webflux/src/main/java/org/springframework/web/reactive/result/method/InvocableHandlerMethod.java
浏览文件 @
d8a7b96b
...
...
@@ -18,6 +18,8 @@ package org.springframework.web.reactive.result.method;
import
java.lang.reflect.InvocationTargetException
;
import
java.lang.reflect.Method
;
import
java.lang.reflect.ParameterizedType
;
import
java.lang.reflect.Type
;
import
java.util.ArrayList
;
import
java.util.Arrays
;
import
java.util.List
;
...
...
@@ -31,7 +33,10 @@ import reactor.core.publisher.Mono;
import
org.springframework.core.DefaultParameterNameDiscoverer
;
import
org.springframework.core.MethodParameter
;
import
org.springframework.core.ParameterNameDiscoverer
;
import
org.springframework.core.ReactiveAdapter
;
import
org.springframework.core.ReactiveAdapterRegistry
;
import
org.springframework.http.HttpStatus
;
import
org.springframework.http.server.reactive.ServerHttpResponse
;
import
org.springframework.lang.Nullable
;
import
org.springframework.util.ClassUtils
;
import
org.springframework.util.ObjectUtils
;
...
...
@@ -61,6 +66,8 @@ public class InvocableHandlerMethod extends HandlerMethod {
private
ParameterNameDiscoverer
parameterNameDiscoverer
=
new
DefaultParameterNameDiscoverer
();
private
ReactiveAdapterRegistry
reactiveAdapterRegistry
=
new
ReactiveAdapterRegistry
();
public
InvocableHandlerMethod
(
HandlerMethod
handlerMethod
)
{
super
(
handlerMethod
);
...
...
@@ -103,6 +110,18 @@ public class InvocableHandlerMethod extends HandlerMethod {
return
this
.
parameterNameDiscoverer
;
}
/**
* Configure a reactive registry. This is needed for cases where the response
* is fully handled within the controller in combination with an async void
* return value.
* <p>By default this is an instance of {@link ReactiveAdapterRegistry} with
* default settings.
* @param registry the registry to use
*/
public
void
setReactiveAdapterRegistry
(
ReactiveAdapterRegistry
registry
)
{
this
.
reactiveAdapterRegistry
=
registry
;
}
/**
* Invoke the method for the given exchange.
...
...
@@ -117,11 +136,21 @@ public class InvocableHandlerMethod extends HandlerMethod {
return
resolveArguments
(
exchange
,
bindingContext
,
providedArgs
).
flatMap
(
args
->
{
try
{
Object
value
=
doInvoke
(
args
);
HandlerResult
result
=
new
HandlerResult
(
this
,
value
,
getReturnType
(),
bindingContext
);
HttpStatus
status
=
getResponseStatus
();
if
(
status
!=
null
)
{
exchange
.
getResponse
().
setStatusCode
(
status
);
}
MethodParameter
returnType
=
getReturnType
();
ReactiveAdapter
adapter
=
this
.
reactiveAdapterRegistry
.
getAdapter
(
returnType
.
getParameterType
());
boolean
asyncVoid
=
isAsyncVoidReturnType
(
returnType
,
adapter
);
if
((
value
==
null
||
asyncVoid
)
&&
isResponseHandled
(
args
,
exchange
))
{
logger
.
debug
(
"Response fully handled in controller method"
);
return
asyncVoid
?
Mono
.
from
(
adapter
.
toPublisher
(
value
))
:
Mono
.
empty
();
}
HandlerResult
result
=
new
HandlerResult
(
this
,
value
,
returnType
,
bindingContext
);
return
Mono
.
just
(
result
);
}
catch
(
InvocationTargetException
ex
)
{
...
...
@@ -204,6 +233,7 @@ public class InvocableHandlerMethod extends HandlerMethod {
param
.
getParameterType
().
getName
()
+
"' on "
+
getBridgedMethod
().
toGenericString
();
}
@Nullable
private
Object
doInvoke
(
Object
[]
args
)
throws
Exception
{
if
(
logger
.
isTraceEnabled
())
{
logger
.
trace
(
"Invoking '"
+
ClassUtils
.
getQualifiedMethodName
(
getMethod
(),
getBeanType
())
+
...
...
@@ -228,4 +258,34 @@ public class InvocableHandlerMethod extends HandlerMethod {
"on "
+
getBridgedMethod
().
toGenericString
();
}
private
boolean
isAsyncVoidReturnType
(
MethodParameter
returnType
,
@Nullable
ReactiveAdapter
reactiveAdapter
)
{
if
(
reactiveAdapter
!=
null
&&
reactiveAdapter
.
supportsEmpty
())
{
if
(
reactiveAdapter
.
isNoValue
())
{
return
true
;
}
Type
parameterType
=
returnType
.
getGenericParameterType
();
if
(
parameterType
instanceof
ParameterizedType
)
{
ParameterizedType
type
=
(
ParameterizedType
)
parameterType
;
if
(
type
.
getActualTypeArguments
().
length
==
1
)
{
return
Void
.
class
.
equals
(
type
.
getActualTypeArguments
()[
0
]);
}
}
}
return
false
;
}
private
boolean
isResponseHandled
(
Object
[]
args
,
ServerWebExchange
exchange
)
{
if
(
getResponseStatus
()
!=
null
||
exchange
.
isNotModified
())
{
return
true
;
}
for
(
Object
arg
:
args
)
{
if
(
arg
instanceof
ServerHttpResponse
||
arg
instanceof
ServerWebExchange
)
{
return
true
;
}
}
return
false
;
}
}
spring-webflux/src/main/java/org/springframework/web/reactive/result/method/annotation/ControllerMethodResolver.java
浏览文件 @
d8a7b96b
...
...
@@ -82,6 +82,8 @@ class ControllerMethodResolver {
private
final
List
<
HandlerMethodArgumentResolver
>
exceptionHandlerResolvers
;
private
final
ReactiveAdapterRegistry
reactiveAdapterRegistry
;
private
final
Map
<
Class
<?>,
Set
<
Method
>>
initBinderMethodCache
=
new
ConcurrentHashMap
<>(
64
);
...
...
@@ -127,6 +129,8 @@ class ControllerMethodResolver {
addResolversTo
(
registrar
,
reactiveRegistry
,
context
);
this
.
exceptionHandlerResolvers
=
registrar
.
getResolvers
();
this
.
reactiveAdapterRegistry
=
reactiveRegistry
;
initControllerAdviceCaches
(
context
);
}
...
...
@@ -214,6 +218,7 @@ class ControllerMethodResolver {
public
InvocableHandlerMethod
getRequestMappingMethod
(
HandlerMethod
handlerMethod
)
{
InvocableHandlerMethod
invocable
=
new
InvocableHandlerMethod
(
handlerMethod
);
invocable
.
setArgumentResolvers
(
this
.
requestMappingResolvers
);
invocable
.
setReactiveAdapterRegistry
(
this
.
reactiveAdapterRegistry
);
return
invocable
;
}
...
...
spring-webflux/src/test/java/org/springframework/web/reactive/result/method/InvocableHandlerMethodTests.java
浏览文件 @
d8a7b96b
...
...
@@ -16,26 +16,40 @@
package
org.springframework.web.reactive.result.method
;
import
java.io.UnsupportedEncodingException
;
import
java.lang.reflect.Method
;
import
java.time.Duration
;
import
java.time.Instant
;
import
java.util.Arrays
;
import
org.junit.Test
;
import
reactor.core.publisher.Flux
;
import
reactor.core.publisher.Mono
;
import
reactor.test.StepVerifier
;
import
org.springframework.core.io.buffer.DataBuffer
;
import
org.springframework.core.io.buffer.DefaultDataBufferFactory
;
import
org.springframework.http.HttpStatus
;
import
org.springframework.http.server.reactive.ServerHttpResponse
;
import
org.springframework.lang.Nullable
;
import
org.springframework.mock.http.server.reactive.test.MockServerHttpRequest
;
import
org.springframework.mock.web.test.server.MockServerWebExchange
;
import
org.springframework.web.bind.annotation.ResponseStatus
;
import
org.springframework.web.reactive.BindingContext
;
import
org.springframework.web.reactive.HandlerResult
;
import
org.springframework.web.server.ServerWebExchange
;
import
org.springframework.web.server.UnsupportedMediaTypeStatusException
;
import
static
org
.
hamcrest
.
Matchers
.*;
import
static
org
.
junit
.
Assert
.*;
import
static
org
.
hamcrest
.
Matchers
.
is
;
import
static
org
.
junit
.
Assert
.
assertEquals
;
import
static
org
.
junit
.
Assert
.
assertNull
;
import
static
org
.
junit
.
Assert
.
assertThat
;
import
static
org
.
junit
.
Assert
.
fail
;
import
static
org
.
mockito
.
Mockito
.
any
;
import
static
org
.
mockito
.
Mockito
.*;
import
static
org
.
springframework
.
web
.
method
.
ResolvableMethod
.*;
import
static
org
.
mockito
.
Mockito
.
mock
;
import
static
org
.
mockito
.
Mockito
.
when
;
import
static
org
.
springframework
.
mock
.
http
.
server
.
reactive
.
test
.
MockServerHttpRequest
.
get
;
import
static
org
.
springframework
.
web
.
method
.
ResolvableMethod
.
on
;
/**
* Unit tests for {@link InvocableHandlerMethod}.
...
...
@@ -45,10 +59,72 @@ import static org.springframework.web.method.ResolvableMethod.*;
*/
public
class
InvocableHandlerMethodTests
{
private
final
MockServerWebExchange
exchange
=
MockServerWebExchange
.
from
(
MockServerHttpRequest
.
get
(
"http://localhost:8080/path"
));
private
final
MockServerWebExchange
exchange
=
MockServerWebExchange
.
from
(
get
(
"http://localhost:8080/path"
));
@Test
public
void
invokeAndHandle_VoidWithResponseStatus
()
throws
Exception
{
Method
method
=
on
(
VoidController
.
class
).
mockCall
(
VoidController:
:
responseStatus
).
method
();
HandlerResult
result
=
invoke
(
new
VoidController
(),
method
).
block
(
Duration
.
ZERO
);
assertNull
(
"Expected no result (i.e. fully handled)"
,
result
);
assertEquals
(
HttpStatus
.
BAD_REQUEST
,
this
.
exchange
.
getResponse
().
getStatusCode
());
}
@Test
public
void
invokeAndHandle_withResponse
()
throws
Exception
{
ServerHttpResponse
response
=
this
.
exchange
.
getResponse
();
Method
method
=
on
(
VoidController
.
class
).
mockCall
(
c
->
c
.
response
(
response
)).
method
();
HandlerResult
result
=
invoke
(
new
VoidController
(),
method
,
resolverFor
(
Mono
.
just
(
response
)))
.
block
(
Duration
.
ZERO
);
assertNull
(
"Expected no result (i.e. fully handled)"
,
result
);
assertEquals
(
"bar"
,
this
.
exchange
.
getResponse
().
getHeaders
().
getFirst
(
"foo"
));
}
@Test
public
void
invokeAndHandle_withResponseAndMonoVoid
()
throws
Exception
{
ServerHttpResponse
response
=
this
.
exchange
.
getResponse
();
Method
method
=
on
(
VoidController
.
class
).
mockCall
(
c
->
c
.
responseMonoVoid
(
response
)).
method
();
HandlerResult
result
=
invoke
(
new
VoidController
(),
method
,
resolverFor
(
Mono
.
just
(
response
)))
.
block
(
Duration
.
ZERO
);
assertNull
(
"Expected no result (i.e. fully handled)"
,
result
);
assertEquals
(
"body"
,
this
.
exchange
.
getResponse
().
getBodyAsString
().
block
(
Duration
.
ZERO
));
}
@Test
public
void
invokeAndHandle_withExchange
()
throws
Exception
{
Method
method
=
on
(
VoidController
.
class
).
mockCall
(
c
->
c
.
exchange
(
exchange
)).
method
();
HandlerResult
result
=
invoke
(
new
VoidController
(),
method
,
resolverFor
(
Mono
.
just
(
this
.
exchange
)))
.
block
(
Duration
.
ZERO
);
assertNull
(
"Expected no result (i.e. fully handled)"
,
result
);
assertEquals
(
"bar"
,
this
.
exchange
.
getResponse
().
getHeaders
().
getFirst
(
"foo"
));
}
@Test
public
void
invokeAndHandle_withExchangeAndMonoVoid
()
throws
Exception
{
Method
method
=
on
(
VoidController
.
class
).
mockCall
(
c
->
c
.
exchangeMonoVoid
(
exchange
)).
method
();
HandlerResult
result
=
invoke
(
new
VoidController
(),
method
,
resolverFor
(
Mono
.
just
(
this
.
exchange
)))
.
block
(
Duration
.
ZERO
);
assertNull
(
"Expected no result (i.e. fully handled)"
,
result
);
assertEquals
(
"body"
,
this
.
exchange
.
getResponse
().
getBodyAsString
().
block
(
Duration
.
ZERO
));
}
@Test
public
void
invokeAndHandle_withNotModified
()
throws
Exception
{
ServerWebExchange
exchange
=
MockServerWebExchange
.
from
(
MockServerHttpRequest
.
get
(
"/"
).
ifModifiedSince
(
10
*
1000
*
1000
));
Method
method
=
on
(
VoidController
.
class
).
mockCall
(
c
->
c
.
notModified
(
exchange
)).
method
();
HandlerResult
result
=
invoke
(
new
VoidController
(),
method
,
resolverFor
(
Mono
.
just
(
exchange
)))
.
block
(
Duration
.
ZERO
);
assertNull
(
"Expected no result (i.e. fully handled)"
,
result
);
}
@Test
public
void
invokeMethodWithNoArguments
()
throws
Exception
{
Method
method
=
on
(
TestController
.
class
).
mockCall
(
TestController:
:
noArgs
).
method
();
...
...
@@ -146,7 +222,7 @@ public class InvocableHandlerMethodTests {
private
Mono
<
HandlerResult
>
invoke
(
Object
handler
,
Method
method
)
{
return
this
.
invoke
(
handler
,
method
,
new
HandlerMethodArgumentResolver
[
0
]);
return
invoke
(
handler
,
method
,
new
HandlerMethodArgumentResolver
[
0
]);
}
private
Mono
<
HandlerResult
>
invoke
(
Object
handler
,
Method
method
,
...
...
@@ -195,4 +271,45 @@ public class InvocableHandlerMethodTests {
}
}
@SuppressWarnings
(
"unused"
)
private
static
class
VoidController
{
@ResponseStatus
(
HttpStatus
.
BAD_REQUEST
)
public
void
responseStatus
()
{
}
public
void
response
(
ServerHttpResponse
response
)
{
response
.
getHeaders
().
add
(
"foo"
,
"bar"
);
}
public
Mono
<
Void
>
responseMonoVoid
(
ServerHttpResponse
response
)
{
return
response
.
writeWith
(
getBody
(
"body"
));
}
public
void
exchange
(
ServerWebExchange
exchange
)
{
exchange
.
getResponse
().
getHeaders
().
add
(
"foo"
,
"bar"
);
}
public
Mono
<
Void
>
exchangeMonoVoid
(
ServerWebExchange
exchange
)
{
return
exchange
.
getResponse
().
writeWith
(
getBody
(
"body"
));
}
@Nullable
public
String
notModified
(
ServerWebExchange
exchange
)
{
if
(
exchange
.
checkNotModified
(
Instant
.
ofEpochMilli
(
1000
*
1000
)))
{
return
null
;
}
return
"body"
;
}
private
Flux
<
DataBuffer
>
getBody
(
String
body
)
{
try
{
return
Flux
.
just
(
new
DefaultDataBufferFactory
().
wrap
(
body
.
getBytes
(
"UTF-8"
)));
}
catch
(
UnsupportedEncodingException
ex
)
{
throw
new
IllegalStateException
(
ex
);
}
}
}
}
src/docs/asciidoc/web/webflux.adoc
浏览文件 @
d8a7b96b
...
...
@@ -1029,8 +1029,14 @@ from the request path.
|
An
API
for
model
and
view
rendering
scenarios
.
|`
void
`
|
For
use
in
method
that
don
't write the response body; or methods where the view name is
supposed to be determined implicitly from the request path.
|
A
method
with
a
`
void
`,
possibly
async
(
e
.
g
.
`
Mono
<
Void
>`),
return
type
(
or
a
`
null
`
return
value
)
is
considered
to
have
fully
handled
the
response
if
it
also
has
a
`
ServerHttpResponse
`,
or
a
`
ServerWebExchange
`
argument
,
or
an
`@
ResponseStatus
`
annotation
.
The
same
is
true
also
if
the
controller
has
made
a
positive
ETag
or
lastModified
timestamp
check
.
//
TODO
(
see
<<
webflux
-
caching
-
etag
-
lastmodified
>>
for
details
)
If
none
of
the
above
is
true
,
a
`
void
`
return
type
may
also
indicate
"no response body"
for
REST
controllers
,
or
default
view
name
selection
for
HTML
controllers
.
|`
Flux
<
ServerSentEvent
>`,
`
Observable
<
ServerSentEvent
>`,
or
other
reactive
type
|
Emit
server
-
sent
events
;
the
`
SeverSentEvent
`
wrapper
can
be
omitted
when
only
data
needs
...
...
src/docs/asciidoc/web/webmvc.adoc
浏览文件 @
d8a7b96b
...
...
@@ -1684,9 +1684,13 @@ through a `RequestToViewNameTranslator`.
|The view and model attributes to use, and optionally a response status.
|`void`
|For use in methods that declare a `ServletResponse` or `OutputStream` argument and write
to the response body; or if the view name is supposed to be implicitly determined through a
`RequestToViewNameTranslator`.
|A method with a `void` return type (or `null` return value) is considered to have fully
handled the response if it also has a `ServletResponse`, or an `OutputStream` argument, or an
`@ResponseStatus` annotation. The same is true also if the controller has made a positive
ETag or lastModified timestamp check (see
<
<
mvc-caching-etag-lastmodified
>
> for details).
If none of the above is true, a `void` return type may also indicate "no response body" for
REST controllers, or default view name selection for HTML controllers.
|`Callable
<V>
`
|Produce any of the above return values asynchronously in a Spring MVC managed thread.
...
...
编辑
预览
Markdown
is supported
0%
请重试
或
添加新附件
.
添加附件
取消
You are about to add
0
people
to the discussion. Proceed with caution.
先完成此消息的编辑!
取消
想要评论请
注册
或
登录