Spring injects a one-element Set instead of an empty Set - java

We have an application where we are trying to inject an empty java.util.HashSet into a member of type java.util.Set, in a class which itself is a #Component. Spring seems to inject a HashSet with one element of the containing type. Any idea why Spring doesn't just inject an empty set?
Set element class:
#Component
public class SetElement
{
private String value;
public String getValue()
{
return value;
}
}
Class that contains a Set as a member:
#Component
public class MyClassWithSet
{
#Autowired
private Set<SetElement> setOfElements;
protected void setStringSet(Set<SetElement> stringSet)
{
this.setOfElements = stringSet;
}
public Set<SetElement> getStringSet()
{
return Collections.unmodifiableSet(setOfElements);
}
}
Spring.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.0.xsd
">
<bean id="setOfElements" class="java.util.HashSet" />
<context:component-scan base-package="com.vikdor.db " />
</beans>
Sample test case to confirm the behavior
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration(locations =
{ "classpath:META-INF/spring.xml" })
public class SpringSetTest
{
#Autowired
private MyClassWithSet myClassWithSet;
#Test
public void test()
{
assertNotNull(myClassWithSet);
assertNotNull(myClassWithSet.getStringSet());
assertTrue(myClassWithSet.getStringSet().isEmpty());
}
}

If you use #Autowired on a typed collection instance, then all beans in the application context that satisfy the type are injected:
It is also possible to provide all beans of a particular type from the
ApplicationContext by adding the annotation to a field or method that
expects an array of that type [...] The same applies for typed
collections:
public class MovieRecommender {
private Set<MovieCatalog> movieCatalogs;
#Autowired
public void setMovieCatalogs(Set<MovieCatalog> movieCatalogs) {
this.movieCatalogs = movieCatalogs;
}
// ...
}
http://static.springsource.org/spring/docs/3.1.x/spring-framework-reference/html/beans.html#beans-autowired-annotation
Thus, your single instance of SetElement is injected into the #Autowired Set<SetElement>. A possible solution would be to use a setter for the field. Alternatively, you could use the #Qualifier annotation or the #Resource annotation to refer to the bean by name.

Related

Constructor works fine with and without #Autowired

I have tennisCoach object created by Spring framework:
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml") ;
Coach theCoach = context.getBean("tennisCoach", Coach.class);
Can't understand why I need #Autowired annotation in TennisCoach constructor in code below. It works fine with and without #Autowired annotation.
#Component
public class TennisCoach implements Coach {
private FortuneService fortuneService;
#Autowired
public TennisCoach(FortuneService theFortuneService) {
fortuneService = theFortuneService;
}
#Override
public String getDailyWorkout() {
return "Practice your backhand volley";
}
#Override
public String getDailyFortune() {
return fortuneService.getFortune();
}
}
UPD
Content of applicationContext.xml :
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd">
<context:component-scan base-package="com.luv2code.springdemo"></context:component-scan>
</beans>
From #Autowired Javadoc:
If a class only declares a single constructor to begin with, it will always be used, even if not annotated.
Since Spring 4.3 you don’t need the #Autowired annotation as soon as you have the only constructor in your class.
Here #Autowired is used for constructor injection. TennisCoach has a dependency on FortuneService and it is injected through constructor. I'm not sure how you have configured beans in applicationContext.xml

A Java config analog of XML configuration not working

