For springframwork based application, when using xml to declare beans, bean id can be configured by passing a unique value and even a parameter and then solve the value at runtime.
Now I hope to replace all xml configuration to java annotation.
Say I want to create two database beans with different id.
bean.xml
<bean id="A.database" class="org.apache.commons.dbcp.BasicDataSource">
<!-- collaborators and configuration for this bean go here -->
</bean>
<bean id="B.database" class="org.apache.commons.dbcp.BasicDataSource">
<!-- collaborators and configuration for this bean go here -->
</bean>
Then I optimize the upper code to one bean with two property file for two application
bean.xml
<bean id="${appName.database}" class="org.apache.commons.dbcp.BasicDataSource">
<!-- collaborators and configuration for this bean go here -->
</bean>
applicationA.properties
appName.database=A.database
applicationB.properties
appName.database=B.database
The whole application is composed of "framework" module which provides beans common for each application, like database bean, jdbcTemplate bean, and "application" module which provides property value for placeholder and initializes beans with unique id. So even if I start multiple application at the same time, they will find corresponding bean from the context.
Generally speaking, I hope to do
#Bean(name = "${beanName}")
public ABean getBean() {}
and resolve ${beanName} at application level.
By reading SpringFramwork document, I found the answer: BeanNameGenerator
NameGenerator.class
public class NameGenerator implements BeanNameGenerator{
#Override
public String generateBeanName(BeanDefinition definition,
BeanDefinitionRegistry registry) {
if(definition.getBeanClassName().contains("Toto")) {
return "toto";
}
return return definition.getBeanClassName();
}
}
AppConfiguration.class
#Configuration
#ComponentScan(basePackages = {"com.example.domain"}, nameGenerator = NameGenerator.class)
public class Config {
}
Domain class with #Component
#Component
public class Toto {
private int id;
}
BootApplication with domain bean name : toto
#SpringBootApplication
public class DemoApplication {
public static void main(String[] args) {
ConfigurableApplicationContext ctx = SpringApplication.run(
DemoApplication.class, args);
for (String name : ctx.getBeanNamesForType(Toto.class)) {
System.out.println(name);
}
}
}
If you want to follow that type of approach create multiple configuration classes you annotated with different Spring profiles.
At start up you can pass a parameter on which profile to use and hence what beans to load within the associated profile.
A more efficient way to do it is use the same property naming convention across all application .properties files. Set a parameter placeholder for the file name which resolves to a JVM arg passed at runtime which is loaded by the#PropertySource annotation.
There's no need to have duplicate beans defined for different environments if it's just properties that are changing.
Related
I'm moving my project package from Spring xml file configuration to class annotation configuration.
Im stuck with a bean instantiation failed on a bean defined in a another context xml file.
This is the definition :
<bean id="mglsChecker" class="DefaultMglsAdapter" destroy-method="close">
<constructor-arg value="${mgls.server.address}"/>
<constructor-arg value="${mgls.fname}"/>
<constructor-arg value="${mgls.lcount}"/>
</bean>
the mglsChecker class is defined in an infrastucture package common to the entire "solution".
The issue is that the variables "${}" are not defined so now this class is not instantiated.
I dont understand how it works when my project is xml file configured.
In the original applicationContext.xml I dont see any references to this mglsChecker context file.
Any help where should i look into ? what am i missing ?
thanks,
You can use
#Configuration
class YourConfig {
// you usually don't need to explicitly give the bean name
// if you don't, Spring gives it the config's method name
#Bean(name = "mglsChecker", destroyMethod = "close")
MglsAdapter mglsChecker(#Value("${mgls.server.address}") String address,
#Value("${mgls.fname}") String fname,
#Value("${mgls.lcount}") long lcount) {
return new DefaultMglsAdapter(address, fname, lcount);
}
}
Personally, I prefer creating #Component classes, but for that you need to be able to edit the DefaultMglsAdapter class.
#Component
class DefaultMglsAdapter implements MglsAdapter {
// fields to hold the configs
DefaultMglsAdapter(#Value("${mgls.server.address}") String address,
#Value("${mgls.fname}") String fname,
#Value("${mgls.lcount}") long lcount) {
// set fields
}
#PreDestroy
void close() {
// cleanup
}
}
EDIT: incorporated Andreas' correction :)
Load the properties in the java file via
#Configuration
#PropertySource("classpath:foo.properties")
public class DefaultMglsAdapter{
//...
}
Inject the properties via
#Value( "${mgls.server.address}" )
private String serverAddress;
The variables which are mentioned with "${}" syntax are key/place-holders of properties.
Please search or find such key from *.properties or *.config or *.xml or any such custom properties files. If you find any such properties file then specify classpath or location of that file where you want to configure it as given below:
By XML:
<context:property-placeholder location="classpath:path/to/PropertiesFile"/>
By Annotation:
#Configuration
#PropertySource("classpath:path/to/PropertiesFile")
#Value("${Property}")
Thanks and Regards.
I am trying to set up a test applicationContext file for running integration tests on a project that I am working on.
I have two classes that have fields that are marked as #Resource. One class I can change and one I cannot as it is imported from a different project that I don't have any permissions to change. I cannot get my configuration file to set these #Resouces fields without giving me an org.springframework.beans.factory.NoUniqueBeanDefinitionException.
Simple example:
appconfig.xml file
...Typical spring setup...
<bean id="baseUrl" class="java.lang.String">
<constructor-arg value="myURL"/>
</bean>
<bean id="supportedLang" class = "java.lang.String">
<constructor-arg value="en"/>
</bean>
Class that uses baseURL, (I have control to change, simplified Version)
#Service("myService")
public class MyService implements AnotherService{
#Resource
private String baseUrl;
public String getBaseUrl(){return baseUrl;}
public void setBaseURL(String baseURL){this.baseUrl = baseUrl;}
}
Class that uses supportedLang (I don't have access to change this class simplified version)
#Service
public class LangSupportImpl implements InitializaingBean, LangSupport{
#Resource(name= "supportedLang")
private String twoLetterSupportedLang;
public getTwoLetterSupportedLang(){return this.twoLetterSupportedLang;}
}
If I don't set up the beans in the application config file I get a no bean defined error instead.
Any help would be greatly appreciated.
Try to use #Resource(name = "baseUrl") in your MyService class. This will tell Spring which exact bean to take and will resolve ambiguity.
Another option is to change XML configuration and add primary="true" to declaration of baseUrl bean
We have a Spring managed application that uses another jar as a dependency, which contains a Spring managed service class, that need to use some value injected from property file
The main application with the Spring context setup
public static void main(String[] args) {
GenericXmlApplicationContext appContext = new GenericXmlApplicationContext("applicationContext.xml");
SomeClass someClass = (SomeClass) appContext.getBean("someClass");
someClass.someMethod();
...
The class which calls the service from the dependent jar
public class SomeClass {
private ServiceFromTheOtherJar serviceFromTheOtherJar;
public SomeClass(ServiceFromTheOtherJar serviceFromTheOtherJar) {
this.serviceFromTheOtherJar = serviceFromTheOtherJar;
}
public void someMethod() {
serviceFromTheOtherJar.call();
...
The applicationContext.xml of the main app
<bean name="serviceFromTheOtherJar" class="com...ServiceFromTheOtherJar"/>
<bean name="someClass" class="com...SomeClass">
<constructor-arg ref="serviceFromTheOtherJar"/>
</bean>
The service class in the dependent jar
public class ServiceFromTheOtherJar {
private String someFieldWeWantToFillFromPropertyFile;
public void setSomeFieldWeWantToFillFromPropertyFile(String someFieldWeWantToFillFromPropertyFile) {
this.someFieldWeWantToFillFromPropertyFile = someFieldWeWantToFillFromPropertyFile;
}
public void call() {
//we would like to use the filled someFieldWeWantToFillFromPropertyFile here
...
And of course we have an application.properties file in the dependent jar, that contains the property value that we would like to inject into someFieldWeWantToFillFromPropertyFile
Now we can add the dependent jar as a dependency to the main app; when the main app is being executed then its Spring context is getting set up all right, and ServiceFromTheOtherJar.call() method gets called as expected; however someFieldWeWantToFillFromPropertyFile does not get filled from the property file whatever we tried so far (e.g. #PropertySource({"application.properties"}), Environment.getProperty(...) etc.)
Restrictions
We have Spring 3 version in both jars, and that has to remain so due to the deployment environment; so Spring 4 solutions are out of question
As you see above, the main app currently uses GenericXmlApplicationContext and changing that seem to indicate a significant rewriting of the application. Therefore e.g. it seemed to be not possible to use #Service annotation on ServiceFromTheOtherJar because it caused BeanCreationException during the execution and context setup
To read values from property file you have to add the following bean to applicationContext.xml.
<bean class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
<property name="locations" value="classpath:application.properties" />
</bean>
Assuming the application.properties-file contains a definition like this
myValue=Hello World
the definition of the service-bean should by extended like this
<bean name="serviceFromTheOtherJar" class="com...ServiceFromTheOtherJar">
<property name="someFieldWeWantToFillFromPropertyFile" value="${myValue}" />
</bean>
Now Spring will look for the application.properties-file in classpath and set the attribute of the service bean according to myValue.
I'm developing a huge application with thousands of Spring beans registered using annotations and wired with each others with #Autowired
The application will be released as a "core" application and our customers should be able to customize it adding or overwriting beans.
If they hace to modify a bean the regular way would be by extending the class and making the Spring context to register the customized class instead of the "core" one, but doing that Spring throws an error because it finds two implementation for the same interface.
How can I achieve that?
How can our customers "de-register" the core class and regster the customizaed one?
Thanks a lot!
You can use a Qualifier. They identify beans by name, not by type.
You also can set the field to a list of beans.
#Autowire
private Foobar[] customizedAndCore;
The simplest way of handling that is by using the #Primary annotation. Spring will inject the primary instead of failing with the duplicate bean exception.
Here a basic draft to use #Qualifier plus #Autowired annotation
Here is the content of Student.java file:
public class Student {
private Integer age;
private String name;
public void setAge(Integer age) {
this.age = age;
}
public Integer getAge() {
return age;
}
public void setName(String name) {
this.name = name;
}
public String getName() {
return name;
}
}
Here is the content of Profile.java file:
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
public class Profile {
#Autowired
#Qualifier("student1")
//#Qualifier("student2")
private Student student;
public Profile(){
System.out.println("Inside Profile constructor." );
}
public void printAge() {
System.out.println("Age : " + student.getAge() );
}
public void printName() {
System.out.println("Name : " + student.getName() );
}
}
Following is the content of the MainApp.java file:
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class MainApp {
public static void main(String[] args) {
ApplicationContext context =
new ClassPathXmlApplicationContext("Beans.xml");
Profile profile = (Profile) context.getBean("profile");
profile.printAge();
profile.printName();
}
}
Consider the example of following configuration file Beans.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/>
<!-- Definition for profile bean -->
<bean id="profile" class="com.Profile">
</bean>
<!-- Definition for student1 bean -->
<bean id="student1" class="com.Student">
<property name="name" value="Zara" />
<property name="age" value="11"/>
</bean>
<!-- Definition for student2 bean -->
<bean id="student2" class="com.Student">
<property name="name" value="Nuha" />
<property name="age" value="2"/>
</bean>
</beans>
The way I see it you have 3 general approaches...
Create a child context and in the child context override beans in the parent context (they will replace them)
Use a profile
Use a customisation of the annotation when injecting the bean
The first option means that customers when they wish to replace the "core" beans with their own ones simply need to create a new factory which is a child of the core factory. As long as the bean in the child has the same name as the on in core all is well.
The second one means the default bean will be in the "default" (no profile) and then the replacement will be in a specific profile. These replacement beans will then override the beans in no profile when that profile is active. Replacement only happens against bean name not against type so when using this approach you have to ensure the new bean has the same name as the original and the injection annotation specifies the name of the bean ala
#Inject
#Named("dataSource")
private DataSource storageRepository;
The third option requires the following to appear in the annotation when using a bean
#Resource(name = "${dataSource}")
private DataSource dataSource;
Then when using this you will need a parameter called dataSource and needs to be set to the specific bean name you want to inject into that location.
e.g.
dataSource=enterpriseDataSource
then the bean named enterpriseDataSource will be injected into that location.
The way I see it approach 1 is arguably the closest fit to what you're looking for. It sounds like you have a "core" factory that you supply that customers depend on so they don't really have ownership of your source code. AFAIK approach 1 is also the only one that will allow autowire by type to work.
Approach 2 is a better fit for when you want to run in different modes... i.e. in dev, test or production mode. The reason for this is you can only override beans that are not in a profile. You can't override a bean already in a profile with a bean in another profile with this approach.
Approach 3 is in fact what I tend to use the most because it does not require profiles or factory hierarchy and allows swapping in of a different bean simply by changing a parameter's value. I wish however I did not have to keep specifying the bean name however. Something else that is possible - and something I use a lot - is swapping in a whole new config file via activation of a different profile.
I am having difficulty understanding why something in Spring Java Config using #Autowired does not work.
First, I am trying to move all my #Autowired annotations in the Java Config classes. This has the effect of making my "POJOs" back into real POJOs. I can then not only test them easily outside of a Spring context, but can also use mock objects easily and readily.
So I first tried this:
#Configuration
public class Module3ConfigClass {
#Autowired
private Module1Bean1 module1Bean1;
#Autowired
private Module2Bean1 module2Bean1;
#Bean
public Module3Bean1 module3Bean1() {
return new Module3Bean1(module1Bean1, module2Bean1);
}
}
However, when the Module3Bean1 constructor is invoked, both passed in Beans are null. If you didn't follow my made up naming convention above, both of those beans would be created by a separate Java Config configuration file. Also note that everything is wired up correctly - I know this because everything works perfectly when the #Autowired tags are on the corresponding private member fields inside of Module3Bean1.
FWIW, I tried adding an #DependsOn annotation to module3Bean1() method, but had the same results. I guess I just would really like to understand this behavior, is it correct (I suspect it is, but why)?
Finally, I found an acceptable workaround shown here:
#Configuration
public class Module3ConfigClass {
#Bean
#Autowired
public Module3Bean1 module3Bean1(Module1Bean1 module1Bean1, Module2Bean1 module2Bean1) {
return new Module3Bean1(module1Bean1, module2Bean1);
}
}
This seems fine to me, but if anyone would care to comment on it, that would be welcome as well.
I think you came across same problem I just had. In my case problem was invalid xml configuration. In my module B I had config like :
<beans>
<context:component-scan base-package="com.moduleB"/>
<import resource="classpath:applicationContext-moduleA.xml"/>
</beans>
In moduleA context I placed "context:annotation-config" annotation.
When I change import/context order to :
<beans>
<import resource="classpath:applicationContext-moduleA.xml"/>
<context:component-scan base-package="com.moduleB"/>
</beans>
Autowiring for configuration class properties started to work.
We had the same issue and came to the conclusion that the error arose because we had a circular dependency where a BeanPostProcessor was involved.
A PropertyPlaceholderConfigurer (a BeanPostProcessor) has been configured to set its propertiesArray property with the help of another bean:
<bean id="globalPropertyPlaceholderConfigurer"
class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer"
lazy-init="false" depends-on="javaLoggingConfigurer">
<property name="locations">
<list>
<value>classpath:config/host/${env.instance}.properties</value>
<value>WEB-INF/config/host/${env.instance}.properties</value>
</list>
</property>
<property name="ignoreResourceNotFound" value="true" />
<property name="propertiesArray" value="#{springPropertyFinder.findProperties()}" />
</bean>
The used springPropertyFinder bean to set the propertiesArray is not a BeanPostProcessor but a "normal" bean that gathers all Properties instances with:
public Properties[] findProperties() {
Map<String, Properties> propertiesMap = applicationContext.getBeansOfType(Properties.class);
for (String title : propertiesMap.keySet()) {
PropertiesLoggerUtil.logPropertiesContent(logger, "Springcontext Properties ("+title+")", propertiesMap.get(title));
}
return propertiesMap.values().toArray(new Properties[propertiesMap.size()]);
}
The #Configuration class contained a bean of type Properties
So our assumption is that the #Configuration class has been created without being processed by the ConfigurationClassPostProcessor (also a BeanPostProcessor), because the PropertyPlaceholderConfigurer depends on the springPropertyFinder, which depends on the properties bean in the #Configuration class. The order of the BeanPostProcessors is probably not setup right under these circumstances.
This described setup worked in XML, but not with Java config.