Spring AOP:如何从方面的URI模板中读取路径变量值?
Spring AOP: How to read path variable value from URI template in aspect?
我想创建 Spring 方面,它将方法参数(由自定义注释注释)设置为特定 class 的实例,该实例由 URI 模板中的 id 标识。路径变量名是注解的参数。与 Spring @PathVariable
非常相似。
所以控制器方法看起来像:
@RestController
@RequestMapping("/testController")
public class TestController {
@RequestMapping(value = "/order/{orderId}/delete", method = RequestMethod.GET)
public ResponseEntity<?> doSomething(
@GetOrder("orderId") Order order) {
// do something with order
}
}
代替classic:
@RestController
@RequestMapping("/testController")
public class TestController {
@RequestMapping(value = "/order/{orderId}/delete", method = RequestMethod.GET)
public ResponseEntity<?> doSomething(
@PathVariable("orderId") Long orderId) {
Order order = orderRepository.findById(orderId);
// do something with order
}
}
注释来源:
// Annotation
@Target(ElementType.PARAMETER)
@Retention(RetentionPolicy.RUNTIME)
public @interface GetOrder{
String value() default "";
}
方面来源:
// Aspect controlled by the annotation
@Aspect
@Component
public class GetOrderAspect {
@Around( // Assume the setOrder method is called around controller method )
public Object setOrder(ProceedingJoinPoint jp) throws Throwable{
MethodSignature signature = (MethodSignature) jp.getSignature();
@SuppressWarnings("rawtypes")
Class[] types = signature.getParameterTypes();
Method method = signature.getMethod();
Annotation[][] annotations = method.getParameterAnnotations();
Object[] values = jp.getArgs();
for (int parameter = 0; parameter < types.length; parameter++) {
Annotation[] parameterAnnotations = annotations[parameter];
if (parameterAnnotations == null) continue;
for (Annotation annotation: parameterAnnotations) {
// Annotation is instance of @GetOrder
if (annotation instanceof GetOrder) {
String pathVariable = (GetOrder)annotation.value();
// How to read actual path variable value from URI template?
// In this example case {orderId} from /testController/order/{orderId}/delete
HttpServletRequest request = ((ServletRequestAttributes)RequestContextHolder
.currentRequestAttributes()).getRequest();
????? // Now what?
}
} // for each annotation
} // for each parameter
return jp.proceed();
}
}
更新 04/Apr/2017:
给出的答案回答了问题->因此被接受。
给出的答案从现有 Spring 工具的不同角度解决了问题,而不会冒新方面的实施问题的风险。早知道就不会问这个问题了
谢谢!
最简单的方法是使用 @ModelAttribute
,它可以进入 @ControllerAdvice
以在多个控制器之间共享。
@ModelAttribute("order")
public Order getOrder(@PathVariable("orderId") String orderId) {
return orderRepository.findById(orderId);
}
@DeleteMapping("/order/{orderId}")
public ResponseEntity<?> doSomething(@ModelAttribute("order") Order order) {
// do something with order
}
另一种方法是实现您自己的 PathVariableMethodArgumentResolver
支持 Order
,或者注册一个 Converter<String, Order>
,现有的 @PathVariable
系统可以使用。
假设它始终是带有注释的第一个参数,也许您想这样做:
package de.scrum_master.aspect;
import java.lang.annotation.Annotation;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.stereotype.Component;
import de.scrum_master.app.GetOrder;
@Aspect
@Component
public class GetOrderAspect {
@Around("execution(* *(@de.scrum_master.app.GetOrder (*), ..))")
public Object setOrder(ProceedingJoinPoint thisJoinPoint) throws Throwable {
MethodSignature methodSignature = (MethodSignature) thisJoinPoint.getSignature();
Annotation[][] annotationMatrix = methodSignature.getMethod().getParameterAnnotations();
for (Annotation[] annotations : annotationMatrix) {
for (Annotation annotation : annotations) {
if (annotation instanceof GetOrder) {
System.out.println(thisJoinPoint);
System.out.println(" annotation = " + annotation);
System.out.println(" annotation value = " + ((GetOrder) annotation).value());
}
}
}
return thisJoinPoint.proceed();
}
}
控制台日志如下所示:
execution(ResponseEntity de.scrum_master.app.TestController.doSomething(Order))
annotation = @de.scrum_master.app.GetOrder(value=orderId)
annotation value = orderId
如果参数注释可以出现在任意位置,您也可以使用切入点 execution(* *(..))
但这不会非常有效,因为它会捕获应用程序中每个组件的所有方法执行。所以你至少应该将它限制为 REST 控制器 and/or 方法,请求映射如下:
@Around("execution(@org.springframework.web.bind.annotation.RequestMapping * (@org.springframework.web.bind.annotation.RestController *).*(..))")
这个的一个变体是
@Around(
"execution(* (@org.springframework.web.bind.annotation.RestController *).*(..)) &&" +
"@annotation(org.springframework.web.bind.annotation.RequestMapping)"
)
如果您已经可以访问 HttpServletRequest
,您可以使用 HandlerMapping.URI_TEMPLATE_VARIABLES_ATTRIBUTE
spring 模板来 select 映射请求中的所有属性。你可以这样使用它:
request.getAttribute(HandlerMapping.URI_TEMPLATE_VARIABLES_ATTRIBUTE)
结果是一个 Map
实例(很遗憾,您需要对其进行强制转换),因此您可以对其进行迭代并获取所需的所有参数。
我想创建 Spring 方面,它将方法参数(由自定义注释注释)设置为特定 class 的实例,该实例由 URI 模板中的 id 标识。路径变量名是注解的参数。与 Spring @PathVariable
非常相似。
所以控制器方法看起来像:
@RestController
@RequestMapping("/testController")
public class TestController {
@RequestMapping(value = "/order/{orderId}/delete", method = RequestMethod.GET)
public ResponseEntity<?> doSomething(
@GetOrder("orderId") Order order) {
// do something with order
}
}
代替classic:
@RestController
@RequestMapping("/testController")
public class TestController {
@RequestMapping(value = "/order/{orderId}/delete", method = RequestMethod.GET)
public ResponseEntity<?> doSomething(
@PathVariable("orderId") Long orderId) {
Order order = orderRepository.findById(orderId);
// do something with order
}
}
注释来源:
// Annotation
@Target(ElementType.PARAMETER)
@Retention(RetentionPolicy.RUNTIME)
public @interface GetOrder{
String value() default "";
}
方面来源:
// Aspect controlled by the annotation
@Aspect
@Component
public class GetOrderAspect {
@Around( // Assume the setOrder method is called around controller method )
public Object setOrder(ProceedingJoinPoint jp) throws Throwable{
MethodSignature signature = (MethodSignature) jp.getSignature();
@SuppressWarnings("rawtypes")
Class[] types = signature.getParameterTypes();
Method method = signature.getMethod();
Annotation[][] annotations = method.getParameterAnnotations();
Object[] values = jp.getArgs();
for (int parameter = 0; parameter < types.length; parameter++) {
Annotation[] parameterAnnotations = annotations[parameter];
if (parameterAnnotations == null) continue;
for (Annotation annotation: parameterAnnotations) {
// Annotation is instance of @GetOrder
if (annotation instanceof GetOrder) {
String pathVariable = (GetOrder)annotation.value();
// How to read actual path variable value from URI template?
// In this example case {orderId} from /testController/order/{orderId}/delete
HttpServletRequest request = ((ServletRequestAttributes)RequestContextHolder
.currentRequestAttributes()).getRequest();
????? // Now what?
}
} // for each annotation
} // for each parameter
return jp.proceed();
}
}
更新 04/Apr/2017:
谢谢!
最简单的方法是使用 @ModelAttribute
,它可以进入 @ControllerAdvice
以在多个控制器之间共享。
@ModelAttribute("order")
public Order getOrder(@PathVariable("orderId") String orderId) {
return orderRepository.findById(orderId);
}
@DeleteMapping("/order/{orderId}")
public ResponseEntity<?> doSomething(@ModelAttribute("order") Order order) {
// do something with order
}
另一种方法是实现您自己的 PathVariableMethodArgumentResolver
支持 Order
,或者注册一个 Converter<String, Order>
,现有的 @PathVariable
系统可以使用。
假设它始终是带有注释的第一个参数,也许您想这样做:
package de.scrum_master.aspect;
import java.lang.annotation.Annotation;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.stereotype.Component;
import de.scrum_master.app.GetOrder;
@Aspect
@Component
public class GetOrderAspect {
@Around("execution(* *(@de.scrum_master.app.GetOrder (*), ..))")
public Object setOrder(ProceedingJoinPoint thisJoinPoint) throws Throwable {
MethodSignature methodSignature = (MethodSignature) thisJoinPoint.getSignature();
Annotation[][] annotationMatrix = methodSignature.getMethod().getParameterAnnotations();
for (Annotation[] annotations : annotationMatrix) {
for (Annotation annotation : annotations) {
if (annotation instanceof GetOrder) {
System.out.println(thisJoinPoint);
System.out.println(" annotation = " + annotation);
System.out.println(" annotation value = " + ((GetOrder) annotation).value());
}
}
}
return thisJoinPoint.proceed();
}
}
控制台日志如下所示:
execution(ResponseEntity de.scrum_master.app.TestController.doSomething(Order))
annotation = @de.scrum_master.app.GetOrder(value=orderId)
annotation value = orderId
如果参数注释可以出现在任意位置,您也可以使用切入点 execution(* *(..))
但这不会非常有效,因为它会捕获应用程序中每个组件的所有方法执行。所以你至少应该将它限制为 REST 控制器 and/or 方法,请求映射如下:
@Around("execution(@org.springframework.web.bind.annotation.RequestMapping * (@org.springframework.web.bind.annotation.RestController *).*(..))")
这个的一个变体是
@Around(
"execution(* (@org.springframework.web.bind.annotation.RestController *).*(..)) &&" +
"@annotation(org.springframework.web.bind.annotation.RequestMapping)"
)
如果您已经可以访问 HttpServletRequest
,您可以使用 HandlerMapping.URI_TEMPLATE_VARIABLES_ATTRIBUTE
spring 模板来 select 映射请求中的所有属性。你可以这样使用它:
request.getAttribute(HandlerMapping.URI_TEMPLATE_VARIABLES_ATTRIBUTE)
结果是一个 Map
实例(很遗憾,您需要对其进行强制转换),因此您可以对其进行迭代并获取所需的所有参数。