TL/DR: The problem boils down to creating a custom Spring scope, injecting a prototype-like scoped bean into a singleton with proxyMode = ScopedProxyMode.TARGET_CLASS but still getting a singleton in the Java config version of the configuration (whereas it works fine with XML).
UPDATE: Problem solved, see answer.
I'm using jBehave to write BDD test scenarios for our Spring application. We recently thought that we need independence in executing test scenarios (meaning that test context has to be reset before each scenario) and found this article on the web that addresses exactly the issue we're dealing with.
The article advises creating a custom Spring Scenario scope, assigning it to the class that represents test context and injecting an AOP proxy instead of the context file.
I've coded everything in accordance with the article and it worked great, but the thing is we need it in terms of Java config, not XML, and when I converted all the changes to Java config, it stopped working - meaning the Map in StoryContext was not reset after each test scenario and contained values from the previous scenario.
My changes were as follows:
marked the ScenarioScope class with the #Component annotation:
#Component
public class ScenarioScope implements Scope {
private final ConcurrentMap<String, Object> cache = new ConcurrentHashMap<>();
#BeforeScenario
public void startScenario() {
cache.clear();
}
#Override
public Object get(String name, ObjectFactory<?> objectFactory) {
return cache.putIfAbsent(name, objectFactory.getObject());
}
#Override
public Object remove(String name) {
return cache.remove(name);
}
#Override
public void registerDestructionCallback(String name, Runnable callback) {
}
#Override
public Object resolveContextualObject(String key) {
return null;
}
#Override
public String getConversationId() {
return "scenario scope";
}
}
created a Spring configuration class to add the new scope:
#Configuration
public class SpringConfiguration {
#Bean
public static CustomScopeConfigurer scopeConfigurer() {
CustomScopeConfigurer configurer = new CustomScopeConfigurer();
configurer.addScope("scenario", new ScenarioScope());
return configurer;
}
}
annotated the StoryContext class with the #Component and #Scope annotations:
#Component
#Scope(value = "scenario", proxyMode = ScopedProxyMode.TARGET_CLASS)
public class StoryContext {
private Map<String, Object> storyContext = new HashMap<>();
public void put(String key, Object value) {
storyContext.put(key,value);
}
public <T> T get(String key, Class<T> tClass) {
return (T) storyContext.get(key);
}
#PostConstruct
public void clearContext() {
storyContext.clear();
}
}
To my knowledge, the code above is analogous to the XML configuration, which was as follows:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation=" http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop-3.0.xsd
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd">
<context:annotation-config />
<context:component-scan base-package="foo"/>
<bean id="scenarioScope" class="foo.ScenarioScope"/>
<bean class="foo.CustomScopeConfigurer">
<property name="scopes">
<map>
<entry key="scenario" value-ref="scenarioScope"/>
</map>
</property>
</bean>
<bean id="storyContext" class="foo.StoryContext" scope="scenario">
<aop:scoped-proxy/>
</bean>
</beans>
Can anyone please point me to why the Java config is not working as expected? I've spent some time researching stackoverflow but the majority of similar questions is solved by adding proxyMode = ScopedProxyMode.TARGET_CLASS to the #Scope annotation, which I did.
UPDATE: So I tried to gradually move from XML to Java config by commenting / decommenting corresponding lines in the files and figured out that the problem is in this part of the code:
<bean class="foo.CustomScopeConfigurer">
<property name="scopes">
<map>
<entry key="scenario" value-ref="scenarioScope"/>
</map>
</property>
</bean>
When I replace it with
#Configuration
public class SpringConfiguration {
#Bean
public static CustomScopeConfigurer scopeConfigurer() {
CustomScopeConfigurer configurer = new CustomScopeConfigurer();
configurer.addScope("scenario", new ScenarioScope());
return configurer;
}
}
the StoryContext bean becomes a singleton. I tried doing it another way through registering a custom BeanFactoryPostProcessor and using the registerScope() method as described here, but it didn't work either.
I've managed to solve the problem, and the solution was trivial: the ScenarioScope instance in the SpringConfiguration class has to be managed by the Spring container rather than be created via the new() operator:
#Configuration
public class SpringConfiguration {
#Bean
public static CustomScopeConfigurer scopeConfigurer(ScenarioScope scenarioScope) {
CustomScopeConfigurer configurer = new CustomScopeConfigurer();
configurer.addScope("scenario", scenarioScope);
return configurer;
}
}

SpringBoot define custome annotation include #Configuration #ImportResource

I'm try to define a custome annotation include #Configuration and #ImportResource
but #ImportResource doesn't work
Any suggestions?
#Documented
#Configuration
#ImportResource
#Target({ElementType.TYPE})
#Order(Ordered.HIGHEST_PRECEDENCE)
#Retention(RetentionPolicy.RUNTIME)
public #interface EnableXXConfiguration {
#AliasFor(annotation = ImportResource.class , attribute = "value")
String[] value() default {};
}
#ImportResource contains two attributes value and locations. The value attribute is ultimately alias for locations attribute so using either of the aliases works fine. Keeping your Custom annotation(EnableXXConfiguration) declaration (the one using value attribute) as it, use below code snippet.
#EnableXXConfiguration(value = { "context1.xml", "com/example/stackoverflow/context2.xml"})
public class DemoApp {
#Autowired
private BeanA beanA;
#Autowired
private BeanB beanB;
public static void main(String[] args) {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(DemoApp.class);
DemoApp demoAppObj = (DemoApp) context.getBean("demoApp");
System.out.println("BeanA member: " + demoAppObj.getBeanA());
System.out.println("BeanB member: " + demoAppObj.getBeanB());
}
public BeanA getBeanA() {
return beanA;
}
public BeanB getBeanB() {
return beanB;
}
}
Assume we are using two xmlss placed at two different locations. context1.xml is placed in resource folder(src/main/resource) and context2.xml is placed at any other location (here at: src/main/java/com/example/stackoverflow)
context1.xml
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="beanA" class="com.example.stackoverflow.BeanA" />
</beans>
context2.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="beanB" class="com.example.stackoverflow.BeanB" />
</beans>

No bean named is defined Exception

