spring-projects / spring-framework

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

Prototype-scoped bean cannot be registered via lambda expression #26293

Closed hhfdna closed 3 years ago

hhfdna commented 3 years ago

Here is my code:

@SpringBootApplication
public abstract class DIApp {
    public static void main(String[] args) {
        SpringApplication.run(DIApp.class);
    }

    @Bean
    @Scope("prototype")
    public HelloService helloService(){
        return new HelloService() {
            @Override
            public void print(String name) {
                System.out.println("hello:"+name);
            }
        };
    }

    @Bean
    CommandLineRunner run2(){
        return args -> {
            for (int i = 0; i < 5; i++) {
                HelloService service = helloService();
                service.print("prototype@"+service);
            }
        };
    }

}

it works fine. for every call, a new HelloService instance is created. but, after i changing HelloService bean to lamda style, it's not ok:

    @Bean
    @Scope("prototype")
    public HelloService helloService(){
        return name -> System.out.println("hello:"+name);
    }

Does lamda not surpport prototype scope?

hhfdna commented 3 years ago

HelloService is just a plain interface:

public interface HelloService {
    void print(String name);
}
quaff commented 3 years ago

works fine with latest version 5.3.2, but every created instance is identical, I think it's reused by JVM not spring.

import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
public class Main {
    public static void main(String[] args) throws Exception {
        try (AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(MyConfiguration.class)) {
            System.out.println(ctx.getBean(Service.class));
            //printed: Main$MyConfiguration$$Lambda$69/1658926803@1d548a08
        }
    }
    @Configuration
    @ComponentScan
    static class MyConfiguration {
        @Bean
        @Scope("prototype")
        public Service service() {
            return () -> {
            };
        }
    }
    static interface Service {
        void test();
    }
}
quaff commented 3 years ago

Very interesting, lambda expression will be identical, but method reference will not.

import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Scope;

public class Main {

    public static void main(String[] args) throws Exception {
        Service s1 = createService();
        Service s2 = createService();
        System.out.println(s1 == s2); // true created by lambda expression
        try (AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(MyConfiguration.class)) {
            Service service1 = ctx.getBean(Service.class);
            Service service2 = ctx.getBean(Service.class);
            System.out.println(service1 == service2); // false created by method reference
        }
    }

    static Service createService() {
        return s -> {
            System.out.println(s);
        };
    }

    @Configuration
    @ComponentScan
    static class MyConfiguration {
        @Bean
        @Scope(BeanDefinition.SCOPE_PROTOTYPE)
        public Service service() {
            return System.out::println;
        }
    }

    static interface Service {
        void print(String name);
    }

}
mdeinum commented 3 years ago

This is due to method-references being treated differently then lambda expression by the compiler/runtime are handled.

See: https://stackoverflow.com/a/29290314/2696260

quaff commented 3 years ago

This is due to method-references being treated differently then lambda expression by the compiler/runtime are handled.

See: https://stackoverflow.com/a/29290314/2696260

Thanks, but it still not explain such difference.

hhfdna commented 3 years ago

so, the conclusion is that if i want a prototype-scoped bean, anonymouse inner class and method reference works, but lamda can't? but why?

hhfdna commented 3 years ago

for inner anonymouse class:

    @Bean
    @Scope("prototype")
    public HelloService helloService(){
        return new HelloService() {
            @Override
            public void print(String name) {
                System.out.println("hello:"+name);
            }
        };
    }

i get defferent instances for every call:

hello:prototype@com.bocsoft.demo.DIApp$1@35a3d49f
hello:prototype@com.bocsoft.demo.DIApp$1@389b0789
hello:prototype@com.bocsoft.demo.DIApp$1@13d9cbf5
hello:prototype@com.bocsoft.demo.DIApp$1@478db956
hello:prototype@com.bocsoft.demo.DIApp$1@6ca18a14

but for lamda style

    @Bean
    @Scope("prototype")
    public HelloService helloService(){
        return name -> System.out.println("hello:"+name);
    }

i get the same instance for every call:

hello:prototype@com.bocsoft.demo.DIApp$$Lambda$164/41489123@34b9f960
hello:prototype@com.bocsoft.demo.DIApp$$Lambda$164/41489123@34b9f960
hello:prototype@com.bocsoft.demo.DIApp$$Lambda$164/41489123@34b9f960
hello:prototype@com.bocsoft.demo.DIApp$$Lambda$164/41489123@34b9f960
hello:prototype@com.bocsoft.demo.DIApp$$Lambda$164/41489123@34b9f960
quaff commented 3 years ago

for inner anonymouse class:

    @Bean
    @Scope("prototype")
    public HelloService helloService(){
        return new HelloService() {
            @Override
            public void print(String name) {
                System.out.println("hello:"+name);
            }
        };
    }

i get defferent instances for every call:

hello:prototype@com.bocsoft.demo.DIApp$1@35a3d49f
hello:prototype@com.bocsoft.demo.DIApp$1@389b0789
hello:prototype@com.bocsoft.demo.DIApp$1@13d9cbf5
hello:prototype@com.bocsoft.demo.DIApp$1@478db956
hello:prototype@com.bocsoft.demo.DIApp$1@6ca18a14

but for lamda style

    @Bean
    @Scope("prototype")
    public HelloService helloService(){
        return name -> System.out.println("hello:"+name);
    }

i get the same instance for every call:

hello:prototype@com.bocsoft.demo.DIApp$$Lambda$164/41489123@34b9f960
hello:prototype@com.bocsoft.demo.DIApp$$Lambda$164/41489123@34b9f960
hello:prototype@com.bocsoft.demo.DIApp$$Lambda$164/41489123@34b9f960
hello:prototype@com.bocsoft.demo.DIApp$$Lambda$164/41489123@34b9f960
hello:prototype@com.bocsoft.demo.DIApp$$Lambda$164/41489123@34b9f960

It's prototype from spring perspective, use method reference or inner class instead if you really care identity.

hhfdna commented 3 years ago

ok, thx @quaff

sbrannen commented 3 years ago

Please note that questions like this are often better suited for Stack Overflow.

Closing this issue since the question has been answered in the comments above.