Home > other >  Why can't I pass a String to a @Service class constructor in Spring?
Why can't I pass a String to a @Service class constructor in Spring?

Time:08-21

I have a Spring Boot app that provides files to users via a web front-end. Users can browse various file back-ends (dropbox, local filesystem etc) and then add that file to their library. The class below concerns only part of the service layer that queries the Dropbox API. The final version will have several other methods but for now, this all that has been implemented:

import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ObjectNode;
import okhttp3.*;

import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;

@Service
public class DbxService {

    private final String dropboxApiAccessToken;
    private final OkHttpClient httpClient;
    private final String dropboxApiAccessTokenFilePath;
    private final ObjectMapper jsonObjectMapper; //fasterxml.jackson.databind.ObjectMapper

    DbxService(String dropboxApiAccessTokenFilePath) throws IOException {
        this.dropboxApiAccessTokenFilePath = dropboxApiAccessTokenFilePath;
        this.httpClient = new OkHttpClient();
        this.jsonObjectMapper = new ObjectMapper();
        this.dropboxApiAccessToken = Files.readString(Path.of(dropboxApiAccessTokenFilePath));
    }

    public String listDropboxDirectory(String path) throws IOException {
        //jackson.databind.node.ObjectNode
        ObjectNode jsonPostRequestNode = jsonObjectMapper.createObjectNode();
        //need JSON string {"path": "[path]"} for Dropbox API Post request
        jsonPostRequestNode.put("path", path);

        //okhttp3 request
        Request dropboxApiRequest = new Request.Builder()
                .url("https://api.dropboxapi.com/2/files/list_folder")
                .post(RequestBody.create(jsonPostRequestNode.asText(), MediaType.parse("application/json")))
                .addHeader("Authorization", "Bearer "   this.dropboxApiAccessToken)
                .addHeader("Content-Type", "application/json")
                .build();
        Response dropboxApiResponse = this.httpClient.newCall(dropboxApiRequest).execute();
        return dropboxApiResponse.body().string();
    }
}

My class is thread-safe as it is immutable - there should not be a problem with it being a singleton (indeed, I very much want it to be a singleton). It would be totally overkill for me to have some other back-end/data repository storing this path, however I do want it to be definable once - say in a configuration class - without having to hard-code the path string literal into a field.

The above code-snippet results in the following error: "Could not autowire. No beans of 'String' type found.". My own investigations suggest it is not possible to inject objects into Spring managed components that are not, in themselves, Spring beans.

How can I refactor this snippet/service to avoid this error?

CodePudding user response:

You might be able to inject a String if you actually register the instance as a been first (never tried). Maybe you also need a @Qualifier-annotation, if there is more than one String bean.

Usually, in Spring Boot the application.yml/ application.properties is used for environment configuration. The values can either be mapped to a POJO which is annotated with @ConfigurationProperties. This allows way you get type safe properties which can be injected in other beans/ services. Or you can use the @Value-annotation to inject a single specific property in a bean/ service.

CodePudding user response:

It turns out that it is not possible to inject objects that are not Spring Beans into a Spring @Service or @Component. In my particular case, I am trying to allow for the injection of a String representing the path to a file containing an API key into my @Service class constructor.

I managed to get my code to work, as desired, by using Spring's @Value annotation. I created en entry in my application.properties file:

DropboxService.AccessToken.FilePath="/foo/bar/credentialsFile"

and changed my code to the following (note the @Value annotation before the class constructor method parameter):

package org.jdnet.audioBookPlayer.Utility;

import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ObjectNode;
import okhttp3.*;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;

import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;

@Service
public class DbxService {

    private final String dropboxApiAccessToken;
    private final OkHttpClient httpClient;
    private final String dropboxApiAccessTokenFilePath;
    private final ObjectMapper jsonObjectMapper; //fasterxml.jackson.databind.ObjectMapper

    DbxService(@Value("${DropboxService.AccessToken.FilePath}") String dropboxApiAccessTokenFilePath) throws IOException {
        this.dropboxApiAccessTokenFilePath = dropboxApiAccessTokenFilePath;
        this.httpClient = new OkHttpClient();
        this.jsonObjectMapper = new ObjectMapper();
        this.dropboxApiAccessToken = Files.readString(Path.of(dropboxApiAccessTokenFilePath));
    }

    public String listDropboxDirectory(String path) throws IOException {
        //jackson.databind.node.ObjectNode
        ObjectNode jsonPostRequestNode = jsonObjectMapper.createObjectNode();
        //need JSON string {"path": "[path]"} for Dropbox API Post request
        jsonPostRequestNode.put("path", path);

        //okhttp3 request
        Request dropboxApiRequest = new Request.Builder()
                .url("https://api.dropboxapi.com/2/files/list_folder")
                .post(RequestBody.create(jsonPostRequestNode.asText(), MediaType.parse("application/json")))
                .addHeader("Authorization", "Bearer "   this.dropboxApiAccessToken)
                .addHeader("Content-Type", "application/json")
                .build();
        Response dropboxApiResponse = this.httpClient.newCall(dropboxApiRequest).execute();
        return dropboxApiResponse.body().string();
    }
}

A better answer would explain why I cannot simply use a @Bean definition in a configuration class as the only good reason I can see for Spring not being able to load a String in is that it cannot know which string to pass in when it tries to autowiring.

  • Related