package com.mkyong.output;
IOutputGenerator.java
public interface IOutputGenerator
{
public void generateOutput();
}
package com.mkyong.output;
OutputHelper.java
#Component
public class OutputHelper {
#Autowired
IOutputGenerator outputGenerator;
public void generateOutput() {
outputGenerator.generateOutput();
}
/*//DI via setter method
public void setOutputGenerator(IOutputGenerator outputGenerator) {
this.outputGenerator = outputGenerator;
}*/
}
package com.mkyong.output.impl;
CsvOutputGenerator.java
#Component
public class CsvOutputGenerator implements IOutputGenerator {
public void generateOutput() {
System.out.println("This is Csv Output Generator");
}
}
SpringBeans.xml
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-2.5.xsd">
<context:component-scan base-package="com.mkyong" />
</beans>
i am getting this exception Exception in thread "main" org.springframework.beans.factory.NoSuchBeanDefinitionException: No bean named 'OutputHelper' is defined
even though i have marked OutputHelper as component.
I have changed
OutputHelper output = (OutputHelper) context.getBean("OutputHelper");
to
OutputHelper output = (OutputHelper) context.getBean("outputHelper");
and it worked.
Hi i think you haven't added following in your Spring XML configuration
xmlns:mvc="http://www.springframework.org/schema/mvc"
http://www.springframework.org/schema/mvc
http://www.springframework.org/schema/mvc/spring-mvc.xsd
<mvc:annotation-driven/>
you need to see top exception and read the whole line.
i guess there have a exception is nested exception just like #Autowired xxxxxx,meas autowired fail.
i have notice this:
#Autowired
IOutputGenerator outputGenerator;
and
#Component
public class CsvOutputGenerator implements IOutputGenerator
so, in the default, class name is used to #Autowired,you can rewrite to
#Autowired
IOutputGenerator csvOutputGenerator;
notice:
"csvOutputGenerator" first letter is lowercase
the easier option would be to enable annotations in beans already registered in the application context, means that you can just use #Autowired instead of getting manually all beans with context.getBean()
just add this line to your SpringBeans.xml
<context:annotation-config>
if you really want to understand what you are doing reading this could help.

#Qualifier Annotation in Spring is not working

I have just learnt Spring Framework and have been using Spring 2.5 for this learning. I have created three beans with these classes
Food.java
package com.spring.danipetrick;
public interface Food {
void ingredients();
}
NasiGoreng.java
package com.spring.danipetrick;
public class NasiGoreng implements Food {
public NasiGoreng() {
}
public void ingredients() {
System.out.println("Rice, Coconut oil, Egg, Crackers");
}
#Override
public String toString() {
return "Nasi Goreng";
}
}
Rendang.java
package com.spring.danipetrick;
public class Rendang implements Food {
public void ingredients() {
System.out.println("Beef, Coconut milk, spices");
}
#Override
public String toString() {
return "Rendang";
}
}
PecintaKuliner.java
package com.spring.danipetrick;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
public class PecintaKuliner {
#Autowired
#Qualifier("nasigoreng")
private Food food;
#Autowired
public void setFood(Food food) {
this.food = food;
}
public Food getFood() {
return this.food;
}
public void sayMaknyus() {
System.out.println(food.toString() + " memang maknyus...");
}
}
Xml configuration, qualifier-test.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd">
<context:annotation-config />
<bean id="bondan" class="com.spring.danipetrick.PecintaKuliner">
</bean>
<bean id="rendang" class="com.spring.danipetrick.Rendang"/>
<bean id="nasigoreng" class="com.spring.danipetrick.NasiGoreng" />
</beans>
Finally, class with main method is QualifierTestMain.java:
package com.spring.danipetrick;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class QualifierTestMain {
public static void main(String[] args) {
ApplicationContext context = new ClassPathXmlApplicationContext("/qualifier-test.xml");
PencintaKuliner bondan = (PencintaKuliner)context.getBean("bondan");
bondan.sayMaknyus();
}
}
When I run this project, I have an error like this
Caused by:
org.springframework.beans.factory.NoSuchBeanDefinitionException: No
unique bean of type [com.spring.danipetrick.Food] is defined: expected
single matching bean but found 2: [rendang, nasigoreng]
Why #Qualifier annotation is not working in my case?
Both your method and field are annotated with #Autowired. As such, Spring will try to inject both. On one of the runs, it will try to inject
#Autowired
#Qualifier("nasigoreng")
private Food food;
which will work because the injection target is qualified.
The method however
#Autowired
public void setFood(Food food) {
this.food = food;
}
does not qualify the injection parameter so Spring doesn't know which bean to inject.
Change the above to
#Autowired
public void setFood(#Qualifier("nasigoreng") Food food) {
this.food = food;
}
But you should decide one or the other, field or setter injection, otherwise it is redundant and may cause errors.
I tried with Spring 4.2.4. Problem resolved just by adding
<context:annotation-config /> in configuration file.
Try only removing #Autowired from setFood() in PecintaKuliner
like
#Autowired
#Qualifier("nasigoreng")
private Food food;
public void setFood(Food food) {
this.food = food;
}
Try removing you constructor from the NasiGoreng class. It worked for me.
For others facing issue in Spring 5 -- Use xmlns based configuration..
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">
<context:annotation-config/>
</beans>

Categories