21

So I have this

@Value("classpath:choice-test.html")
private Resource sampleHtml;
private String sampleHtmlData;

@Before
public void readFile() throws IOException {
    sampleHtmlData = IOUtils.toString(sampleHtml.getInputStream());
}

What I'd like to know is if it's possible to not have the readFile() method and have sampleHtmlData be injected with the contents of the file. If not I'll just have to live with this but it would be a nice shortcut.

rozner
  • 570
  • 2
  • 5
  • 11

3 Answers3

40

Technically you can do this with XML and an awkward combination of factory beans and methods. But why bother when you can use Java configuration?

@Configuration
public class Spring {

    @Value("classpath:choice-test.html")
    private Resource sampleHtml;

    @Bean
    public String sampleHtmlData() {
        try(InputStream is = sampleHtml.getInputStream()) {
            return IOUtils.toString(is, StandardCharsets.UTF_8);
        }
    }
}

Notice that I also close the stream returned from sampleHtml.getInputStream() by using try-with-resources idiom. Otherwise you'll get memory leak.

Koray Tugay
  • 21,794
  • 41
  • 171
  • 299
Tomasz Nurkiewicz
  • 324,247
  • 67
  • 682
  • 662
  • I think I'll give this a try, thanks, but unfortunately I'm compiling to Java 6 so I can't use the try/with :( – rozner Dec 06 '12 at 09:16
  • 7
    Thanks. Instead of using IOUtils, I use `new String(Files.readAllBytes(sampleHtml.getFile().toPath()));` . – Evan Hu Jan 18 '15 at 16:09
  • 2
    Instead of using IOUtils I suggest using `StreamUtils.copyToString(in);`: This is bundled with spring so you have this method anyway and in contrast to @EvanHu 's proposal I think that this is more compatible if your ressources are not in the filesystem. – yankee Jul 25 '17 at 19:07
1

There is no built-in functionality for this to my knowledge but you can do it yourself e.g. like this:

<bean id="fileContentHolder">
  <property name="content">
    <bean class="CustomFileReader" factory-method="readContent">
      <property name="filePath" value="path/to/my_file"/>
    </bean>
   </property>
</bean>

Where readContent() returns a String which is read from the file on path/to/my_file.

abalogh
  • 8,159
  • 1
  • 32
  • 49
0

If you want it reduced to one line per injection you can add annotation and conditional converter. This will also preserve ctrl-click navigation and autocomplete in IntelliJ.

@SpringBootApplication
public class DemoApplication {

    @Value("classpath:application.properties")
    @FromResource
    String someFileContents;

    @PostConstruct
    void init() {
        if (someFileContents.startsWith("classpath:"))
            throw new RuntimeException("injection failed");
    }

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

    DemoApplication(ConfigurableEnvironment environment, ResourceLoader resourceLoader) {
        // doing it in constructor ensures it executes before @Value injection in application
        // if you don't inject values into application class, you can extract that to separate configuration
        environment.getConversionService().addConverter(new ConditionalGenericConverter() {
            @Override
            public boolean matches(TypeDescriptor sourceType, TypeDescriptor targetType) {
                return targetType.hasAnnotation(FromResource.class);
            }

            @Override
            public Set<GenericConverter.ConvertiblePair> getConvertibleTypes() {
                return Set.of(new GenericConverter.ConvertiblePair(String.class, String.class));
            }

            @Override
            public Object convert(Object source, TypeDescriptor sourceType, TypeDescriptor targetType) {
                try (final var stream = resourceLoader.getResource(Objects.toString(source)).getInputStream()) {
                    return StreamUtils.copyToString(stream, StandardCharsets.UTF_8);
                } catch (IOException e) {
                    throw new IllegalArgumentException(e);
                }
            }
        });
    }
}

@Target({ElementType.FIELD, ElementType.METHOD, ElementType.PARAMETER, ElementType.ANNOTATION_TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@interface FromResource {
}
Marcin Wisnicki
  • 4,147
  • 4
  • 34
  • 52