I'm working on setting up a utility that lets us load an annotation-based configuration that overrides an XML configuration (for testing). I have tried a number of different setups, but this is the only one that I've gotten to work:
GenericApplicationContext firstCtx = new GenericApplicationContext();
XmlBeanDefinitionReader xmlReader = new XmlBeanDefinitionReader(firstCtx );
xmlReader.loadBeanDefinitions("applicationContext.xml");
GenericApplicationContext ctx = new GenericApplicationContext();
AnnotatedBeanDefinitionReader annotatedReader = new AnnotatedBeanDefinitionReader(ctx);
annotatedReader.register(SomeConfigClass.class);
ctx.refresh();
for (String currBeanName : firstCtx.getBeanDefinitionNames())
{
if (!ctx.containsBeanDefinition(currBeanName))
{
ctx.registerBeanDefinition(currBeanName, firstCtx.getBeanDefinition(currBeanName));
}
}
While this technically does work, it seems like a really cumbersome way to do this. Is there a better way to load an annotation-based configuration over an XML-based configuration?
Thanks!
I think a simpler way is to simply declare SomeConfigClass as a bean within your application context and the configured beans in SomeConfigClass will be wired in.
<bean class="..SomeConfigClass"/>
Or <context:component-scan base-package="package of SomeConfigClass"/>
Or the other way round, in SomeClassClass, do #ImportResource("applicationContext.xml")
Related
I want to create multiple application contexts in my Tomcat application.
Some of these application contexts have the same package and class names, but they all refer to different jars.
For example:
application0 use service.jar, model.jar
application1 use service-a.jar, model-a.jar
application2 use service-b.jar, model-b.jar
application0 context is OK because is in orign project.
I reference some web page to custom application1, I use my custom classloader to start applicationContext.
File file0 = new File("D://git/project1/service-a.jar");
File file1 = new File("D://git/project1/modele-a.jar");
// convert the file to URL format
URL url0 = file0.toURI().toURL();
URL url1 = file1.toURI().toURL();
List<URL> urls = new LinkedList<>();
List<File> libs = listFilesForFolder(new File("D://protal//apache-tomcat-8.0.39//lib"));
for(File lib : libs) {
urls.add(lib.toURI().toURL());
}
urls.add(url1);
urls.add(url0);
final URLClassLoader customClassLoader = new URLClassLoader(urls.toArray(new URL[urls.size()]));
ClassPathXmlApplicationContext context1 = new ClassPathXmlApplicationContext("applicationContext.xml") {
protected void initBeanDefinitionReader(XmlBeanDefinitionReader reader)
{
super.initBeanDefinitionReader(reader);
reader.setValidationMode(XmlBeanDefinitionReader.VALIDATION_NONE);
reader.setBeanClassLoader(customClassLoader);
}
};
allApplicationContexts.add(context1);
The Spring contexts start OK, but they fail to create the component-scan bean, and PropertyPlaceholderConfigurer isn't working. Everything else seems correct.
I sure my config is correct because it works without the custom classloader. Libs contains all spring lib.
Is it possible to get this working with multiple Spring contexts?
I need to set some specific Oracle JDBC connection properties in order to speed up batch INSERTs (defaultBatchValue) and mass SELECTs (defaultRowPrefetch).
I got suggestions how to achieve this with DBCP (Thanks to M. Deinum) but I would like to:
keep the default Tomcat jdbc connection pool
keep application.yml for configuration
I was thinking about a feature request to support spring.datasource.custom_connection_properties or similar in the future and because of this tried to pretent this was already possible. I did this by passing the relevant information while creating the DataSource and manipulated the creation of the DataSource like this:
#Bean
public DataSource dataSource() {
DataSource ds = null;
try {
Field props = DataSourceBuilder.class.getDeclaredField("properties");
props.setAccessible(true);
DataSourceBuilder builder = DataSourceBuilder.create();
Map<String, String> properties = (Map<String, String>) props.get(builder);
properties.put("defaultRowPrefetch", "1000");
properties.put("defaultBatchValue", "1000");
ds = builder.url( "jdbc:oracle:thin:#xyz:1521:abc" ).username( "ihave" ).password( "wonttell" ).build();
properties = (Map<String, String>) props.get(builder);
log.debug("properties after: {}", properties);
} ... leaving out the catches ...
}
log.debug("We are using this datasource: {}", ds);
return ds;
}
In the logs I can see that I am creating the correct DataSource:
2016-01-18 14:40:32.924 DEBUG 31204 --- [ main] d.a.e.a.c.config.DatabaseConfiguration : We are using this datasource: org.apache.tomcat.jdbc.pool.DataSource#19f040ba{ConnectionPool[defaultAutoCommit=null; ...
2016-01-18 14:40:32.919 DEBUG 31204 --- [ main] d.a.e.a.c.config.DatabaseConfiguration : properties after: {password=wonttell, driverClassName=oracle.jdbc.OracleDriver, defaultRowPrefetch=1000, defaultBatchValue=1000, url=jdbc:oracle:thin:#xyz:1521:abc, username=ihave}
The actuator shows me that my code replaced the datasource:
But the settings are not activated, which I can see while profiling the application. The defaultRowPrefetch is still at 10 which causes my SELECTs to be much slower than they would be if 1000 was activated.
Setting the pools connectionProperties should work. Those will be passed to the JDBC driver. Add this to application.properties:
spring.datasource.connectionProperties: defaultRowPrefetch=1000;defaultBatchValue=1000
Edit (some background information):
Note also that you can configure any of the DataSource implementation
specific properties via spring.datasource.*: refer to the
documentation of the connection pool implementation you are using for
more details.
source: spring-boot documentation
As Spring Boot is EOL for a long time I switched to Spring Boot 2.1 with its new default connection pool Hikari. Here the solution is even more simply and can be done in the application.properties or (like shown here) application.yml:
spring:
datasource:
hikari:
data-source-properties:
defaultRowPrefetch: 1000
(In a real-life config there would be several other configuration items but as they are not of interest for the question asked I simply left them out in my example)
Some additional information to complement the answer by #Cyril. If you want to upvote use his answer, not mine.
I was a little bit puzzled how easy it is to set additional connection properties that in the end get used while creating the database connection. So I did a little bit of research.
spring.datasource.connectionProperties is not mentioned in the reference. I created an issue because of this.
If I had used the Spring Boot YML editor, I would have seen which properties are supported. Here is what STS suggests when you create an application.yml and hit Ctrl+Space:
The dash does not matter because of relaxed binding but if you interpret it literally the propertys name is spring.datasource.connection-properties.
The correct setup in application.yml looks like this:
spring:
datasource:
connection-properties: defaultBatchValue=1000;defaultRowPrefetch=1000
...
This gets honored which is proven by my perf4j measurements of mass SELECTs.
Before:
2016-01-19 08:58:32.604 INFO 15108 --- [ main]
org.perf4j.TimingLogger : start[1453190311227]
time[1377] tag[get elements]
After:
2016-01-19 08:09:18.214 INFO 9152 --- [ main]
org.perf4j.TimingLogger : start[1453187358066]
time[147] tag[get elements]
The time taken to complete the SQL statement drops from 1377ms to 147, which is an enormous gain in performance.
After digging around in the Tomcat code for a bit, I found that the dataSource.getPoolProperties().getDbProperties() is the Properties object that will actually get used to generate connections for the pool.
If you use the BeanPostProcessor approach mentioned by #m-deinum, but instead use it to populate the dbProperties like so, you should be able to add the properties in a way that makes them stick and get passed to the Oracle driver.
import java.util.Properties;
import org.apache.tomcat.jdbc.pool.DataSource;
import org.apache.tomcat.jdbc.pool.PoolConfiguration;
#Component
public class OracleConfigurer implements BeanPostProcessor {
#Override
public Object postProcessBeforeInitialization(Object bean, String name) throws BeansException {
if (bean instanceof DataSource) {
DataSource dataSource = (DataSource)bean;
PoolConfiguration configuration = dataSource.getPoolProperties();
Properties properties = configuration.getDbProperties();
if (null == properties) properties = new Properties();
properties.put("defaultRowPrefetch", 1000);
properties.put("defaultBatchValue", 1000);
configuration.setDbProperties(properties);
}
return bean;
}
#Override
public Object postProcessAfterInitialization(Object bean, String name) throws BeansException {
return bean;
}
}
I have a Spring Batch service containg ItemWriter to write the data to the CSV.
I have used the example give by Spring Batch guide. https://spring.io/guides/gs/batch-processing/
I tried to modify the ItemWriter to create the CSV again.
The Problems which I am facing are -
It is not creating the CSV file if it is not present.
If I made it available before hand it is not writing data to it.
#Bean
public ItemWriter<Person> writer(DataSource dataSource) {
FlatFileitemWriter<Person> csvWriter = new FlatFileItemWriter<Person>();
csvWriter.setResource(new ClassPathResource("csv/new-data.csv"));
csvWriter.setShouldDeleteIfExists(true);
DelimitedLineAggregator<Person> lineAggregator = new DelimitedLineAggregator<Person>();
lineAggregator.setDelimiter(",");
BeanWrapperFieldExtractor<Person> fieldExtractor = new BeanWrapperFieldExtractor<Person>();
String[] names = {"firstName", "lastName"};
fieldExtractor.setNames(names);
lineAggregator.setFieldExtractor(fieldExtractor);
csvWriter.setLineAggregator(lineAggregator);
return csvWriter;
}
I have gone through various links but they show the example with XML based configuration. How to do it completely in JAVA ?
You are using a ClassPathResource to write. I'm not sure, but I don't think you can write to a ClassPathResource. Try using a normal FileSystemResource and try again.
Moreover, how do you inject the writer? are you sure that it really is instantiated as spring bean?
Why do you have DataSource as a parameter since you don't need a datasource to instantiate a FlatFileItemWriter.
What I am trying to do is, to have a test to startup the whole application to see if there's any error. But I want to use the applicationContext.xml from the /src/resource folder and not form test/resource. How can I do that in JUnit?
My application is big and a lot of people share the same codebase. So, I just wanted to have a quick test to see if the checkin can start up the application.
This is my simple code but it looks like it's missing some of the autowire stuff, that's why I want to use the xml files from /src/resource, so I don't have to maintain two locations.
My application is plain Spring MVC 3.0
#Test(enabled = false)
public void shouldStartupTheApp() throws Exception {
Server server = new Server();
SelectChannelConnector connector = new SelectChannelConnector();
connector.setPort(9999);
server.setConnectors(new Connector[] {connector});
Context context = new Context(server, "/", Context.SESSIONS);
DispatcherServlet dispatcherServlet = new DispatcherServlet();
dispatcherServlet.setContextConfigLocation("classpath:/test-applicationContext.xml");
ServletHolder servletHolder = new ServletHolder(dispatcherServlet);
context.addServlet(servletHolder, "/*");
server.start();
}
You can import your src/resources/filename.xml in other xml-file using
<import resource="classpath:/filename.xml" />
I'm writing a jar intended to be used with Spring and Ehcache. Spring requires that there be a cache defined for each element, so I was planning to have an Ehcache defined for the jar, preferably as a resource in the jar that could be imported into the primary Ehcache configuration for the app. However, my reading of the example Ehcache config file and my Google searches have not turned up any way to import a sub Ehcache config file.
Is there a way to import a sub Ehcache config file, or is there some other way to solve this problem?
What I did to do something similar (replace some placeholders in my Ehcache xml file - a import statement is more or less a placeholder if you will) is to extend (more or less copy to be honest) from Springs EhCacheManagerFactoryBean and create the final Ehcache xml config file on the fly.
For creating the CacheManager instance in afterPropertiesSet() you just hand over a InputStream which points to your config.
#Override
public void afterPropertiesSet() throws IOException, CacheException {
if (this.configLocation != null) {
InputStreamSource finalConfig = new YourResourceWrapper(this.configLocation); // put your custom logic here
InputStream is = finalConfig.getInputStream();
try {
this.cacheManager = (this.shared ? CacheManager.create(is) : new CacheManager(is));
} finally {
IOUtils.closeQuietly(is);
}
} else {
// ...
}
// ...
}
For my filtering stuff I internally used a ByteArrayResource to keep the final config.
data = IOUtils.toString(source.getInputStream()); // get the original config (configLocation) as string
// do your string manipulation here
Resource finalConfigResource = new ByteArrayResource(data.getBytes());
For "real" templating one could also think of using a real template engine like FreeMarker (which Spring has support for) to do more fancy stuff.