I am migrating an application from an XmlWebApplicationContext to a pure java configuration solution using AnnotationConfigApplicationContext. I am having a problem reusing existing xml configuration files via #ImportResource. We are using spring 3.2.11.
When I use the xml based context, beans defined in the xml files that are java configuration (#Configuration) are automatically picked up by the context and any beans they define are visible. However, when imported through #ImportResource, #Beans in the configuration objects are not created.
Here is a unit test that illustrates my problem:
XmlConfigTest.java
#Test
public void testAnnotationContext()
{
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext();
ctx.register(XmlFromJava.class);
ctx.refresh();
assertEquals("xml value", ctx.getBean("xmlBean", String.class));
assertEquals("nested value", ctx.getBean("nestedBean", String.class));
}
#Test
public void testXmlContext()
{
ClassPathXmlApplicationContext ctx = new ClassPathXmlApplicationContext("classpath:test.xml");
ctx.refresh();
assertEquals("xml value", ctx.getBean("xmlBean", String.class));
// fails here
assertEquals("nested value", ctx.getBean("nestedBean", String.class));
}
#Configuration
#ImportResource("classpath:test.xml")
public static class XmlFromJava { }
NestedConfig.java
#Configuration
public class NestedConfig
{
#Bean
public String nestedBean()
{
return "nested value";
}
}
test.xml
<context:annotation-config/>
<bean class="NestedConfig"/>
<bean name="xmlBean" class="java.lang.String">
<constructor-arg value="xml value"/>
</bean>
I would expect the bean 'nestedBean' to exist from the NestedConfig class. testAnnotationContext() fails to load the 'nestedBean' but testXmlContext() works.
Related
I have a project which uses an old spring.jar (1.2.6),from this project, I am expected to call a newer version (spring version 5.0.7) spring boot project's method. Below is the way I am creating my bean in old version project.
I am getting NullPointer exception while creating the Autowired bean.
Create bean from XML:spring
test-context.xml:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN" "qvc-spring-beans.dtd">
<beans>
<bean name="testPci" class="com.test.client.TestPci">
</bean>
</beans>
sampleParent-context.xml:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE beans SYSTEM "spring-beans.dtd">
<beans>
<import resource="classpath:/com/test/test-context.xml" />
<bean id="classA" class="com.test.A" >
<property name="testPci">
<ref bean="testPci"/>
</property>
</bean>
</beans>
Java code old spring project:
package com.test;
public class A{
private TestPci testPci;
private ApplicationContext ctx;
public TestPci getTestService() {
if (!StringUtils.isValid(ctx)) {
ctx = new ClassPathXmlApplicationContext("./com/test/test-context.xml");
}
if (!StringUtils.isValid(this.testPci)) {
if (StringUtils.isValid(ctx)) {
testPci = (TestPci) ctx.getBean("testPci");
TestPci testPci = (TestPci) ctx
.getBean("testPci");
this.setSecureTestService(testPci);
}
}
return this.getSecureTestService();
}
public TestPci getSecureTestService() {
return testPci;
}
public void setSecureTestService(TestPci testPci) {
this.testPci = testPci;
}
public void methodA(){
//Calling newer code form old spring code:
testPci.testing("1", "2", "3");
}
}
Calling "TestPci" class as above, but when trying to call using the above, it actually calls the "TestPci"."testing" method. But the object autowired as "testWebClientService" is returning as null. I would like to get the object created instead it returns null.
New spring version class:
#Service
#EnableConfigurationProperties(TestWebClientProperties.class)
#Configurable
public class TestPci{
#Autowired
private TestWebClientService testWebClientService;
public Map<String, String> testing(String a, String b, String c) throws Exception {
Map<String, String> map = testWebClientService.find(a, b, c);
System.out.println("**=="+map.get(0));
return map;
}
}
Adding junit which is used to call the TestPci class from newer version of spring:
#RunWith(SpringJUnit4ClassRunner.class)
#EnableConfigurationProperties(TestWebClientProperties.class)
#SpringBootTest(classes = { TestWebClientService.class, TestPci.class }, webEnvironment = WebEnvironment.NONE)
public class TestJunit {
#MockBean(name="restTemplate")
public RestTemplate restTemplate;
#Autowired
private TestPci testPci;
#Test
public void ff() throws Exception {
testPci.testing("1","1","1");
}
}
I'm used to Spring with xml configuration. With xml, I can have one main implementation and one test implementation for a class, so that the test implementation will be used for JUnit tests, how can I do this with annotations ? Cause it looks like the implementation is already chosen in the "#qualifier" annotation ?
Let's take an example :
<bean id="myService" class="example.Service" />
<bean id="myHibernateDao" class="example.HibernateDao" />
<bean id="myStubDao" class="example.StubDao" />
In xml config, I can have this in src/main/resources :
<bean id="myService" class="example.Service">
<ref="myHibernateDao" />
</bean>
And this in src/test/resources :
<bean id="myService" class="example.Service">
<ref="myStubDao" />
</bean>
How can I do this with annotations, if I have already declared #Qualifier("myHibernateDao") into my service class ?
As explained in the comment above and in spring blog, you can do this via #Profile annotation.
Please find a sample config from the example below,
DataConfig.java
interface DataConfig {
DataSource dataSource();
}
StandaloneDataConfig .java
#Configuration
#Profile("dev")
public class StandaloneDataConfig implements DataConfig {
#Bean
public DataSource dataSource() {
return new EmbeddedDatabaseBuilder().setType(EmbeddedDatabaseType.HSQL)
.addScript("classpath:com/bank/config/sql/schema.sql")
.addScript("classpath:com/bank/config/sql/test-data.sql")
.build();
}
}
JndiDataConfig.java
#Configuration
#Profile("production")
public class JndiDataConfig implements DataConfig {
#Bean
public DataSource dataSource() {
try {
Context ctx = new InitialContext();
return (DataSource) ctx.lookup("java:comp/env/jdbc/datasource");
} catch (NamingException ex) {
throw new RuntimeException(ex);
}
}
}
TransferServiceConfig.java
#Configuration
public class TransferServiceConfig {
#Autowired
DataConfig dataConfig;
#Bean
public TransferService transferService() {
return new DefaultTransferService(accountRepository(), feePolicy());
}
#Bean
public AccountRepository accountRepository() {
return new JdbcAccountRepository(dataConfig.dataSource());
}
#Bean
public FeePolicy feePolicy() {
return new ZeroFeePolicy();
}
}
Setting bean profile to an application context
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext();
ctx.getEnvironment().setDefaultProfiles("dev");
ctx.register(TransferServiceConfig.class, StandaloneDataConfig.class,
JndiDataConfig.class);
ctx.refresh();
Since the bean profile has been set to Dev, The TransferServiceConfigwill be injected with StandaloneDataConfig
You can basically do the same thing with #Configuration classes.
Production Setup
Let's assume you have many different DAO implementations that might or might not implement a common interface (it doesn't matter). Let's further assume that MyServiceDao is the concrete implementation that your Service class needs.
Write the following configuration classes:
#Configuration
public class MyServiceConfiguration {
#Bean
public Service myService(MyServiceDao dao) {
return new Service(dao);
}
}
-
#Configuration
public class MyProductionServiceDaoConfiguration {
#Bean
public MyServiceDao myServiceDao() {
return new MyServiceDao();
}
}
Methods in #Configuration classes that are annotated with #Bean are eligible for Spring's auto-wiring. In the MyServiceConfiguration above, Spring will use the type of the method parameter to find a matching bean. If you include MyProductionServiceDaoConfiguration when creating the Spring context, this will be the instance of MyServiceDao that myServiceDao() created.
Test Setup
In your tests, you want to replace MyServiceDao with a stub. The stub needs to extend MyServiceDao so that Spring can find the right bean based on types. Let's call the stub MyServiceDaoStub. Whether you create it using a library like Mockito (which can also create stubs, not just mocks) or actually write an extension of MyServiceDao is up to you.
Instead of including MyProductionServiceDaoConfiguration in your Spring configuration, use the following class:
#Configuration
public class MyTestServiceDaoConfiguration {
#Bean
public MyServiceDao myServiceDao() {
return new MyServiceDaoStub();
}
}
In your test use #ContextConfiguration to load the test setup:
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration(classes = { MyServiceConfiguration.class, MyTestServiceDaoConfiguration.classs })
public class MyServiceTest {
// your tests
}
Using #Autowired
Spring will also process #Autowired annotations in objects returned from #Bean annotated methods. If your Service looks like this:
public class Service {
#Autowired
private MyServiceDao dao;
// more code
}
you can change the myService() method to:
#Bean
public Service myService() {
return new Service();
}
I'm using Spring 4.1.6.
I have something like the following:
foo.properties:
valueX=a
valueY=b
Spring bean:
<context:property-placeholder location="classpath:foo.properties" ignore-unresolvable="false" ignore-resource-not-found="false" />
<bean id="foo" class="com.foo.bar.MyClass" >
<property name="someValue" value="${valueX}" />
</bean>
I have a non-Spring class which also needs to use a value from foo.properties.
Non Spring Class:
public void doSomething() {
String valueY = System.getProperty("valueY");
}
When Spring loads foo.properties, is there a way to populate all the properties into System properties so that I can get "valueY" using System.getProperty("valueY").
I don't want to load foo.properties again in my non-Spring class.
The context:property-placeholder will create a PropertySourcesPlaceholderConfigurer config bean for you. You cannot access the properties from this bean programatically as stated here.
What you can do is to load the properties into a separate spring bean as given below.
#Bean(name = "mapper")
public PropertiesFactoryBean mapper() {
PropertiesFactoryBean bean = new PropertiesFactoryBean();
bean.setLocation(new ClassPathResource("application.properties"));
return bean;
}
and then set the system property when the context load is finished using a listener as given below. Got the code from this answer
#Component
public class YourJobClass implements ApplicationListener<ContextRefreshedEvent> {
#Resource(name = "mapper")
private Properties myTranslator;
public void onApplicationEvent(ContextRefreshedEvent contextRefreshedEvent) {
System.setProperties(myTranslator);
}
}
I have a Spring boot application that needs to perform LDAP queries. I'm trying to take the following recommendation from the Spring boot documentation:
"Many Spring configuration examples have been published on the
Internet that use XML configuration. Always try to use the equivalent
Java-base configuration if possible."
In a Spring XML configuration file, I would have used:
<ldap:context-source
url="ldap://localhost:389"
base="cn=Users,dc=test,dc=local"
username="cn=testUser"
password="testPass" />
<ldap:ldap-template id="ldapTemplate" />
<bean id="personRepo" class="com.llpf.ldap.PersonRepoImpl">
<property name="ldapTemplate" ref="ldapTemplate" />
</bean>
How would I configure this using a Java-based configuration? I need to be able to change URL, base, username, and password attributes of ldap:context-source without a code rebuild.
The <ldap:context-source> XML tag produces an LdapContextSource bean and the <ldap:ldap-template> XML tag produces an LdapTemplate bean so that's what you need to do in your Java configuration:
#Configuration
#EnableAutoConfiguration
#EnableConfigurationProperties
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
#Bean
#ConfigurationProperties(prefix="ldap.contextSource")
public LdapContextSource contextSource() {
LdapContextSource contextSource = new LdapContextSource();
return contextSource;
}
#Bean
public LdapTemplate ldapTemplate(ContextSource contextSource) {
return new LdapTemplate(contextSource);
}
#Bean
public PersonRepoImpl personRepo(LdapTemplate ldapTemplate) {
PersonRepoImpl personRepo = new PersonRepoImpl();
personRepo.setLdapTemplate(ldapTemplate);
return personRepo;
}
}
To allow you to change the configuration without a rebuild of your code, I've used Spring Boot's #ConfigurationProperties. This will look in your application's environment for properties prefixed with ldap.contextSource and then apply them to the LdapContextSource bean by calling the matching setter methods. To apply the configuration in the question, you can use an application.properties file with four properties:
ldap.contextSource.url=ldap://localhost:389
ldap.contextSource.base=cn=Users,dc=test,dc=local
ldap.contextSource.userDn=cn=testUser
ldap.contextSource.password=testPass
I'm working on an application that uses Spring and slf4j. That application uses more ApplicationContext parallelly.
Is there any way to these different ApplicationContexts use different logging properties?
So the first AC could log into "x.txt" while the second to "y.txt".
I wouldn't like to use more properties file. The appropriate way would be to define a Logger Bean in Spring XML configuration file where I could set different output target for the appropriate property.
For example:
<bean id="LoggerBean" class="???">
<property name="target" value="${target}" />
</bean>
Here I could manipulate the target variable from source, which would be very handy.
private static final Logger log = LoggerFactory.getLogger(MyClass.class);
So LoggerFactory.getLogger would use the LoggerBean bean configuration to instantiate a Logger class.
I need a method where each ApplicationContext has an own LoggerFactory object with different properties (like different target output). So I wouldn't have to rewrite the current code.
I use ApplicationContexts configured by the same xml config file. So these ApplicationContexts use
the same classes. Because of that, all Logger are instantiated from LoggerFactory with the same class names they used in.
All Logger are instantiated by LoggerFactory.getLogger(MyClass.class) form, as those classes are same in all ApplicationContext ("MyClass"), I can't define differently named Loggers.
Thanks for any reply.
You can define a Spring-managed bean that will configure the logger. For example, assuming you are using logback to implement the slf4j API, this class will load a specified logging configuration file into logback after Spring sets its properties:
public class LogBackConfigurer implements InitializingBean {
private Resource location;
public void setLocation(Resource location) {
this.location = location;
}
public void afterPropertiesSet() throws Exception {
JoranConfigurator configurator = new JoranConfigurator();
LoggerContext loggerContext = (LoggerContext) LoggerFactory.getILoggerFactory();
configurator.setContext(loggerContext);
configurator.doConfigure(location.getInputStream());
}
}
In each Spring configuration file you want to have a different logging configuration, define a bean like the following with a different logging configuration file location.
<bean class="com.example.log.LogBackConfigurer">
<property name="location" value="classpath:a-logback.xml"/>
</bean>
The class modifies the single application-wide logging context, which is necessary because you want to call the static Logger factory method in your application code. To ensure the logging configuration files don't step on each other, they each have to define differently named loggers.
The final solution was the following:
SLF4j and Logback support MDC which contains key/value pairs on per thread basis. Although the main advantages for our problem is that a child thread automatically inherits key/value pairs of its parent, so if a new Thread is created during initialization of ApplicationContext, that Thread will inherit those pairs from the calling thread. After that you can include these stored values in log message pattern.
So I put a special ApplicationContext identifier in MDC before loading the ApplicationContext. When classes are instantiated with a Logger field, these fields obtain their unique identifier which is included in the log message pattern.
<Pattern>[%X{contextID}] - [%thread] - %date{dd/MM/yyyy HH:mm:ss} %level %msg%n</Pattern>
LoggerSeparator.java
public class LoggerSeparator implements InitializingBean{
private Integer contextID;
public LoggerSeparator() {}
public void setContextID(int id) {
this.contextID = id;
}
#Override
public void afterPropertiesSet() throws Exception {
if ( contextID != null )
MDC.put("contextID", contextID.toString());
}
}
This bean is the first defined Spring Bean in main.xml.
<bean class="com.myproblem.LoggerSeparator">
<property name="contextID" value="${contextID}" />
</bean>
...
That class set the contextID in MD. The contextID comes from source code.
...
Properties props = new Properties();
props.put("contextID", contextID);
PropertyPlaceholderConfigurer conf = new PropertyPlaceholderConfigurer();
conf.setProperties(props);
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext();
context.addBeanFactoryPostProcessor(conf);
context.setConfigLocation("beans/main.xml");
context.refresh();
...
The log messages are logged into one file, but now I can separate them by their unique identifier.
You can use custom FactoryBean to add the logger into the context:
public class Slf4jLoggerFactoryBean implements FactoryBean<Logger> {
private String loggerName;
public Logger getObject() throws Exception {
return LoggerFactory.getLogger(loggerName);
}
public Class<?> getObjectType() {
return Logger.class;
}
public boolean isSingleton() {
return true;
}
public void setLoggerName(String loggerName) {
this.loggerName = loggerName;
}
}
And then the XML will look like:
<bean id="LoggerBean" class="com.example.Slf4jLoggerFactoryBean">
<property name="target" value="${target}" />
</bean>