How to autowire a list of properties with Spring? [duplicate] - java

I've been searching but cannot find these steps. I hope I'm missing something obvious.
I have a properties file with the following contents:
machines=A,B
I have another file like that but having a different number of members in the machines element like this:
machines=B,C,D
My question is how do I load this variable-length machines variable into a bean in my spring config in a generic way?
something like this:
<property name="machines" value="${machines}"/>
where machines is an array or list in my java code. I can define it however I want if I can figure out how to do this.
Basically I'd rather have spring do the parsing and stick each value into a list element instead of me having to write something that reads in the full machines string and do the parsing myself (with the comma delimiter) to put each value into an array or list. Is there an easy way to do this that I'm missing?

You may want to take a look at Spring's StringUtils class. It has a number of useful methods to convert a comma separated list to a Set or a String array. You can use any of these utility methods, using Spring's factory-method framework, to inject a parsed value into your bean. Here is an example:
<property name="machines">
<bean class="org.springframework.util.StringUtils" factory-method="commaDelimitedListToSet">
<constructor-arg type="java.lang.String" value="${machines}"/>
</bean>
</property>
In this example, the value for 'machines' is loaded from the properties file.
If an existing utility method does not meet your needs, it is pretty straightforward to create your own. This technique allows you to execute any static utility method.

Spring EL makes easier.
Java:
List <String> machines;
Context:
<property name="machines" value="#{T(java.util.Arrays).asList('${machines}')}"/>

