javaspringspring-bootdependency-injectionspring-bean

What happens first at startup of a Spring boot application - loading of the application.properties file or creations of beans annotated with @Bean?


I've this class:

@Slf4j
@Configuration
public class TxnIdGeneratorProperties {

    @Bean
    public TimeBasedTxnIdGenerator seqGenerator() {
        if (log.isInfoEnabled())
            log.info("{} Loading CustomProperties :: getTxnIdGeneratorAccounts....", BOOT_CONFIG);

        TimeBasedTxnIdGenerator sequencer = new TimeBasedTxnIdGenerator();
        sequencer.calculateLimit();
        return sequencer;
    }

}

Here I'm creating an object of TimeBasedTxnIdGenerator, calling the calculateLimit() method, and returning the same object as the bean that must be created during startup. This is the TimeBasedTxnIdGenerator class:

@Service
public class TimeBasedTxnIdGenerator extends AbstractSequencer implements TransactionIdGenerator {
    
    @Value("${qr.code.app.url}")
    private String qrCodeAppUrl;
    
    private static final int NODE_ID_BITS = 10;
    private static final long maxNodeId = (1L << NODE_ID_BITS) - 1;

    public TimeBasedTxnIdGenerator() {
        super();
    }
    
    @Override
    public synchronized String doExecute() {
        return getSequenceId();
    }
    
    private static long createNodeId() {
        long nodeId;
        try {
            StringBuilder sb = new StringBuilder();
            Enumeration<NetworkInterface> networkInterfaces = NetworkInterface.getNetworkInterfaces();
            while (networkInterfaces.hasMoreElements()) {
                NetworkInterface networkInterface = networkInterfaces.nextElement();
                byte[] mac = networkInterface.getHardwareAddress();
                if (mac != null) {
                    for(byte macPort: mac) {
                        sb.append(String.format("%02X", macPort));
                    }
                }
            }
            nodeId = sb.toString().hashCode();
        } catch (Exception ex) {
            nodeId = (new SecureRandom().nextInt());
        }
        nodeId = nodeId & maxNodeId;
        return nodeId;
    }

    @Override
    public void calculateLimit() {
        
        System.out.println("MFS App param : " + qrCodeAppUrl);
        
        long nodeId = createNodeId();
        setSequencerLimit(nodeId);
    }   
}

The problem is when I try to read this qr.code.app.url configuration value from the application.properties file, when I print the value at the startup, it is getting printed as null. But when I change the data type of qrCodeAppUrl from String to int, I'm getting this startup error:

Caused by: org.springframework.beans.TypeMismatchException: Failed to convert value of type 'java.lang.String' to required type 'int'; For input string: "digipay://pay?"

So, the application knows the value of that configuration before calling the calculateLimit() method, but inside the method, its value is printed as null. Why is that?


Solution

  • You're creating the class instance with new, the @Value annotation on the field is not going to get evaluated. EDIT: as pointed out by @SotiriosDelimanolis in the comment, the annotation is going to get evaluated, but only after the object is returned from the method.

    You can create Beans either with the @Service annotation or in the @Configuration as a @Bean. To inject the value that way, you can do

    public class TimeBasedTxnIdGenerator extends AbstractSequencer implements TransactionIdGenerator {
        private final String qrUrl;
    
        public TimeBasedTxnIdGenerator(String url) {
            qrUrl = url;
        }
        // ...
    }
    
    @Configuration
    public class Config {
        @Bean
        public TimeBasedTxnIdGenerator seqGenerator(@Value("${qr.code.app.url}") String qrCodeAppUrl) {
            if (log.isInfoEnabled())
                log.info("{} Loading CustomProperties :: getTxnIdGeneratorAccounts....", BOOT_CONFIG);
    
            TimeBasedTxnIdGenerator sequencer = new TimeBasedTxnIdGenerator(qrCodeAppUrl);
            sequencer.calculateLimit();
            return sequencer;
        }
    }