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 实例(很遗憾,您需要对其进行强制转换),因此您可以对其进行迭代并获取所需的所有参数。