Search code examples
springspring-bootdependency-injectionsingletoninversion-of-control

Registering an instance as 'singleton' bean at application startup


I am playing around with Spring Boot and I am trying to construct an instance of ServiceImpl to be resolved when a Service is required. Currently I am annotating the implementation as @Component but this does not give me the chance to construct the instance as I want.

The ServiceImpl should be constructed with a String containing a path to a file on disk. I would like to do this in the main method of the @SpringBootApplication class of the application.

Maybe it's just me coming from a long .NET background where we usually setup the IoC container like:

Service service = new Service("C:\\data.bin");
container.RegisterSingleton<IService>(service); // now whoever asks for a IService will receive this precise instance

Does this make sense in Spring world?

LE: I am well aware of the GoF singleton definition (i.e. prevent everyone else from creating instances of the class) - I am not targeting this.


Solution

  • As described in the comment this can be done by storing your location details on a configuration file and then inject them upon Spring Bean initialization.

    Assuming your application.properties looks like this:

    my.sample.config.A=somelocationA
    my.sample.config.B=somelocationB
    my.sample.config.C=somelocationC
    my.sample.config.D.one=somelocationD1
    my.sample.config.D.two=somelocationD2
    

    Below I'm demo-ing 4 ways to do achieve this:

    1.By injecting your property directly on the Bean method creation:

    @Bean
    public A myBeanA(@Value("${my.sample.config.A}") String myprop) {
        System.out.println("from bean A with " + myprop);
        return new A(myprop);
    }
    

    2.By injecting the property on a Config-wide variable and use that in your Bean method creation:

    @Value("${my.sample.config.B}")
    private String mylocationB;
    //..
    @Bean
    public B myBeanB() {
        System.out.println("from bean B with " + mylocationB);
        return new B(mylocationB);
    }
    

    3.By injecting the whole environment in the Config and then hand-pick the property needed:

    @Autowired
    private Environment env;
    //..
        @Bean 
        public C myBeanC() {
        String locationC = env.getProperty("my.sample.config.C");
        System.out.println("from bean C with " + locationC);
        return new C(locationC);
    }
    

    4.This is a Spring Boot exclusive way. You can use Type-safe Configuration Properties annotating with @ConfigurationProperties directly your bean defining a prefix-namespace and all params from that point onwards will be auto-mapped to the properties defined in that bean!

    @ConfigurationProperties(prefix = "my.sample.config.D")
    @Component
    class D {
        private String one;
        private String two;
    
        public String getOne() { return one; }
    
        public void setOne(String one) {
            System.out.println("from bean D with " + one);
            this.one = one;
        }
        public String getTwo() { return two; }
    
        public void setTwo(String two) {
            System.out.println("from bean D with " + two);
            this.two = two;
        }
    }
    

    Below the overall one-file code in one piece:

    package com.example;
    
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.beans.factory.annotation.Value;
    import org.springframework.boot.SpringApplication;
    import org.springframework.boot.autoconfigure.SpringBootApplication;
    import org.springframework.boot.context.properties.ConfigurationProperties;
    import org.springframework.context.annotation.Bean;
    import org.springframework.core.env.Environment;
    import org.springframework.stereotype.Component;
    
    @SpringBootApplication
    public class DemoApplication {
    
        @Autowired
        private Environment env;
    
        @Value("${my.sample.config.B}")
        private String mylocationB;
    
        @Bean
        public A myBeanA(@Value("${my.sample.config.A}") String myprop) {
            System.out.println("from bean A with " + myprop);
            return new A(myprop);
        }
    
        @Bean
        public B myBeanB() {
            System.out.println("from bean B with " + mylocationB);
            return new B(mylocationB);
        }
    
        @Bean
        public C myBeanC() {
            String locationC = env.getProperty("my.sample.config.C");
            System.out.println("from bean C with " + locationC);
            return new C(locationC);
        }
    
        @ConfigurationProperties(prefix = "my.sample.config.D")
        @Component
        class D {
            private String one;
            private String two;
    
            public String getOne() { return one; }
    
            public void setOne(String one) {
                System.out.println("from bean D with " + one);
                this.one = one;
            }
            public String getTwo() { return two; }
    
            public void setTwo(String two) {
                System.out.println("from bean D with " + two);
                this.two = two;
            }
        }
    
        public static void main(String[] args) {
            SpringApplication.run(DemoApplication.class, args);
        }
    
        class A {
            private final String location;
            public A(String location) { this.location = location; }
        }
    
        class B {
            private final String location;
            public B(String location) { this.location = location; }
        }
    
        class C {
            private final String location;
            public C(String location) { this.location = location; }
        }
    
    }