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

Posted xlxxcc

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Spring boot 使用@Endpoint注解自定义端点, 不能通过 Restfult 访问问题 原因分析相关的知识,希望对你有一定的参考价值。

1、使用@Endpoint注解自定义端点

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

引入依赖

<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-actuator</artifactId>
</dependency>

自定义端点代码

注意: @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: * <li>@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中添加

<plugin>
    <groupId>org.apache.maven.plugins</groupId>
    <artifactId>maven-compiler-plugin</artifactId>
    <version>3.3</version>
    <configuration>
        <source>1.8</source>
        <target>1.8</target>
        <compilerArgs>
            <arg>-parameters</arg>
        </compilerArgs>
    </configuration>
</plugin>

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/

以上是关于Spring boot 使用@Endpoint注解自定义端点, 不能通过 Restfult 访问问题 原因分析的主要内容,如果未能解决你的问题,请参考以下文章

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

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

Spring boot 使用@Endpoint注解自定义端点, 不能通过 Restfult 访问问题处理方法

Spring boot 使用@Endpoint注解自定义端点, 不能通过 Restfult 访问问题处理方法

Spring boot 使用@Endpoint注解自定义端点, 不能通过 Restfult 访问问题处理方法

Spring Boot 2.x 自定义Endpoint