I have a singleton class that I created that is used as an adapter to our Influx database. It basically looks like:
public class InfluxDBAdapter {
// some private static final query Strings
private static InfluxDBAdapter adapter = null;
private static InfluxDB influxDB;
private InfluxDBAdapter() {}
public static InfluxDBAdapter getInstance() {
if (adapter == null) {
adapter = new InfluxDBAdapter();
influxDB = InfluxDBFactory.connect(URL, USERNAME, PWD);
influxDB.query(new Query(CREATE_DB, DB_NAME));
influxDB.setLogLevel(InfluxDB.LogLevel.BASIC);
influxDB.setDatabase(DB_NAME);
}
return adapter;
}
// some more methods to utilize the database
}
and then in another class, I use it like so:
#Service
public class SomeService {
private InfluxDBAdapter adapter;
public SomeService() {}
#PostConstruct
public void init() {
adapter = InfluxDBAdapter.getInstance();
}
}
And so this works, but I'm in the midst of refactoring my code and I wanted to know if was possible to simply autowire my InfluxDBAdapter class vs what I'm currently doing and still achieve the same result?
Create an #Configuration class which both constructs the InfluxDB as well as your adapter. With this you could even make use of the Spring Boot properties support.
#Configuration
public class InfluxDBConfiguration {
#Bean
public InfluxDB influxDB() {
InfluxDB influxDB = InfluxDBFactory.connect(URL, USERNAME, PWD);
influxDB.query(new Query(CREATE_DB, DB_NAME));
influxDB.setLogLevel(InfluxDB.LogLevel.BASIC);
influxDB.setDatabase(DB_NAME);
return influxDB;
}
#Bean
public InfluxDBAdapter influxDBAdapter(InfluxDB influxDB) {
return new InfluxDBAdapter(influxDB);
}
}
Now your InfluxDBAdapter needs a constructor (for dependency injection) retrieving the InfluxDB.
public class InfluxDBAdapter {
// some private static final query Strings
private InfluxDB influxDB;
InfluxDBAdapter(InfluxDB influxDB) {
this.influxDB=influxDB;
}
// some more methods to utilize the database
}
Make sure that the InfluXDBConfiguration and InfluxDBAdapter are in the same package so that the default visible constructor can be called (default visible to prevent, easy, outside instantiation).
In the InflxuDBConfiguration you could remove the static fields containing the hardcoded username etc. and replace it with access to either the Environment or use an #ConfigurationProperties annotated class to work with type safe properties.
#ConfigurationProperties(prefix="influxdb")
#Component
public class InfluxDBProperties {
private String url = "default-url";
private String dbName = "default-dbname";
private String username = "default-user";
private String password = "default-pwd";
// Other properties of your liking;
// getters & setters
}
Now with this InfluxDBProperties you could add influx.url=http://whatever to your application.properties or profile specific one and have it externally configurable. You can inject it into the influxDB method to retrieve the properties from.
#Bean
public InfluxDB influxDB(InfluxDBProperties props) {
InfluxDB influxDB = InfluxDBFactory.connect(props.getUrl(), props.getUsername(), props.getPassword());
influxDB.query(new Query(CREATE_DB, props.getDbName()));
influxDB.setLogLevel(InfluxDB.LogLevel.BASIC);
influxDB.setDatabase(props.getDbName());
return influxDB;
}
No more statics, configurable for every environment.
Yes, this should work. Spring can invoke private constructors so there shouldn't be any issue.
But why would you want to do this? The singleton pattern goes against the basic tenant of dependency injection. If you want a singleton InfluxDBAdapter bean, just make it a singleton bean.
I would recommend adding a configuration class, which could look something like
#Configuration
public class InfluxDBConfig {
// constants omitted...
#Bean
public InfluxDB influxDB() {
final InfluxDB influxDB = InfluxDB(URL, USERNAME, PWD);
influxDB.query(new Query(CREATE_DB, DB_NAME));
influxDB.setLogLevel(InfluxDB.LogLevel.BASIC);
influxDB.setDatabase(DB_NAME);
return influxDB;
}
}
You can then annotate InfluxDBAdapter with #Component since the InfluxDB instance can be injected. Modify the constructors of the InfluxDB and InfluxDBAdapter classes accordingly, of course.
Some of these constants can probably be provided through configuration properties so that your configuration logic isn't mangled with your business logic.
Related
I'm using Spring and need some help:
I want to set one API key using application.properties instead of hardcoding it, but it always returns null. IntelliJ evaluates it correctly to the value I've set in the file.
I've already read other questions here and almost all solutions are saying that Spring can only "inject" those value anotations in managed classes, like Components, Beans, etc. That's what (think) I did and still got null!
Everything else is working as I intended. Any direction is appreciated!
My application.properties
api.someapiservice.key=08e...f
Class that uses the properties value:
#Component
public class ApiClient implements ApiClientInterface {
#Value("${api.someapiservice.key}")
private String API_KEY;
public ApiClient () {
System.out.println(API_KEY); //Returns null after spring log info: Initialized JPA EntityManagerFactory for persistence unit 'default'
...
}
Class that uses ApiClient:
#Component
public class SomeService {
private final SomeRepository someRepository;
private final ApiClient apiClient;
public PlaylistService(SomeRepository someRepository , ApiClient apiClient ) {
this.SomeRepository = SomeRepository;
this.apiClient = ApiClient;
}
Field injection can't possibly happen until after the instance is already constructed, so your #Value (or #Autowired) fields will always be null in the constructor. Move the #Value to a constructor parameter instead.
If you want to know what is the value of your #Value field on start up. You can use #PostConstruct annotation, or you can move #Value annotation on your class constructor.
private String API_KEY;
public ApiClient(#Value("${api.test.value}") String key) {
this.API_KEY= key;
System.out.println(this.API_KEY);
}
or using #PostConstruct Annotation
#Value("${api.someapiservice.key}")
private String API_KEY;
#PostConstruct
public void init() {
System.out.println(this.API_KEY);
}
I need to create sth like Shared class, which I can use in the following way:
Shared.getProperty(key);
I tried to use Environment object, but it is always null. Where should it be specified and how?
I use .xml for my bean configuration.
I also have application.properties, where I want to retrieve data from.
// Shared.java
#Component
#ConfigurationProperties("prefix.for.application.properties")
public class Shared {
private String str;
// getters, setters
}
// application.properties
prefix.for.application.properties.str=STR
// other code
#Autovired
private Shared shared;
shared.getStr();
The best way is to have properties defined in application.properties file and then you can access these properties using #Value annotation.
The way I do it is using defining the values in application.properties and then creating a Configuration class for example:
Define constants in application.properties
app.email_subject =My app Registration
app.email_from =Some person
An annotated class
#Configuration
#ConfigurationProperties(prefix = "app")
public class GlobalProperties {
#Value("email_subject")
private String emailSubject;
#Value("email_from")
private String emailFrom;
// getters and setters
}
You can use this class anywhere you want like this:
#Service
public class SomeService {
#Autowired
private GlobalProperties globalProperties;
public someMethod() {
System.out.println(globalProperties.getEmailFrom());
}
}
I am using below config.yml
# AWS DynamoDB settings
dynamoDB:
# Access key
aws_access_key_id: "access-key"
#Secret Key
aws_secret_access_key: "secret-key"
aws_dynamodb_region: EU_WEST_1
And below class to read the above config values in my DynamoDBConfig class.
public class DynamoDBConfig {
public DynamoDBConfig() {
}
#JsonProperty("aws_access_key_id")
public String accessKey;
#JsonProperty("aws_secret_access_key")
public String secretKey;
#JsonProperty("aws_dynamodb_region")
public String region;
// getters and setters
}
Finally ApplicationConfig class which include DynamoDB config.
public class ReadApiConfiguration extends Configuration {
#NotNull
private DynamoDBConfig dynamoDBConfig = new DynamoDBConfig();
#JsonProperty("dynamoDB")
public DynamoDBConfig getDynamoDBConfig() {
return dynamoDBConfig;
}
#JsonProperty("dynamoDB")
public void setDynamoDBConfig(DynamoDBConfig dynamoDBConfig) {
this.dynamoDBConfig = dynamoDBConfig;
}
}
Now i want to read aws_access_key and aws_secret_key values in my AWSclient.java class to create a awsclient
BasicAWSCredentials awsCreds = new BasicAWSCredentials("access_key_id", "secret_key_id");
My problem is, how i read/inject the config values, in my AWSClient class. I am using the dropwizard-guice module for DI. and couldn't figure out , how can i bind the configuration object created at the DW startup time to its class.
P.S. :-> I've gone through this SO post but it doesn't solve my issue, as its not using guice as a DI module.
Normally, you can inject your configuration object either into a class field or into a constructor, like:
public class AWSclient {
#Inject
public AWSclient(ReadApiConfiguration conf) {
initConnection(conf.getDynamoDBConfig().getSecretKey(), ...)
}
}
Additionally, annotate your ReadApiConfiguration class with the #Singleton annotation.
I have recently added a Throttler field to a Server class that is to be instantiated only if throttling is enabled (this is a config entry), and if so, the max number of requests per sec (another config entry) is to be passed to its constructor.
Here is the code without dependency injection of Throttler:
public class Server {
private Config config;
private Throttler throttler;
#Inject
public Server(Config config) {
this.config = config;
if (config.isThrottlingEnabled()) {
int maxServerRequestsPerSec = config.getMaxServerRequestsPerSec();
throttler = new Throttler(maxServerRequestsPerSec);
}
}
}
public class Throttler {
private int maxRequestsPerSec;
public Throttler(int maxRequestsPerSec) {
this.maxRequestsPerSec = maxRequestsPerSec
}
}
Now to inject Throttler, I used a Provider, since it doesn't always need to be instantiated. But now I am forced to inject Config into Throttler and let it "configure itself":
public class Server {
private Config config;
private Provider<Throttler> throttlerProvider;
#Inject
public Server(Config config, Provider<Throttler> throttlerProvider) {
this.config = config;
this.throttlerProvider = throttlerProvider;
if (config.isThrottlingEnabled()) {
this.throttler = throttlerProvider.get();
}
}
}
public class Throttler {
private int maxRequestsPerSec;
#Inject
public Throttler(Config config) {
maxRequestsPerSec = config.getMaxServerRequestsPerSec();
}
}
I don't like this solution because:
There is a dependency of an utility class (Throttler) to Config.
Throttler is now tied to a specific configuration entry, which means it cannot be used by anything else but Server.
I would prefer to somehow inject maxRequestsPerSec into the construtor.
Is that possible with Guice?
The Guice FAQ recommends to introduce a factory interface which builds the class with its dependencies and additional parameters passed by the client.
public class Throttler {
...
public static class Factory {
#Inject
public class Factory(... Throttler dependencies ...) {...}
public Throttler create(int maxRequestsPerSec) {
return new Throttler(maxRequestsPerSec /*, injected Throttler dependencies */);
}
}
}
This way, all the direct dependencies of Throttler remain encapsulated in the Throttler class.
You can also use the AssistedInject extension to reduce the boilerplate code.
It totally depends on how you implement the Provider interface and on your application. If the only way to get the maxRequestsPerSec is from the Config,you can do something along these lines:
You could have the specific Provider implementation injected, and have a setter in that. So in your constructor you inject CustomProvider<Throttler> (which implements Provider), then perform setMaxRequestsPerSec, and then use that in the get method when instantiating your Throttler.
If you don't want to inject CustomProvider, you can instead inject the Provider and then do an instanceof check but I think it would be better to inject the CustomProvider.
Based on parameters passed to a method, I need to select from one of many Spring beans that are implementations of the same class, but configured with different parameters.
E.g. if user A invokes the method, I need to call dooFoo() on bean A, but if it's user B then I need to call the very same method, only on bean B.
Is there a 'Springier' way of doing this other than sticking all the beans in a map, and deriving a key from the parameters passed to my method?
We face that issue in our project, and we solve it through a Factory-Like class. The client class -the one that needed the bean at runtime- had an instance of the factory, that was injected through Spring:
#Component
public class ImTheClient{
#Autowired
private ImTheFactory factory;
public void doSomething(
Parameters parameters) throws Exception{
IWantThis theInstance = factory.getInstance(parameters);
}
}
So, the IWantThis instance depends on the runtime value of the parameters parameter. The Factory implementation goes like this:
#Component
public class ImTheFactoryImpl implements
ImTheFactory {
#Autowired
private IWantThisBadly anInstance;
#Autowired
private IAlsoWantThis anotherInstance;
#Override
public IWantThis getInstance(Parameters parameters) {
if (parameters.equals(Parameters.THIS)) {
return anInstance;
}
if (parameters.equals(Parameters.THAT)) {
return anotherInstance;
}
return null;
}
}
So, the factory instance holds reference to both of the posible values of the IWantThis class, being IWantThisBadly and IAlsoWantThis both implementations of IWantThis.
Seems like do you want a ServiceLocator using the application context as registry.
See ServiceLocatorFactoryBean support class for creating ServiceLocators mapping keys to bean names without coupling client code to Spring.
Other option is to use a naming convention or annotation based configuration.
for example, assuming that you annotate Services with #ExampleAnnotation("someId"), you can use something like the following Service Locator to retrieve them.
public class AnnotationServiceLocator implements ServiceLocator {
#Autowired
private ApplicationContext context;
private Map<String, Service> services;
public Service getService(String id) {
checkServices();
return services.get(id);
}
private void checkServices() {
if (services == null) {
services = new HashMap<String, Service>();
Map<String, Object> beans = context.getBeansWithAnnotation(ExampleAnnotation.class);
for (Object bean : beans.values()) {
ExampleAnnotation ann = bean.getClass().getAnnotation(ExampleAnnotation.class);
services.put(ann.value(), (Service) bean);
}
}
}
}
Sticking them in a map sounds fine. If it's a Spring-managed map (using util:map, or in Java config), that's better than creating it somewhere else, because then Spring owns all the object references and can manage their lifecycle properly.
If the beans (A, B) you are talking about are SessionScope its no problem at all, they will be selected correctly.
public class BusinessLogic {
private BaseClassOfBeanAandB bean;
public void methodCalledByUserAorB() {
bean.doFoo();
}
}