Cannot resolve bean in SpEL for Spring Data MongoDB collection name - spring-boot

I am trying to customize the collection name where an entity class is saved into and indexed, using Spring Data MongoDB and Spring Batch. The class is declared as follows:
#Document
#CompoundIndex(name = "unique_source", def = "{'fid': 1, 'sid': 1}", unique = true, background = true)
public class VariantSource {
...
}
And the item writer:
public class VariantSourceMongoWriter extends MongoItemWriter<VariantSource> {
public VariantSourceEntityMongoWriter(MongoOperations mongoOperations, String collectionName) {
setTemplate(mongoOperations);
setCollection(collectionName);
}
}
Saving works fine: the objects are written into the collection provided as argument. The problem is that the indexes are created in the default collection, named after the class name (variantSource).
After reading this and this, I created the following:
public class MongoCollections {
public String getCollectionFilesName() {
return "my_custom_collection_name"; // TODO Dynamic value
}
}
#Configuration
public class MongoCollectionsConfiguration {
#Bean
public MongoCollections mongoCollections() {
return new MongoCollections();
}
#RunWith(SpringRunner.class)
#ContextConfiguration(classes = {MongoCollectionsConfiguration.class})
public class VariantSourceMongoWriterTest {
#Autowired
private MongoCollections mongoCollections;
}
I have checked the instance is correctly autowired into the unit tests, but I can't make it work with SpEL.
After changing the #Document annotation to look like this:
#Document(collection = "#{#mongoCollections.getCollectionFilesName()}")
the following exception is thrown:
org.springframework.expression.spel.SpelEvaluationException: EL1057E:(pos 1): No bean resolver registered in the context to resolve access to bean 'mongoCollections'
And if I use this:
#Document(collection = "#{mongoCollections.getCollectionFilesName()}")
the exception is this one:
org.springframework.expression.spel.SpelEvaluationException: EL1007E:(pos 0): Property or field 'mongoCollections' cannot be found on null
Finally, the following creates a collection with the name as specified, symbols included:
#Document(collection = "#mongoCollections.getCollectionFilesName()")

As pointed by this answer, to fix the injection:
#Document(collection = "#{mongoCollections.getCollectionFilesName()}")
SpelEvaluationException: EL1007E:(pos 0): Property or field 'mongoCollections' cannot be found on null
(or a direct method bean: #Document(collection = "#{getCollectionFilesName}")), try setting the ApplicationContext into the MongoMappingContext (which is used to instantiate the MongoConverter, and later the MongoTemplate):
#Bean
public MongoMappingContext MongoMappingContext() {
MongoMappingContext mappingContext = new MongoMappingContext();
mappingContext.setApplicationContext(applicationContext);
return mappingContext;
}

Make sure that your bean mongoCollections is registered in the application context,
and also correct the SpEL expression as below.
#Document(collection = "#{#mongoCollections.getCollectionFilesName()}")

I was able to get my #Document tag to access a bean by simply changing my MongoTemplate configuration file.
Previously, I had it set up like this:
#Configuration
public class MongoTemplateConfiguration {
...
#Bean
public MongoTemplate mongoTemplate() {
...
return new MongoTemplate(...);
}
}
Changing it to follow this (3.2 Java Configuration) format was all I needed in order to remove the "bean resolver" error:
#Configuration
public class MongoTemplateConfiguration extends AbstractMongoClientConfiguration {
...
#Override
#Bean
public com.mongodb.client.MongoClient mongoClient() {
MongoClientSettings settings = ...;
return MongoClients.create(settings);
}
}

Related

how to get bean scope on spring?

I have the following class:
public class ServiceFactory {
private ServiceFactory() {
}
public static <T extends XXXX> T loadService(Class<T> klass) {
ApplicationContext applicationContext = ApplicationContextProvider.getApplicationContext();
return applicationContext.getBean(klass);
}
}
It loads beans at runtime (I have a specific reason to do it like this).
I need to check if the bean is annotated with #Scope(BeanDefinition.SCOPE_PROTOTYPE) or just enforce it to be a prototype.
How would I do this?
First you need to find a bean name for your class. Then you may look for BeanDefinition using that name and get scope.
public <T> String findScope(ConfigurableApplicationContext applicationContext, Class<T> type) {
String[] names = applicationContext.getBeanFactory().getBeanNamesForType(type);
if(names.length != 1){
throw new IllegalArgumentException("Could not find bean of type" + type.getCanonicalName());
}
return applicationContext.getBeanFactory().getBeanDefinition(names[0]).getScope();
}

spring data JPA & spring data elasticsearch; No property index found for type?

I'm unsure why this is happening! I've got a class that is used by spring data elasticsearch and spring data jpa, but when I try run my application I get an error.
Error creating bean with name 'articleSearch':
Invocation of init method failed; nested exception is
org.springframework.data.mapping.PropertyReferenceException:
No property index found for type Article!
Caused by: org.springframework.data.mapping.PropertyReferenceException:
No property index found for type Article!
at org.springframework.data.mapping.PropertyPath.<init>(PropertyPath.java:77) ~[spring-data-commons-1.11.4.RELEASE.jar:na]
I've got the following application class:
#SpringBootApplication
#EnableAsync
#ComponentScan(basePackages = {"com.article.models", "com.user"})
public class ArticleApplication {
And the following elasticsearch config:
#Configuration
#EnableElasticsearchRepositories(basePackages = "com.article.search")
public class ElasticSearchConfiguration {
#Resource
private Environment environment;
#Bean
public Client client() {
TransportClient client = new TransportClient();
TransportAddress address = new InetSocketTransportAddress(environment.getProperty("elasticsearch.host"), Integer.parseInt(environment.getProperty("elasticsearch.port")));
client.addTransportAddress(address);
return client;
}
#Bean
public ElasticsearchOperations elasticsearchTemplate() {
return new ElasticsearchTemplate(client());
}
}
This is how I've setup my model class:
#Entity
#Table(name="article")
#Document(indexName="article", type="articles")
public class Article implements Serializable {
I've then got a package search that extends the elasticsearchrepository, like so:
public interface ArticleSearch extends ElasticsearchRepository<Article, String> {
I'm trying to autowire the articlesearch class inside another service which is causing the error to occur:
#Autowired
ArticleSearch articleSearch;
What am I missing here?! I guess it's a bit more complex when trying to use data-jpa + data-elasticsearch.
I found out why this was happening. I'm not sure why, but spring didn't seem to be picking up my ElasticSearchConfiguration configuration class!
So I simply moved all the contents from that and dumped it in my main application class (where all my other config is).
I also removed component scan & added the enablejparepository + enableelasticsearchrepository annotations to my main class. Here is what it looks like now:
#SpringBootApplication
#EnableAsync
#EnableElasticsearchRepositories(basePackages = "com.article.search")
#EnableJpaRepositories(basePackages = {"com.article.dao", "com.user.dao"})
public class ArticleApplication {

Multiple Spring Configuration files (one per Profile)

I'm a Spring rookie and trying to benefit from the advantages of the easy 'profile' handling of Spring. I already worked through this tutorial: https://spring.io/blog/2011/02/14/spring-3-1-m1-introducing-profile and now I'd like to adapt that concept to an easy example.
I've got two profiles: dev and prod. I imagine a #Configuration class for each profile where I can instantiate different beans (implementing a common interface respectively) depending on the set profile.
My currently used classes look like this:
StatusController.java
#RestController
#RequestMapping("/status")
public class StatusController {
private final EnvironmentAwareBean environmentBean;
#Autowired
public StatusController(EnvironmentAwareBean environmentBean) {
this.environmentBean = environmentBean;
}
#RequestMapping(method = RequestMethod.GET)
Status getStatus() {
Status status = new Status();
status.setExtra("environmentBean=" + environmentBean.getString());
return status;
}
}
EnvironmentAwareBean.java
public interface EnvironmentAwareBean {
String getString();
}
EnvironmentAwareBean.java
#Service
public class DevBean implements EnvironmentAwareBean {
#Override
public String getString() {
return "development";
}
}
EnvironmentAwareBean.java
#Service
public class ProdBean implements EnvironmentAwareBean {
#Override
public String getString() {
return "production";
}
}
DevConfig.java
#Configuration
#Profile("dev")
public class DevConfig {
#Bean
public EnvironmentAwareBean getDevBean() {
return new DevBean();
}
}
ProdConfig.java
#Configuration
#Profile("prod")
public class ProdConfig {
#Bean
public EnvironmentAwareBean getProdBean() {
return new ProdBean();
}
}
Running the example throws this exception during startup (SPRING_PROFILES_DEFAULT is set to dev):
(...) UnsatisfiedDependencyException: (...) nested exception is org.springframework.beans.factory.NoUniqueBeanDefinitionException: No qualifying bean of type [EnvironmentAwareBean] is defined: expected single matching bean but found 3: prodBean,devBean,getDevBean
Is my approach far from a recommended configuration? In my opinion it would make more sense to annotate each Configuration with the #Profile annotation instead of doing it for each and every bean and possibly forgetting some variants when new classes are added later on.
Your implementations of EnvironmentAwareBean are all annotated with #Service.
This means they will all be picked up by component scanning and hence you get more than one matching bean. Do they need to be annotated with #Service?
Annotating each #Configuration with the #Profile annotation is fine. Another way as an educational exercise would be to not use #Profile and instead annotate the #Bean or Config classes with your own implementation of #Conditional.

Spring #Bean factory method invoked ahead of #Value variable being resolved

I'm having an issue with the order resolution of Spring Java/XML configuration. It seems that the #Value annotations are not being resolved ahead of #Bean factory methods being invoked, specifically when loading properties from external XML configuration.
This is a condensed version of what I'm doing:
#Configuration
#ImportResource({"classpath:configurable-context.xml"})
public class SecurityConfig {
#Value("#{myProps['my.custom.key']}")
private String someValue = null;
#Bean
public SomeObject someObject() {
return new SomeObject(someValue); // Fails because someValue == null
}
}
and this is configurable-context.xml :
...
<util:map id="myProps">
<entry key="my.custom.key" value="myVal"/>
</util:map>
...
The issue is that the someObject(...) factory method is invoked ahead of the #Value annotation being evaluated for someValue, so this is null at the time.
Any thoughts on how I can force resolution of the someValue variable ahead of the factory method being invoked?
Update
As inspired by response from #Ekem, this code worked for me using XML sourced properties:
#Configuration
#ImportResource({"classpath:configurable-context.xml"})
public class SecurityConfig {
#Resource(name = "myProps")
private Properties myProps;
#Bean
public SomeObject someObject() {
return new SomeObject(myProps.getProperty("my.custom.key")); // Now works :-)
}
}
Change your configuration as follows so that myProps bean is initialized first
#Configuration
#ImportResource({"classpath:configurable-context.xml"})
public class SecurityConfig {
#Value("#{myProps['my.custom.key']}")
private String someValue = null;
#Bean
#DependOn("myProps")
public SomeObject someObject() {
return new SomeObject(someValue);
}
}
Alternatively to make your configuration clean use of the environment abstraction as follows
#Configuration
#PropertySource("classpath:application.properties")
public class SecurityConfig {
#Autowired
private private Environment env;
#Bean
public SomeObject someObject() {
return new SomeObject(env.getProperty("my.custom.key"));
}
}
Then add an application.properties file to the root of your classpath with an entry my.custom.key=myVal
This will eliminate the need for an xml application context just to define a hardcoded property

Spring #Required properties when creating #Bean annotated beans

I'm developing a Spring Boot application and am trying out using Java annotation-based bean creation (using #Configuration and #Bean) rather than the familiar old XML-based bean creation. I'm puzzled though. If I attempt to create a bean in XML but fail to set an #Required property I get a BeanInitializationException when the application context is created. In my trials so far with annotation-based bean creation though this does not seem to be the case.
For example:
public class MyClass {
...
#Required
public void setSomeProp(String val){
}
}
Then in Spring XML:
<bean class="MyClass"/>
This will blow up during application startup (and IntelliJ flags it) because the required property is not set. But the same does not seem to be true of this:
#Configuration
public class MyConfig {
#Bean
public MyClass myClass() {
return new MyClass();
}
}
This application starts up just fine even though the required property is not ever set. I must be missing something here, because this seems like a pretty key feature in Spring.
UPDATE
I did some digging & debugging and it turns out that the bean definition is somehow being flagged to skip checking that #Required fields are set. In the Spring class 'RequiredAnnotationBeanPostProcessor' the boolean method 'shouldSkip()' is returning true for beans created this way. When I used the debugger to force that method to return false bean creation did indeed blow up with the expected exception.
Seeing as I'm making a pretty basic Spring Boot application I'm inclined (as Zergleb suggests) to submit this as a bug.
UPDATE 2
Some further debugging has revealed that even if the field is getting set forcing the check still throws the same exception, as if it hadn't been set. So perhaps dunni is correct and there is no way for this to work with #Bean notation.
As you said I also could not get #Required to run as expected this may be a bug and needs to be reported. I have a few other suggestions that did work for me.
Class annotated with #Configuration
//With the bean set up as usual These all worked
#Bean
public MyClass myClass() {
return new MyClass();
}
When you annotate the class #Component and load using component scanning works as expected.(The component scanning part is important you either need your #Configuration class to either have #ComponentScan or perhaps remove #Configuration and replace with #SpringBootApplication and this will enable scanning for components without needing to wire them up using #Bean configs)
#Component // Added this
public class MyClass {
...
#Required //Failed as expected
public void setSomeProp(String val){
}
}
Use #Autowired(required=true) //Fails with BeanCreationException //No qualifying bean of type [java.lang.String] found for dependency
//No more #Component
public class MyClass {
...
#Autowired(required=true) //Fails
public void setSomeProp(String val){
}
}
#Autowired required=false //Does not crash
public class MyClass {
...
#Autowired(required=false) //Simply never gets called if missing
public void setSomeProp(String val){
}
}
#Value //Does not work if test.property is missing // Could not resolve placeholder 'test.property' in string value "${test.property}
public class MyClass {
#Value("${test.property}")
String someProp;
//This getter is not neccesary neither is a setter
public String getSomeProp() {
return this.someProp;
}
}
#Value with default value//Does not crash // When getSomeProp is called it returns "My Default Value"(Unless you have test.property=Anything in your application.properties file then it returns "Anything"
public class MyClass {
#Value("${test.property:My Default Value}")
String someProp;
//This getter is not neccesary neither is a setter
public String getSomeProp() {
return this.someProp; //Returns "My Default Value"
}
}
Inside your #Configuration file also fails if it cannot find anything to populate String someProp in the myClass method
#Bean
public MyClass myClass(String someProp) { //Fails being unable to populate this arg
MyClass myObj = new MyClass();
myObj.setSomeProp(someProp);
return ;
}
If course this won't work, since you create the object of MyClass yourself (new MyClass()), thus the annotations are not evaluated. If you create a bean with a #Bean method, the container will only make sure, that all dependencies are there (method parameters) and that the bean scope is adhered to, meaning if it's a singleton bean, only one bean is created per application context. The creation of the bean/object itself is solely the responsibility of the developer.
The equivalent of the xml <bean> tag is annotating the class with #Component, where the bean is created completely by the container, thus the annotations are evaluated.
As it is being said that when you are having your own #Configuration class where you are creating the bean by itself, #Required doesn't apply there.
When you already have a #Component, let Spring Boot do the component scan and at the required setter property you can add #Autowired and it will work fine.
Found this link on web- https://www.boraji.com/spring-required-annotation-example
For example:
I have a Component called Employee having Id and Name.
#Component
public class Employee {
int id;
String name;
public int getId() {
return id;
}
#Autowired
#Required
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
I have a Configuration class called AppConfig.java
#Configuration
public class AppConfig {
#Bean
public int getId() {
return 1;
}
}
So now we see, that component Employee needs an Id property for binding during startup, so I wrote bean method of type Integer, which will get autowired during runtime. If you do not write a bean of type Integer, it will result a BeanCreationException.
And here is my main class file.
#SpringBootApplication
public class SingletonApplication {
public static void main(String[] args) {
ApplicationContext ctx =
SpringApplication.run(SingletonApplication.class, args);
Employee emp = (Employee)ctx.getBean(Employee.class);
System.out.println(emp.getId());
}
}

Resources