spring-projects / spring-framework

Spring Framework
https://spring.io/projects/spring-framework
Apache License 2.0
56.25k stars 37.98k forks source link

`@Aspect` does not work in native-image when pointcut expression uses interface #31739

Closed imgoby closed 9 months ago

imgoby commented 9 months ago

For example:

@Before(value = "execution(* org.springframework.cloud.client.serviceregistry.ServiceRegistry.register(*)) && target(registry) && args(registration)", argNames = "registry, registration")

org.springframework.cloud.consul.serviceregistry.ServiceRegistry is an interface.

In JVM the code is OK, but it does not work built with native-image (Spring 6.0.11), unless I change it to:

@Before(value = "execution(* org.springframework.cloud.consul.serviceregistry.ConsulServiceRegistry.register(*)) && target(registry) && args(registration)", argNames = "registry, registration")
sbrannen commented 9 months ago

I imagine you'll need to register runtime hints for ConsulServiceRegistry so that it can be determined via reflection within a native image that it implements ServiceRegistry.

See if that line of thinking helps you resolve the issue.

If not, please provide a minimal example application that we can run ourselves -- preferably a public Git repository or a ZIP file attached to this issue.

imgoby commented 9 months ago

this is the project: https://github.com/imgoby/spring-cloud-alibaba-graalvm/tree/main/spring-cloud-alibaba-consul-dubbo-provider

its dependency is https://github.com/imgoby/spring-cloud-alibaba-graalvm/tree/main/spring-cloud-alibaba-starter-dubbo

The relevant class is this: https://github.com/imgoby/spring-cloud-alibaba-graalvm/blob/main/spring-cloud-alibaba-starter-dubbo/src/main/java/com/alibaba/cloud/dubbo/registry/DubboServiceRegistrationEventPublishingAspect.java


    /**
     * The pointcut expression for {@link ServiceRegistry#register(Registration)}.
     */
    public static final String REGISTER_POINTCUT_EXPRESSION = "execution(* org.springframework.cloud.client.serviceregistry.ServiceRegistry.register(*)) && target(registry) && args(registration)";

    /**
     * The pointcut expression for {@link ServiceRegistry#deregister(Registration)}.
     */
    public static final String DEREGISTER_POINTCUT_EXPRESSION = "execution(* org.springframework.cloud.client.serviceregistry.ServiceRegistry.deregister(*)) && target(registry) && args(registration)";

I'm trying to modify it to an implementation class.So, some codes were changed to : public static final String REGISTER_POINTCUT_EXPRESSION_CONSUL = "execution(* org.springframework.cloud.consul.serviceregistry.ConsulServiceRegistry.register(*)) && target(registry) && args(registration)";

imgoby commented 9 months ago

I imagine you'll need to register runtime hints for ConsulServiceRegistry so that it can be determined via reflection within a native image that it implements ServiceRegistry.

See if that line of thinking helps you resolve the issue.

If not, please provide a minimal example application that we can run ourselves -- preferably a public Git repository or a ZIP file attached to this issue.

Yes, the runtime class maybe ConsulServiceRegistry or EurekaServiceRegistry or NacosServiceRegistry or ZookeeperServiceRegistry.It is defined by user configuration

imgoby commented 9 months ago

I understand a bit now. I may need to make the necessary modifications like this

    @Configuration
    @ConditionalOnBean(name = "org.springframework.cloud.client.serviceregistry.ServiceRegistry")
    @Aspect
    public class ConsulAspect{}
    @Configuration
    @ConditionalOnBean(name = "org.springframework.cloud.netflix.eureka.serviceregistry.EurekaServiceRegistry")
    @Aspect
    public class EurekaAspect{}
imgoby commented 9 months ago

Is that right?

sbrannen commented 9 months ago

I don't think @ConditionalOnBean will provide any benefit for the scenario you're encountering.

Rather, I was saying I think you'll need to register runtime hints for the use of reflection (for evaluating your AspectJ pointcut expression) within a GraalVM native image.

sbrannen commented 9 months ago

Potentially related issues:

imgoby commented 9 months ago

Can you provide some sample code?

imgoby commented 9 months ago

import org.apache.dubbo.config.ApplicationConfig;
import org.apache.dubbo.config.spring.context.annotation.EnableDubbo;
import org.springframework.aot.hint.MemberCategory;
import org.springframework.aot.hint.RuntimeHints;
import org.springframework.aot.hint.RuntimeHintsRegistrar;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ImportRuntimeHints;

@SpringBootApplication
@EnableDiscoveryClient
@EnableDubbo
@ImportRuntimeHints(ProviderApplication.ServiceRegistryRuntimeHints.class)
public class ProviderApplication {
    public static void main(String[] args) {
        SpringApplication.run(ProviderApplication.class, args);
    }

    static class ServiceRegistryRuntimeHints implements RuntimeHintsRegistrar {
        @Override
        public void registerHints(RuntimeHints hints, ClassLoader classLoader) {
            hints.reflection().registerType(org.springframework.cloud.consul.serviceregistry.ConsulServiceRegistry.class,
                    MemberCategory.values());
        }
    }

}

Is this OK?But I should write the final implement class in my code.

sbrannen commented 9 months ago

Is this OK?

Yes, I was suggesting something like that.

Though, I don't think you need to register all MemberCategory values to get it to work.

In any case, can you please confirm that ServiceRegistryRuntimeHints allows your aspect to work properly in a native image?

But I should write the final implement class in my code.

I don't understand what you mean by that. Can you please expound?

imgoby commented 9 months ago

Is this OK?

Yes, I was suggesting something like that.

Though, I don't think you need to register all MemberCategory values to get it to work.

In any case, can you please confirm that ServiceRegistryRuntimeHints allows your aspect to work properly in a native image?

But I should write the final implement class in my code.

I don't understand what you mean by that. Can you please expound?

Previously, it was just an interface called org. springframework. cloud. client. serviceregistry.ServiceRegistry,and there are several implementation classes,such as org.springframework.cloud.consul.serviceregistry.ConsulServiceRegistry.class, org.springframework.cloud.netflix.eureka.serviceregistry.EurekaServiceRegistry

Is it written here as follow? hints.reflection().registerType(org.springframework.cloud.consul.serviceregistry.ConsulServiceRegistry.class, MemberCategory.values()); hints.reflection().registerType(org.springframework.cloud.netflix.eureka.serviceregistry.EurekaServiceRegistry.class, MemberCategory.values());

imgoby commented 9 months ago

This is the file I want to modify, even if I use RuntimeHintsRegistry, @Before will not be triggered.

https://github.com/imgoby/spring-cloud-alibaba-graalvm/blob/main/spring-cloud-alibaba-starter-dubbo/src/main/java/com/alibaba/cloud/dubbo/registry/DubboServiceRegistrationEventPublishingAspect.java

Is something wrong?

imgoby commented 9 months ago

@ImportRuntimeHints is not working in my project and the issue cannot be resolved. Therefore, I divided the original classes into four. The project is now working normally.

https://github.com/imgoby/spring-cloud-alibaba-graalvm/blob/main/spring-cloud-alibaba-starter-dubbo/src/main/java/com/alibaba/cloud/dubbo/registry/DubboServiceRegistrationEventPublishingAspect.java