If you make the property "machines" a String array, then spring will do it automatically for you
machines=B,C,D
<property name="machines" value="${machines}"/>
public void setMachines(String[] test) {

Since Spring 3.0, it is also possible to read in the list of values using the #Value annotation.
Property file:
machines=B,C,D
Java code:
import org.springframework.beans.factory.annotation.Value;
#Value("#{'${machines}'.split(',')}")
private List<String> machines;

You can inject the values to the list directly without boilerplate code.(Spring 3.1+)
#Value("${machines}")
private List<String> machines;
for the key "machines=B,C,D" in the properties file by creating following two instance in your configuration.
#Bean
public static PropertySourcesPlaceholderConfigurer propertyPlaceholderConfigurer() {
return new PropertySourcesPlaceholderConfigurer();
}
#Bean
public ConversionService conversionService() {
return new DefaultConversionService();
}
Those will cover all the separate based split and whitespace trim as well.

Related

How do I use a variable from an application.yml file in my normal code?

For example, let's say that in my yml file I had a variable called indicator. And based on what the indicator variable's value was I want the code to do something different. How would I access the yml variable in the regular code and use it accordingly?
You can use this:
#Value("${your.path.yml.string}")
private String x;
YML:
your:
path:
yml:
string: hello
x will be "hello"
You need to use Spring Expression Language which says we should write it as
#Value("${spring.application.name}")
private String appName;
For Default value if key is not present in yaml/yml or properties file
#Value("${spring.application.name: defaultValue}")
private String appName;
The last way you can fetch value is using environment object
#Autowired
private Environment environment;
String appName = environment.get("spring.application.name");
You can add #Value annotation to any field in your beans.
#Value("$(path.to.your.variable)")
String myString;
Works with constructors as well.
public MyClass(#Value("$(path.to.your.variable)") String myString) {
You can use #Value on fields or parameters to assign the property to some variable.
Property example:
#Value("${indicator}")
private String indicator
Parameter example:
private void someMethod(#Value("${indicator}") String indicator) {
...
}
Then you can use indicator as you want.
Note: the class where you use #Value should be a Spring Component
With Spring-Boot, you have the file application.yml automatically provided for you. What you can do is adding a property in this file, for instance:
my.properties: someValue
Then, in one of your Spring Bean (either define with #Component or #Bean) you can retrieve this value using the annotation #Value. Then, do whatever you want with this variable.
For instance:
#Component
public class MyClass {
#Value("${my.properties"}
private String myProp; // will get "someValue" injected.
...
// Just use it in a method
public boolean myMethod() {
if(myProp.equals("someValue") {
// do something
} else {
// do something else
}
}
}
The best way to do this is not to have a tight coupling between Spring and your "normal" code at all, but instead to use the normal Java features like constructors along with Spring #Bean methods:
class MyService {
final String indicatorName;
MyService(String indicatorName) {
this.indicatorName = indicatorName;
}
}
... in your configuration class...
#Bean
MyService myService(#Value("indicator.name") String indicatorName) {
return new MyService(indicatorName);
}
Two notes for Spring Boot specifically:
The #ConfigurationProperties feature allows you to map properties onto structured Java data classes and is typically cleaner than using #Value by hand.
Always namespace properties that you define yourself to avoid future collisions with other libraries, so instead of indicator.name use company.project.indicator.name. I recommend looking at DataSourceProperties in Boot to see an example of how to set all this up.
More broadly, though, when you say that you want the code to "do something different", it sounds like the better option might be to have different classes that get activated under different circumstances. Both Spring profiles and Spring Boot auto-configuration help to do this.
The problem statement can be re-defined as Configuration Management in Java.
You should have a component like ConfigManager that gets instantiated as part of your application start up. That component will read a properties file, a yaml in your use case. Subsequent app logic will fetch these values from the ConfigManager exposed as simple key/value pairs.
All that is left for you to identify how to read and parse values from yaml file. This is already answered here:
Parse a YAML file

Injecting list of strings in Guice

I am trying to inject one arraylist of Strings in Guice. For that I am trying to construct list as following in module, which I can inject later into any class:
Multibinder<String> myList =
Multibinder.newSetBinder(binder(), String.class);
myList.addBinding().to("Test1");
myList.addBinding().to("Test2");
In the above line I am getting following error:
The method to(Class<? extends String>) in the type LinkedBindingBuilder<String> is not applicable for the arguments (String)
In this context, I have found this: Guice : Inject an ArrayList of Strings, but I think given solution does not fit my use case.
Use .toInstance()
myList.addBinding().toInstance("Test1");
myList.addBinding().toInstance("Test2");`
I realize this is an older post, but I've been unsuccessful at finding a solution until today. I've wanted to inject a list of comma-delimited strings defined in a property file in a generic way without having to explicitly create #Named providers for every property that needed to be represented as a list of Strings.
Today I found this post (Injecting a List of Strings From a Properties File Using Guice). The relevant part for me was this line in my configure method:
#Override
protected void configure() {
...
binder().convertToTypes(Matchers.only(new TypeLiteral<List<String>>() {
}), (String value, TypeLiteral<?> toType) -> Arrays.asList(value.split(",")));
...
}
I didn't use DefaultListDelimiterHandler as the original solution did, but instead just did a standard string split converted to a List. Now from a property file, I can have something like:
testList=one,two,three
And I can inject it using:
#Inject
#Named("testList")
List<String> testList;

Spring 3+. Resolve properties by prefix in a placeholder

In my Java class I have a field of type, say, java.util.Properties or java.util.Map.
I'd like Spring to inject the field with all properties which start with the given prefix. Ideally by specifying wildcard in the property annotation, smth like #Value(${prefix.*}) that doesn't seem to work unfortunately.
How can I achieve that?
AFAIK there isn't a direct way to achieve what you are looking for.
You can however use Spring EL in combination with custom implementation of org.springframework.core.io.support.PropertiesLoaderSupport.
First extend the PropertiesLoaderSupport (or any of it's subclass). Introduce a method (say filterProps), which will return java.util.Properties object with filtered properties, such as below
public class CustomPropertyLoader extends PropertiesLoaderSupport {
public Properties filterProperties(String prefix){
Properties temp = new Properties();
for(Properties props : this.localProperties){
/*
* Iterate over props and filter them as
* per prefix (or any custom logic) to temp
*/
}
return temp;
}
}
TIP - You can use a regex for more generic scenarios instead of String as method argument.
Second define a corresponding bean of above class
<bean id="customPropertyLoader" class="x.y.z.CustomPropertyLoader">
<property name="locations" value="classpath*:/**/some*.properties"/>
</bean>
Last annotate the field of type java.util.Properties in your class as
#Value(#{customPropertyLoader.filterProperties('prefix.*')})
P.S.: Please excuse any compilation / syntax error as I haven't compiled and check the setup; but it should work. In case not, let know in comments.

Spring Data, Mongo, and #TypeAlias: reading not working

The issue
Awhile back I started using MongoDB and Spring Data. I'd left most of the default functionality in place, and so all of my documents were stored in MongoDB with a _class field pointing to the entity's fully-qualified class name.
Right away that didn't "smell" right to me, but I left it alone. Until recently, when I refactored a bunch of code, and suddenly none of my documents could be read back from MongoDB and converted into their (refactored/renamed) Java entities. I quickly realized that it was because there was now a fully-qualified-classname mismatch. I also quickly realized that--given that I might refactor again sometime in the future--if I didn't want all of my data to become unusable I'd need to figure something else out.
What I've tried
So that's what I'm doing, but I've hit a wall. I think that I need to do the following:
Annotate each entity with #TypeAlias("ta") where "ta" is a unique, stable string.
Configure and use a different TypeInformationMapper for Spring Data to use when converting my documents back into their Java entities; it needs to know, for example, that a type-alias of "widget.foo" refers to com.myapp.document.FooWidget.
I determined that I should use a TypeInformationMapper of type org.springframework.data.convert.MappingContextTypeInformationMapper. Supposedly a MappingContextTypeInformationMapper will scan my entities/documents to find #TypeAlias'ed documents and store an alias->to->class mapping. But I can't pass that to my MappingMongoConverter; I have to pass a subtype of MongoTypeMapper. So I am configuring a DefaultMongoTypeMapper, and passing a List of one MappingContextTypeInformationMapper as its "mappers" constructor arg.
Code
Here's the relevant part of my spring XML config:
<bean id="mongoTypeMapper" class="org.springframework.data.mongodb.core.convert.DefaultMongoTypeMapper">
<constructor-arg name="typeKey" value="_class"></constructor-arg>
<constructor-arg name="mappers">
<list>
<ref bean="mappingContextTypeMapper" />
</list>
</constructor-arg>
</bean>
<bean id="mappingContextTypeMapper" class="org.springframework.data.convert.MappingContextTypeInformationMapper">
<constructor-arg ref="mappingContext" />
</bean>
<bean id="mappingMongoConverter"
class="org.springframework.data.mongodb.core.convert.MappingMongoConverter">
<constructor-arg ref="mongoDbFactory" />
<constructor-arg ref="mappingContext" />
<property name="mapKeyDotReplacement" value="__dot__" />
<property name="typeMapper" ref="mongoTypeMapper"/>
</bean>
<bean id="mongoTemplate" class="org.springframework.data.mongodb.core.MongoTemplate">
<constructor-arg ref="mongoDbFactory" />
<constructor-arg ref="mappingMongoConverter" />
</bean>
Here's a sample entity/document:
#Document(collection="widget")
#TypeAlias("widget.foo")
public class FooWidget extends Widget {
// ...
}
One important note is that any such "Widget" entity is stored as a nested document in Mongo. So in reality you won't really find a populated "Widget" collection in my MongoDB instance. Instead, a higher-level "Page" class can contain multiple "widgets" like so:
#Document(collection="page")
#TypeAlias("page")
public class Page extends BaseDocument {
// ...
private List<Widget> widgets = new ArrayList<Widget>();
}
The error I'm stuck on
What happens is that I can save a Page along with a number of nested Widgets in Mongo. But when I try to read said Page back out, I get something like the following:
org.springframework.beans.BeanInstantiationException: Could not instantiate bean class [com.myapp.document.Widget]: Is it an abstract class?
I can indeed see pages in Mongo containing "_class" : "page", with nested widgets also containing "_class" : "widget.foo" It just appears like the mapping is not being applied in the reverse.
Is there anything I might be missing?
In the default setting, the MappingMongoConverter creates a DefaultMongoTypeMapper which in turn creates a MappingContextTypeInformationMapper.
That last class is the one responsible for maintaining the typeMap cache between TypeInformation and aliases.
That cache is populated in two places:
In the constructor, for each mappingContext.getPersistentEntities()
When writing an object of an aliased type.
So if you want to make sure the alias is recognized in any context, you need to make sure that all your aliased entities are part of mappingContext.getPersistentEntities().
How you do that depends on your configuration. For instance:
if you're using AbstractMongoConfiguration, you can overwrite its getMappingBasePackage() to return the name of a package containing all of your entities.
if you're using spring boot, you can use #EntityScan to declare which packages to scan for entities
in any case, you can always configure it with a custom set (from a static list or a custom scan) using mongoMappingContext.setInitialEntitySet()
One side note, for an entity to be discovered by a scan, it has to be annotated with either #Document or #Persitent.
More informations can be found in the spring-data-commons Developer Guide
I spent a bunch of time with my debugger and the Spring Data source code, and I learned that Spring Data isn't as good as it probably should be with polymorphism as it should be, especially given the schema-less nature of NoSQL solutions like MongoDB. But ultimately what I did was to write my own type mapper, and that wasn't too tough.
The main problem was that, when reading in my Page document, the default mappers used by Spring Data would see a collection called widgets, then consult the Page class to determine that widgets pointed to a List, then consult the Widget class to look for #TypeAlias information. What I needed instead was a mapper that scanned my persistent entities up front and stored an alias-to-class mapping for later use. That's what my custom type mapper does.
I wrote a blog post discussing the details.
If you extend AbstractMongoConfiguration, you can override method getMappingBasePackage to specify the base package for your documents.
#Configuration
class RepositoryConfig extends AbstractMongoConfiguration {
#Override
protected String getMappingBasePackage() {
return "com.example";
}
}
Update: In spring-data-mongodb 2+ you should use:
#Configuration
class RepositoryConfig extends AbstractMongoConfiguration {
#Override
protected Collection<String> getMappingBasePackages(){
return Arrays.asList("com.example");
}
}
because getMappingBasePackage() is no deprecated and won't work.
Today I ran into the exact same issue. After more research I found out that my subclass was missing a repository. It appears that Spring Data is using the repositories to determine which concrete subclass to create and when it is missing, it falls back to the superclass which in this case is abstract.
So please try to add a FooWidgetRepository and map it to FooWidget with correct ID type. It might work in your case as well.
If you use spring boot with auto-configuration, declaring the following bean can help:
#Bean
MongoMappingContext mongoMappingContext(ApplicationContext applicationContext, MongoCustomConversions conversions) throws ClassNotFoundException {
MongoMappingContext context = new MongoMappingContext();
context.setInitialEntitySet(new EntityScanner(applicationContext).scan(Persistent.class));
context.setSimpleTypeHolder(conversions.getSimpleTypeHolder());
return context;
}
what does the trick is the following line:
new EntityScanner(applicationContext).scan(Persistent.class)
Instead of scanning for Documents it will scan for both Document and TypeAlias, since both of these annotations are Persistent
Andreas Svensson is right, this can be done much simpler than described by Dave Taubler.
I posted a slightly more elaborate answer than Andreas' (including sample code) in this post. Excerpt:
So all you need to do is to declare an "unused" Repository-Interface
for your sub-classes, just like you proposed as "unsafe" in your OP:
public interface NodeRepository extends MongoRepository<Node, String> {
// all of your repo methods go here
Node findById(String id);
Node findFirst100ByNodeType(String nodeType);
... etc.
}
public interface LeafType1Repository extends MongoRepository<LeafType1, String> {
// leave empty
}
public interface LeafType2Repository extends MongoRepository<LeafType2, String> {
// leave empty
}

Can Guice initialize beans?

I've used Spring before (and like it), but thought I'd take a look at Guice.
Is there a way to initialize something like maps or lists into beans using Guice?
For instance, I've done the following before in Spring to inject a list of items I want to process into some bean.
<property name="FilesToProcess">
<list>
<value>file1.xml</value>
<value>file2.xml</value>
</list>
</property>
How can I do this in Guice?
Guice2 has MultiBindings and MapBindings, which should work for you.
https://github.com/google/guice/wiki/Multibindings
Updated:
After looking at this again, it seems that you may be asking how you can inject runtime values into Guice, perhaps as arbitrary objects.
Guice is very focused around doing everything as typesafe code, so it doesn't lend itself
naturally to this. What I've done to provide input to Guice is to create an XML schema and use jaxb to suck this in and inject the resulting objects.
There is a Names.bindProperties method for binding regular old properties into Guice
constants.
There is also some level of integration with Spring, so you may want to look at this as
well.
Guice lets you inject type literals. The syntax is a little strange. There is a blog entry that explains why.
The binding would look something like this:
public class SampleModule extends AbstractModule {
protected void configure() {
bind(new TypeLiteral<List<String>>() {}).
annotatedWith(Names.named("FilesToProcess")).
toInstance(Arrays.asList("file1.xml", "file2.xml"));
}
}
And then your application code could inject the list by name like this:
public class SampleClass {
private final List<String> files;
#Inject
public SampleClass(#Named("FilesToProcess") List<String> files) {
this.files = files;
}
}
I agree with Dave Stenglein for runtime values.
There are frameworks like Obix that are specialized in the configuration. I like Guice for code injection, but they are better for that configuration injection.

Categories

Resources