Todas las solicitudes y respuestas manejadas por nuestro controlador resorte resto tiene una sección común, que tiene ciertos valores:
{
"common": {
"requestId": "foo-bar-123",
"otherKey1": "value1",
"otherKey2": "value2",
"otherKey3": "value3"
},
...
}
Actualmente todas mis funciones de controlador están leyendo el common
y lo copia en la respuesta manualmente. Me gustaría moverlo a un interceptor de algún tipo.
Traté de hacer esto utilizando ControllerAdvice
y ThreadLocal
:
@ControllerAdvice
public class RequestResponseAdvice extends RequestBodyAdviceAdapter
implements ResponseBodyAdvice<MyGenericPojo> {
private ThreadLocal<Common> commonThreadLocal = new ThreadLocal<>();
/* Request */
@Override
public boolean supports(
MethodParameter methodParameter, Type type, Class<? extends HttpMessageConverter<?>> aClass) {
return MyGenericPojo.class.isAssignableFrom(methodParameter.getParameterType());
}
@Override
public Object afterBodyRead(
Object body,
HttpInputMessage inputMessage,
MethodParameter parameter,
Type targetType,
Class<? extends HttpMessageConverter<?>> converterType) {
var common = (MyGenericPojo)body.getCommon();
if (common.getRequestId() == null) {
common.setRequestId(generateNewRequestId());
}
commonThreadLocal(common);
return body;
}
/* Response */
@Override
public boolean supports(
MethodParameter returnType, Class<? extends HttpMessageConverter<?>> converterType) {
return MyGenericPojo.class.isAssignableFrom(returnType.getParameterType());
}
@Override
public MyGenericPojo beforeBodyWrite(
MyGenericPojo body,
MethodParameter returnType,
MediaType selectedContentType,
Class<? extends HttpMessageConverter<?>> selectedConverterType,
ServerHttpRequest request,
ServerHttpResponse response) {
body.setCommon(commonThreadLocal.get());
commonThreadLocal.remove();
return body;
}
}
Esto funciona cuando pruebo el envío de una solicitud a la vez. Pero, ¿es garantizado que afterBodyRead
y beforeBodyWrite
es llamado en el mismo hilo, cuando están llegando varias solicitudes?
Si no es así, o incluso de otro modo, ¿cuál es la mejor manera de hacer esto?
Creo que no hay necesidad de su propia ThreadLocal
puede utilizar atributos de la petición.
@Override
public Object afterBodyRead(
Object body,
HttpInputMessage inputMessage,
MethodParameter parameter,
Type targetType,
Class<? extends HttpMessageConverter<?>> converterType) {
var common = ((MyGenericPojo) body).getCommon();
if (common.getRequestId() == null) {
common.setRequestId(generateNewRequestId());
}
Optional.ofNullable((ServletRequestAttributes) RequestContextHolder.getRequestAttributes())
.map(ServletRequestAttributes::getRequest)
.ifPresent(request -> {request.setAttribute(Common.class.getName(), common);});
return body;
}
@Override
public MyGenericPojo beforeBodyWrite(
MyGenericPojo body,
MethodParameter returnType,
MediaType selectedContentType,
Class<? extends HttpMessageConverter<?>> selectedConverterType,
ServerHttpRequest request,
ServerHttpResponse response) {
Optional.ofNullable(RequestContextHolder.getRequestAttributes())
.map(rc -> rc.getAttribute(Common.class.getName(), RequestAttributes.SCOPE_REQUEST))
.ifPresent(o -> {
Common common = (Common) o;
body.setCommon(common);
});
return body;
}
EDITAR
Optional
s puede ser reemplazado con
RequestContextHolder.getRequestAttributes().setAttribute(Common.class.getName(),common,RequestAttributes.SCOPE_REQUEST);
RequestContextHolder.getRequestAttributes().getAttribute(Common.class.getName(),RequestAttributes.SCOPE_REQUEST);
EDIT 2
Sobre la seguridad hilo
1) aplicación web Spring basado en servlet estándar tenemos escenario hilo-por-petición. Solicitud es procesada por uno de los subprocesos de trabajo a través de todos los filtros y rutinas. La cadena de procesamiento será ejecutado por el mismo hilo de principio a fin. Así afterBodyRead
y beforeBodyWrite
garantizada para ser ejecutado por el mismo hilo para una determinada solicitud.
2) Su RequestResponseAdvice por sí mismo no tiene estado. Utilizamos RequestContextHolder.getRequestAttributes()
que es ThreadLocal y declarada como
private static final ThreadLocal<RequestAttributes> requestAttributesHolder =
new NamedThreadLocal<>("Request attributes");
Y javadoc ThreadLocal afirma:
su clase proporciona las variables de subproceso local. Estas variables difieren de sus homólogos normales en que cada hilo que accede a uno (a través de su método GET o set) tiene su propia copia, inicializado de forma independiente de la variable.
Así que no veo ningún problema hilo de seguridad en este sulotion.