0

Task:

I am trying to search solution for springboot application, where we would like to load CSV files, only in debug mode or development mode. Those CSV files contain test values, which represents data in correct format.

Solution:

According to spring boot documentation I choose the yaml configuration file, where I can say, that CSV file will be on some exact location (i.e.: classpath:/ ). Property looks like this:

spring
  profiles: development
  csv:
    first-csv: classpath:/first.csv

Then there is logic when we are reading sources. This logic will decide if we will call production data or mock data (csv files) according to mode (development or any other). In development mode we will try to read csv files, which should be placed in root folder for an application (as you can see above in yaml configuration.

Problem:

When I call constructor for class, where I need to know the position of .csv files, I don't know how to set those string values to local string values through constructor. My call looks like this:

@Configuration
class WhereWeDecideWhichWayToGo() {

  private final CSVProperties csvProperties;

  //Code for production solution - which works

  @Bean
  @Profile("development")
  public CreateMockData createMockData() {
    return new MockData(csvProperties.getMockCSV());
    }
}

Where CSVProperties is a class with only getters / setters for property from yaml configuration file. This class has annotation:

@Component
@ConfigurationProperties("csv")

And it works. The value from configuration file is read. During debug I can confirm, that csvProperties.getMockCSV() = "classpath:/first.csv", but its not assigned to proper variable in MockData class, which looks like this:

class MockData {

String CSV;

public MockData(
        String mockCSV) {
  // Following line is skipped in debug (and also in normal run)
    CSV = mockCSV;
  }
  // Do some stuff with CSV file
}

Question:

Why is the line in constructor for MockData with initialisation of CSV String

CSV = mockCSV;

skipped - not assigned and the code just continue (skip assignment), even if mockCSV has a correct value:

classpath:/first.csv

I think, that the problem is initialisation order, because I am using the value of CSV in method after the constructor and its null.

11
  • When is your MockData class constructed? My guess would be that it is happening at some point during the initialisation before the property has been set from the .yml file. It might be worth editing your question to include some context around where you call new MockData(...). Commented Mar 9, 2018 at 9:36
  • Ok, I will edit the question. I think that property was correctly read, because, if I go through in debug, the value of csvProperties.getMockCSV is set to: classpath:/first.csv. Commented Mar 9, 2018 at 9:41
  • You say "Following line is skipped" - that doesn't make any sense. Random lines won't just get skipped. What happens if you put some logging (just a System.out.println(...) will do) before and after that line. Do they get executed, or is the constructor never run. You still haven't shown any context around where you are calling that constructor. Commented Mar 9, 2018 at 11:28
  • The constructor never run. I am just surprised, that I am calling it through "return new MockData(csvProperties.getMockCSV());" and the assignment is not executed. I don't know what context do you mean? I already wrote whole Bean, from which is constructor called, what more should I write? Commented Mar 9, 2018 at 12:12
  • Show the code where you are calling the constructor. Are you certain that the code that calls the constructor is definitely being executed? Based on what you've shown, calling new MockData(...) will certainly populate the value. I think this question is getting very confusing, possibly because you're posting heavily modified versions of your code, and so important context is being lost. Maybe try creating a MCVE and posting that. Commented Mar 9, 2018 at 12:17

1 Answer 1

2

As you seemed to ignore my suggestion to create an MCVE, I've attempted to do so using the code from your question, and it works for me.

The code you've posted is obviously heavily edited from what you're actually doing, so I had to guess a few things, and there are also a few syntax errors in the examples you've posted, so I've attempted to infer what they really are in your code. Here's what I've done that works, along with notes about where I've guessed, hopefully it will point you in the right direction.

So, from the beginning, here's the class with @SpringBootApplication, presumably you have something like this somewhere in your codebase:

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

Here's the configuration properties class. Note that you don't need it to be an @Component.

@ConfigurationProperties("csv")
public class CSVProperties {
    private String firstCsv;

    public String getFirstCsv() {
        return firstCsv;
    }

    public void setFirstCsv(String firstCsv) {
        this.firstCsv = firstCsv;
    }
}

I called the property firstCsv to match the value from your application.yml file which is first-csv. It's interesting that in your code you seem to be referring to this property with csvProperties.getMockCSV(). I'm guessing that's just a typo in your question, because that will never work - the property name needs to match the key in your application.yml file.

Here's the configuration class. You haven't shown this anywhere in your examples, but presumably you have an equivalent somewhere. The important part is the @EnableConfigurationProperties, which must specify the class that has the @ConfigurationProperties annotation.

@Configuration
@EnableConfigurationProperties(CSVProperties.class)
public class CSVExampleConfiguration {
    private final CSVProperties csvProperties;

    @Autowired
    public CSVExampleConfiguration(CSVProperties csvProperties) {
        this.csvProperties = csvProperties;
    }

    @Bean
    @Profile("!development")
    public MockData createProductionMockData() {
        return new MockData("production");
    }

    @Bean
    @Profile("development")
    public MockData createMockData() {
        return new MockData(csvProperties.getFirstCsv());
    }
}

It's also interesting that in your question you say you're creating the bean with a method that looks like public CreateMockData createMockData() {, but then you return a MockData. Again, I'm guessing this is a typo in your question, as it won't compile like this (unless MockData extends CreateMockData, but that seems odd).

I'm not sure how you're setting the production profile, but hopefully what I've done above is a reasonable equivalent - @Profile("!development") says "create this bean if the 'development' profile is not set"

My application.yml file is also slightly different from what you've posted in your question:

spring:
  profiles: development

csv:
  first-csv: classpath:/first.csv

Your example won't work at all, firstly because you're missing a : after spring, and secondly because you seem to have csv nested under spring, which isn't how you set the configuration properties - it needs to be at the root level as above. Again, I assume these are just typos in the question because you say the application starts (which it wouldn't with the missing :) and that you see the configuration property get set (which it wouldn't with csv nested under spring).

Finally, I tested all this with a simple controller. You haven't given any details about the rest of your application, but this is what I've done, which hopefully mimics your code:

@RestController
public class TestController {
    private final MockData mockData;

    @Autowired
    public TestController(MockData mockData) {
        this.mockData = mockData;
    }

    @RequestMapping(value = "/foo", method = RequestMethod.GET)
    public String getCsv() {
        return mockData.getCSV();
    }
}

So, then, with the application running and no profile set, if I visit http://localhost:8080/foo in a browser, I get the String "production" in the response, and if I restart the application, with a profile of "development", I get a response of "classpath:/first.csv".

I appreciate that this probably isn't the "just do this" answer that you might have hoped for, but I'd suggest you compare this to your code, and try to modify anything that differs. If you still can't get it to work, then there must be some other differences elsewhere in your application that are causing the problem. If you need more help, edit your question with the specifics that I've missed, but please try to post actual code (copy and pasted, not re-typed, to avoid introducing confusing typos).

Sign up to request clarification or add additional context in comments.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.