micronaut-projects / micronaut-core

Micronaut Application Framework
http://micronaut.io
Apache License 2.0
6.07k stars 1.07k forks source link

Issue with introspection on OpenJ9 #2168

Open zdegner opened 5 years ago

zdegner commented 5 years ago

Older reflection-based bean introspection appears to fail on Eclipse OpenJ9 JVM. This seems to be due to an assumption about the order that methods are declared during reflection:

https://github.com/micronaut-projects/micronaut-core/blob/fbbe8ea8e5d8ee32f8352832893da8413d70bd17/core/src/main/java/io/micronaut/core/beans/SimpleBeanInfo.java#L265-L267

Notice that in the method above if a method is associated with the Object class then it is assumed that no more class methods will follow (break; versus continue;). When running OpenJ this assumption does not hold true, the first method is always Object::getClass()

OpenJ has a substantially smaller memory footprint, ~60%, when compared to Hotspot and comparable performance. This is the only case where I've noticed a runtime difference between Hotspot and OpenJ. (The actual scenario was when serializing an x-www-form-data body via a @Client for an OAuth call, the form data uses BeanMap.of and falls back on this version of introspection.)

Task List

Steps to Reproduce

  1. Create a class that will be introspected
@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
public class TokenFormData {
    private String grant_type;
    private String client_id;
    private String client_secret;
}
  1. Use the introspector to get the properties from the bean (BeanMap.of is used when serializing x-www-form-data via the http-client). Return these values from a Controller:
@Controller("/")
public class ExampleController {
    @Get("/token")
    public Set<String> getToken() {
        var beanMap = BeanMap.of(new TokenFormData(
                "client_credentials",
                "client id",
                "some secret"
        ));
        return beanMap.keySet();
    }
}
  1. Run via ./gradlew run and call GET http://localhost:8080/token, you'll see the properties as expected:
[
    "grant_type",
    "client_secret",
    "client_id"
]
  1. Now run via Docker using the OpenJ JVM:
./gradlew build && docker build -t foo . && docker run -it -p 8080:8080 foo
  1. GET http://localhost:8080/token, you'll see the properties are now not found:
[]

Expected Behaviour

Introspected bean should reveal properties

Actual Behaviour

Introspected bean does not reveal any properties. A workaround is to add @Introspected and the properties will be picked up correctly on both Hotspot and OpenJ.

Environment Information

Example Application

https://github.com/zdegner/micronaut-introspection-openj

croudet commented 5 years ago

I think it is the same behaviour seen in https://github.com/micronaut-projects/micronaut-views/issues/4

graemerocher commented 5 years ago

Thanks for the report. The reflection based approach as been deprecated. Could you annotate TokenFormData with @Introspected and see if that resolves the problem?

zdegner commented 5 years ago

Yes, adding @Introspected to TokenFormData resolves the issue.

zdegner commented 5 years ago

In case any else runs into this issue -- the exact scenario was when using @Client with MediaType.APPLICATION_FORM_URLENCODED body type:

@Client("token-service")
public interface TokenClient {
    @Post(value = "/connect/token", produces = MediaType.APPLICATION_FORM_URLENCODED)
    TokenResponse getAccessToken(@Body TokenFormData formData);
}

Solution was to add @Introspected to the form data object:

@Getter
@Setter
@AllArgsConstructor
@Introspected
public class TokenFormData {
    private String grant_type;
    private String client_id;
    private String client_secret;
}