Spring boot 使用@Endpoint注解自定义端点, 不能通过 Restfult 访问问题 原因分析

Spring boot 使用@Endpoint注解自定义端点, 不能通过 Restfult 访问问题 原因分析,第1张

Spring boot 使用@Endpoint注解自定义端点, 不能通过 Restfult 访问问题 原因分析 1、使用@Endpoint注解自定义端点

参考 spring-boot-starter-actuator.jar 包健康检查端点源码 org.springframework.boot.actuate.health.HealthEndpoint 实现

引入依赖

  org.springframework.boot
  spring-boot-starter-actuator

自定义端点代码

注意: @EndPoint中的id不能使用驼峰法,需要以-分割。

默认的基础路径是/actuator,如果一个端点配置的 id 是my-endpoint,那么它的全路径就是/actuator/my-endpoint 

@Selector 的含义是让这个访问路径变成restful风格: /actuator/my-endpoint/{name}

@Component
@Endpoint(id = "my-endpoint")
public class MyEndpoint {
    @ReadOperation
    public String get(@Selector String name) {
        return  name;
    }
}
配置
// management.endpoints.web.exposure.include=my-endpoint
management.endpoints.web.exposure.include=*
查询端点列表:

通过端点的基础路径查询端点列表:  http://localhost:9080/actuator

health 组件健康信息访问路径:  http://localhost:9080/actuator/health/{component}

我们可以通过 http://localhost:9080/actuator/health/consul 查看 consul的健康信息

而对应的自定义端点访问路径:  http://localhost:9080/actuator/my-endpoint/{arg0}

访问 http://localhost:9080/actuator/my-endpoint/abc 却报 404

其可以正常访问路径变成:  http://localhost:9080/actuator/my-endpoint/{arg0}?name=abc

为什么自定义端点restfult风格访问失效了呢?

经过断点发现 WebEndpointDiscoverer.createOperation() 在绑定访问路径时, 自定义端点 通过 DiscoveredOperationMethod 获取到的方法参数名变为 arg0.

而 DiscoveredOperationMethod 是通过 jdk 的 method.getParameters() 获取参数, 其代码如下:

private native Parameter[] getParameters0();

public Parameter[] getParameters() {
    // TODO: This may eventually need to be guarded by security
    // mechanisms similar to those in Field, Method, etc.
    //
    // Need to copy the cached array to prevent users from messing
    // with it.  Since parameters are immutable, we can
    // shallow-copy.
    return privateGetParameters().clone();
}

private Parameter[] synthesizeAllParams() {
    final int realparams = getParameterCount();
    final Parameter[] out = new Parameter[realparams];
    for (int i = 0; i < realparams; i++)
        // TODO: is there a way to synthetically derive the
        // modifiers?  Probably not in the general case, since
        // we'd have no way of knowing about them, but there
        // may be specific cases.
        out[i] = new Parameter("arg" + i, 0, this, i);
    return out;
}

private Parameter[] privateGetParameters() {
    // Use tmp to avoid multiple writes to a volatile.
    Parameter[] tmp = parameters;

    if (tmp == null) {

        // Otherwise, go to the JVM to get them
        try {
            tmp = getParameters0();
        } catch(IllegalArgumentException e) {
            // Rethrow ClassFormatErrors
            throw new MalformedParametersException("Invalid constant pool index");
        }

        // If we get back nothing, then synthesize parameters
        if (tmp == null) {
            hasRealParameterData = false;
            tmp = synthesizeAllParams();
        } else {
            hasRealParameterData = true;
            verifyParameters(tmp);
        }

        parameters = tmp;
    }

    return tmp;
}


自定义端点最终走到 native 方法 getParameters0() 获取不到方法参数信息, 交由 synthesizeAllParams() 方法得到 arg0 参数.

而actuator自带端点 走到 native 方法 getParameters0() 可以获取静态的方法的参数信息, 显然与代码编译有关.

于是搜索 method.getParameters() 得到一些解释:

在Java8之前,代码编译为class文件后,方法参数的类型是固定的,但参数名称却丢失了,这和动态语言严重依赖参数名称形成了鲜明对比。(java是静态语言,所以入参名称叫什么其实无所谓的)

java1.8以后,官方提供了反射的方法能获取到接口的参数名称。并且需要在javac编译时,加上-parameters参数才行。

后查看spring boot的源码发现, 都需要加上 -parameters 编译

./spring-boot-project/spring-boot-docs/src/docs/asciidoc/features/testing.adoc:TIP: If you are using `@SpyBean` to spy on a bean with `@Cacheable` methods that refer to parameters by name, your application must be compiled with `-parameters`.

./spring-boot-project/spring-boot-docs/src/docs/asciidoc/actuator/endpoints.adoc:NOTE: To let the input be mapped to the operation method's parameters, Java code that implements an endpoint should be compiled with `-parameters`, and Kotlin code that implements an endpoint should be compiled with `-java-parameters`.

./spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/asciidoc/reacting.adoc:10. Configures any `JavaCompile` tasks to use the `-parameters` compiler argument.
./spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/asciidoc/reacting.adoc:2. Configures any `KotlinCompile` tasks to use the `-java-parameters` compiler argument.
./spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/asciidoc/packaging.adoc:The `application` closure uses Ant-style patch matching for include/exclude parameters.
./spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/main/java/org/springframework/boot/gradle/plugin/JavaPluginAction.java:	private static final String PARAMETERS_COMPILER_ARG = "-parameters";
./spring-boot-project/spring-boot-tools/spring-boot-maven-plugin/src/docs/asciidoc/using.adoc:* Compilation with `-parameters`.

./buildSrc/src/main/java/org/springframework/boot/build/JavaConventions.java: * 
  • {@link JavaCompile} tasks are configured to use {@code -parameters}. ./buildSrc/src/main/java/org/springframework/boot/build/JavaConventions.java: if (!args.contains("-parameters")) { ./buildSrc/src/main/java/org/springframework/boot/build/JavaConventions.java: args.add("-parameters");
  • 解决处理方法:

    添加编译参数 -parameters

    1) 在IDEA中,

    File->Settings->Java Compiler 的 Addintional command line parameters 的下面加上-parameters参数即可

    2) 、在Maven中添加
    
        org.apache.maven.plugins
        maven-compiler-plugin
        3.3
        
            1.8
            1.8
            
                -parameters
            
        
    
    
    3) 、 在eclipse中

    Preferences->java->Compiler下勾选Store information about method parameters选项。

    2、如何在运行期获取method中的参数名

    见: https://www.jianshu.com/p/a7f4336f445c

    3、 Actuator端点配置 PATH, 访问路径:

    默认的基础路径是/actuator,如果一个端点配置的 id 是sessions,那么它的全路径就是/actuator/sessions

    自定义管理端点路径
    management.endpoints.web.base-path = /manage
    

    此配置会将/actuator/sessions/{name}转换成/manage/sessions/{name}

    自定义管理服务器地址

    默认端口和应用的端口是一致的

    management.server.port = 8081
    management.server.address = 127.0.0.1
    
    激活端点
    //激活所有的端点的web方式请求
    management.endpoints.web.exposure.include=*
    //关闭端点web方式
    management.endpoints.web.exposure.exclude=env,beans
    //激活所有的JMX方式请求
    management.endpoints.jmx.exposure.include=*
    //健康信息展示详细信息, 值never:永远不会显示细节,always:显示详细信息,when-authorized:详细信息仅向授权用户显示	
    management.endpoint.health.show-details=always
    //健康信息展示详细信,配置授权角色
    management.endpoint.health.roles
    
    跨域方式请求
    //允许跨域的网址
    management.endpoints.web.cors.allowed-origins=http://example.com
    //允许跨域的方法
    management.endpoints.web.cors.allowed-methods=GET,POST
    
    4、 Actuator端点注解 Web 端点

    @Endpoint、@WebEndpoint 或 @EndpointWebExtension 上的 *** 作将使用 Jersey、Spring MVC 或 Spring WebFlux 通过 HTTP 自动暴露。

    通过使用 @Selector 注解 *** 作方法的一个或多个参数,可以进一步自定义路径.

    HTTP 方法由 *** 作类型决定,如下表所示:

    *** 作HTTP 方法@ReadOperationGET@WriteOperationPOST@DeleteOperationDELETE

    @ReadOperation 返回一个值,响应状态为 200(OK)。如果它未返回值,则响应状态将为 404(未找到)。

    如果 @WriteOperation 或 @DeleteOperation 返回值,则响应状态将为 200(OK)。如果它没有返回值,则响应状态将为 204(无内容)。

    Servlet 端点

    通过实现一个带有 @ServletEndpoint 注解的类,Servlet 可以作为端点暴露,该类也实现了 Supplier。Servlet 端点提供了与 Servlet 容器更深层次的集成,但代价是可移植性。它们旨在用于将现有 Servlet 作为端点暴露。对于新端点,应尽可能首选 @Endpoint 和 @WebEndpoint 注解。

    控制器端点

    @ControllerEndpoint 和 @RestControllerEndpoint 可用于实现仅由 Spring MVC 或 Spring WebFlux 暴露的端点。使用 Spring MVC 和 Spring WebFlux 的标准注解(如 @RequestMapping 和 @GetMapping)映射方法,并将端点的 ID 用作路径的前缀。控制器端点提供了与 Spring 的 Web 框架更深层次的集成,但代价是可移植性。应尽可能首选 @Endpoint 和 @WebEndpoint 注解。

    5、@Endpoint 注解生效原理解析

    https://blog.csdn.net/kangsa998/article/details/103166953/

    欢迎分享,转载请注明来源:内存溢出

    原文地址: http://outofmemory.cn/zaji/5437645.html

    (0)
    打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
    上一篇 2022-12-11
    下一篇 2022-12-11

    发表评论

    登录后才能评论

    评论列表(0条)

    保存