简体   繁体   English

在运行时更改 Spring RequestMapping 注释值

[英]Changing Spring RequestMapping annotation value at runtime

I am trying to change the value of the RequestMapping annotation at runtime for a HTTP GET method - hello (which returns a simple string) inside a rest service class - SpringRestController .我正在尝试在运行时为 HTTP GET 方法更改 RequestMapping 注释的值 -你好(它返回一个简单的字符串)在 rest 服务 class 内 - SpringRRR

The value of the uri defined in the @RequestMapping annotation on the hello method is "/hello/{name}" . hello 方法的 @RequestMapping 注解中定义的 uri 的值为"/hello/{name}" I am able to change the value of the annotation at runtime to "hi/{name}" using reflection in the constructor of the SpringRestController class.我可以在 SpringRestController class 的构造函数中使用反射将运行时注释的值更改为“hi/{name}”

I am able to verify the modified value by printing the value of the annotation inside an init method annotated with @PostConstruct annotation and also inside another controller.我可以通过在使用@PostConstruct 注释注释的 init 方法以及另一个 controller 内部打印注释的值来验证修改后的值。 However, when I am trying to access the GET method in a browser:但是,当我尝试在浏览器中访问 GET 方法时:

with the modified value - http://localhost:9090/spring-boot-rest/rest/hi/Pradeep ( does not work )使用修改后的值 - http://localhost:9090/spring-boot-rest/rest/hi/Pradeep不起作用

with the original value - http://localhost:9090/spring-boot-rest/rest/hello/Pradeep ( works fine )使用原始值 - http://localhost:9090/spring-boot-rest/rest/hello/Pradeep工作正常


I expect the HTTP GET method hello to be accessible using the modified path value at runtime - "/hi/{name}" instead of the original path value - "/hello/{name}" .我希望 HTTP GET 方法 hello 可以在运行时使用修改后的路径值访问 - "/hi/{name}"而不是原始路径值 - "/hello/{name}" PS - This is a requirement for us and needs to be done this way so that value of @RequestMapping can be configured externally without changes to the source code. PS - 这是对我们的要求,需要以这种方式完成,以便可以在外部配置 @RequestMapping 的值而无需更改源代码。
Here is the code - SpringRestController.java这是代码 - SpringRestController.java

package com.example.spring.rest.controller;
import javax.annotation.PostConstruct;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import com.example.spring.rest.custom.annotations.ConfigurableRequestMapping;
import com.example.spring.rest.reflection.ReflectionUtils;
@RestController
@RequestMapping("/rest")
public class SpringRestController {

    public SpringRestController() {
            RequestMapping rm = SpringRestController.class.getAnnotation(RequestMapping.class);
            System.out.println("Old annotation : " + rm.value()[0]);
            RequestMapping rmNew = new ConfigurableRequestMapping("/rest");
            ReflectionUtils.alterAnnotationValueJDK8_v2(SpringRestController.class, RequestMapping.class, rmNew);
            RequestMapping rmModified = SpringRestController.class.getAnnotation(RequestMapping.class);
            System.out.println("Constructor -> New annotation : " + rmModified.value()[0]);
    }

    @RequestMapping(value = "/hello/{name}")
    public String hello(@PathVariable String name) {
        System.out.println("Name : " + name);
        return "Hello " + name;
    }

    @PostConstruct
    private void init(){
        System.out.println("Annotations initialization post construct.");
        RequestMapping rmModified = SpringRestController.class.getAnnotation(RequestMapping.class);
        System.out.println("Init method -> New annotation : " + rmModified.value()[0]);
    }
}

Code for changing annotation value -更改注释值的代码 -

ReflectionUtils.java ReflectionUtils.java

package com.example.spring.rest.reflection;
import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import org.springframework.web.bind.annotation.RequestMapping;
import com.example.spring.rest.controller.SpringRestController;
import com.example.spring.rest.custom.annotations.ConfigurableRequestMapping;
import com.example.spring.rest.utils.PropertyReader;

public class ReflectionUtils {

    @SuppressWarnings("unchecked")
    public static Object changeAnnotationValue(Annotation annotation, String key, Object newValue){
        Object handler = Proxy.getInvocationHandler(annotation);
        Field f;
        try {
            f = handler.getClass().getDeclaredField("memberValues");
        } catch (NoSuchFieldException | SecurityException e) {
            throw new IllegalStateException(e);
        }
        f.setAccessible(true);
        Map<String, Object> memberValues;
        try {
            memberValues = (Map<String, Object>) f.get(handler);
        } catch (IllegalArgumentException | IllegalAccessException e) {
            throw new IllegalStateException(e);
        }
        Object oldValue = memberValues.get(key);
        if (oldValue == null || oldValue.getClass() != newValue.getClass()) {
            throw new IllegalArgumentException();
        }
        memberValues.put(key,newValue);
        return oldValue;
    }
}

This is not possible to change the annotation value in runtime since Spring already registered that value.由于 Spring 已经注册了该值,因此无法在运行时更改注释值。 Aside from being curious about what do you really try to achieve, feel free to use multiple @PathVariable parameters, and handle the evaluation yourself.除了对您真正尝试实现的目标感到好奇之外,您还可以随意使用多个@PathVariable参数并自己处理评估。

// Either hardcoded values or loaded from elsewhere
private static List<String> GREETINGS = Arrays.asList("Hello", "Hi");

...

@GetMapping(value = "/{greeting}/{name}")
public String greeting(@PathVariable String greeting, @PathVariable String name) {
    System.out.println("Name : " + name);
    if (GREETINGS.stream().anyMatch(greeting::equalsIgnoreCase)) {
        return greeting + " " + name;
    }
    throw new ResponseStatusException(HttpStatus.BAD_REQUEST, 
        "Unknown greeting " + greeting, e);
}

Moreover, the point of the REST API endpoints is to be predictable .此外,REST API 端点的点是可预测的。 What you try to achieve seems like a contradiction to it.你试图达到的目标似乎与它相矛盾。 You can have multiple endpoints such as /hi/{name} and /hello/{name} , however, in this particular case either the usage of multiple parameters is correct, or this following endpoint that respects the resource and uses @RequestParam .您可以有多个端点,例如/hi/{name}/hello/{name} ,但是,在这种特殊情况下,多个参数的使用是正确的,或者以下端点尊重资源并使用@RequestParam I'd design it rather with this way since greeting is the resource.我宁愿用这种方式设计它,因为问候是资源。

  • A sample endpoint: /greeting?greeting={greeting}&name={name}示例端点: /greeting?greeting={greeting}&name={name}
  • A sample call: /greeting?greeting=Hello&name=Pradeep%20Prabhakaran示例调用: /greeting?greeting=Hello&name=Pradeep%20Prabhakaran

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM