How do I ensure dependent configurations are initialized with Spring #Configuration annotation? - java

I am trying to use #Configuration annotations to wire up my application but I keep getting a NullPointerException in one of the initializers because the bean it refers to is not yet initialized (I think). I have tried specifying in the web.xml just the 'root' config class and also tried doing a package scan and neither seem to work.
Sorry about the big code dump. I tried to produce a much simpler set of classes to reproduce the issue, but of course, when I did that, everything worked fine. Here are my classes (imports elided):
DataSourceConfig.java:
#Configuration
public class DataSourceConfig {
public DataSourceConfig() {
System.err.println("DataSourceConfig constructed...");
}
#Bean
public DataSource dataSource() {
BasicDataSource bean = new BasicDataSource();
bean.setDriverClassName("com.mysql.jdbc.Driver");
bean.setUrl("jdbc:mysql://localhost:3306/observation");
bean.setUsername("observation");
bean.setPassword("*******");
bean.setInitialSize(1);
bean.setMaxActive(5);
bean.setTestOnBorrow(true);
System.err.println("dataSource bean initialized: " + bean.toString());
return bean;
}
}
HibernateConfig.java
#Configuration
#Import(DataSourceConfig.class)
public class HibernateConfig {
public HibernateConfig() {
System.err.println("HibernateConfig constructing...");
}
#Autowired
private DataSourceConfig dataSourceConfig;
#Bean
protected NamingStrategy namingStrategy() {
return new ImprovedNamingStrategy();
}
private AnnotationSessionFactoryBean sessionFactoryBean = null;
#Bean
#DependsOn("dataSourceConfig")
public AnnotationSessionFactoryBean sessionFactory() {
if (sessionFactoryBean == null) {
sessionFactoryBean = new AnnotationSessionFactoryBean();
NPE Here--> sessionFactoryBean.setDataSource(dataSourceConfig.dataSource());
sessionFactoryBean.setSchemaUpdate(true);
sessionFactoryBean.setNamingStrategy(namingStrategy());
sessionFactoryBean.setPackagesToScan(new String[] {
"com.newco.observations.domain",
"com.newco.observations.domain.*" });
Properties props = new Properties();
props.setProperty("hibernate.default_schema", "observation");
props.setProperty("hibernate.dialect",
"org.hibernate.dialect.MySQLDialect");
props.setProperty("hibernate.show_sql", "true");
sessionFactoryBean.setHibernateProperties(props);
System.err.println("sessionFactory initialized");
}
return sessionFactoryBean;
}
#Bean
#DependsOn("dataSourceConfig")
public JdbcTemplate jdbcTemplate() {
return new JdbcTemplate(dataSourceConfig.dataSource());
}
#Bean
#DependsOn("sessionFactory")
public ResourceTransactionManager txManager() {
HibernateTransactionManager bean = new HibernateTransactionManager();
bean.setSessionFactory((SessionFactory) sessionFactory().getObject());
return bean;
}
#Bean
#DependsOn("sessionFactory")
public HibernateTemplate hibernateTemplate() {
return new HibernateTemplate((SessionFactory) sessionFactory()
.getObject());
}
}
DaoConfig.java:
#Configuration
#Import(HibernateConfig.class)
public class DaoConfig {
public DaoConfig()
{
System.err.println("DaoConfig constructing...");
}
private #Autowired HibernateConfig hibernateConfig;
#Bean
#DependsOn("hibernateTemplate")
public PhenomenonGroupDao phenomenonGroupDao()
{
PhenomenonGroupDaoImpl bean = new PhenomenonGroupDaoImpl();
bean.setHibernateTemplate(hibernateConfig.hibernateTemplate());
return bean;
}
#Bean
#DependsOn("hibernateTemplate")
public PhenomenonDao phenomenonDao()
{
PhenomenonDaoImpl bean = new PhenomenonDaoImpl();
bean.setHibernateTemplate(hibernateConfig.hibernateTemplate());
return bean;
}
#Bean
#DependsOn("hibernateTemplate")
public DiscretePhenomenonDao discretePhenomenonDao()
{
DiscretePhenomenonDaoImpl bean = new DiscretePhenomenonDaoImpl();
bean.setHibernateTemplate(hibernateConfig.hibernateTemplate());
return bean;
}
}
You can see from the System.err.println's and the #DependsOn annotations a kind of flailing about that I'm doing.
I can provide the full log if it's useful, but here is what I think are the relevant lines (with a little formatting to make it more readable (maybe)):
208 [Thread-0] INFO org.springframework.context.annotation.ConfigurationClassEnhancer
Successfully enhanced com.bjk.observation.server.config.DaoConfig; enhanced class name is: com.bjk.observation.server.config.DaoConfig$$EnhancerByCGLIB$$96e1956
229 [Thread-0] INFO org.springframework.beans.factory.support.DefaultListableBeanFactory
Pre-instantiating singletons in org.springframework.beans.factory.support.DefaultListableBeanFactory#185572a: defining beans [org.springframework.context.annotation.internalConfigurationAnnotationProcessor, org.springframework.context.annotation.internalAutowiredAnnotationProcessor, org.springframework.context.annotation.internalRequiredAnnotationProcessor, org.springframework.context.annotation.internalCommonAnnotationProcessor, org.springframework.context.annotation.internalPersistenceAnnotationProcessor, daoConfig,com.bjk.observation.server.config.DataSourceConfig#0, dataSource, com.bjk.observation.server.config.HibernateConfig#0, namingStrategy, sessionFactory, jdbcTemplate, txManager, hibernateTemplate, phenomenonGroupDao, phenomenonDao, discretePhenomenonDao]; root of factory hierarchy DaoConfig constructing...
252 [Thread-0] INFO org.springframework.beans.factory.support.DefaultListableBeanFactory
Destroying singletons in org.springframework.beans.factory.support.DefaultListableBeanFactory#185572a: defining beans [org.springframework.context.annotation.internalConfigurationAnnotationProcessor, org.springframework.context.annotation.internalAutowiredAnnotationProcessor, org.springframework.context.annotation.internalRequiredAnnotationProcessor, org.springframework.context.annotation.internalCommonAnnotationProcessor, org.springframework.context.annotation.internalPersistenceAnnotationProcessor, daoConfig, com.bjk.observation.server.config.DataSourceConfig#0, dataSource, com.bjk.observation.server.config.HibernateConfig#0, namingStrategy, sessionFactory, jdbcTemplate, txManager, hibernateTemplate, phenomenonGroupDao, phenomenonDao, discretePhenomenonDao]; root of factory hierarchy
253 [Thread-0] ERROR org.springframework.web.context.ContextLoader
Context initialization failed org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'daoConfig': Injection of autowired dependencies failed; nested exception is org.springframework.beans.factory.BeanCreationException: Could not autowire field: private com.bjk.observation.server.config.HibernateConfig com.bjk.observation.server.config.DaoConfig.hibernateConfig; nested exception is org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'com.bjk.observation.server.config.HibernateConfig#0': Instantiation of bean failed; nested exception is org.springframework.beans.BeanInstantiationException: Could not instantiate bean class [com.bjk.observation.server.config.HibernateConfig]: Constructor threw exception; nested exception is java.lang.NullPointerException

The problem, I believe is here:
#Autowired
private DataSourceConfig dataSourceConfig;
You're not supposed to explicitly wire yourself with other #Configuration-annotated classes, but rather the beans that they produce. Spring will sort out the plumbing for you.
So replace the above field with the simpler:
#Autowired
private DataSource dataSource;
Spring will fetch the DataSource from DataSourceConfig and transparently inject it into the field.
Similarly, replace
#Autowired
private HibernateConfig hibernateConfig;
with
#Autowired
private HibernateTemplate hibernateTemplate;
You'll notice that the #Configuration style doesn't feel as nice when working with factory beans like AnnotationSessionFactoryBean, since you often have to call getObject() on it yourself. Sometimes, it's more natural to use XML config, and mix it with the java config style.

Related

Unresolveable Circular reference error when configuring multiple datasources in a spring batch

I'm trying to configure two datasources in my spring batch application. One for batch metadata tables, and another for the business tables.
Snippet from my application.properties file:
spring.datasource.url=
spring.datasource.username=
spring.datasource.password=
spring.datasource.driver-class-name=
spring.batchdatasource.url=
spring.batchdatasource.username=
spring.batchdatasource.password=
spring.batchdatasource.driver-class-name=
My batch config file:
#Configuration
public class SpringBatchConfig extends DefaultBatchConfigurer{
#Autowired
private JobBuilderFactory jobs;
#Autowired
private StepBuilderFactory steps;
// #Autowired
// private DataSource dataSource;
#Autowired
private PlatformTransactionManager transactionManager;
#Bean(name = "batchDatasource")
#ConfigurationProperties(prefix="spring.batchdatasource")
public DataSource batchDataSource(){
return DataSourceBuilder.create().build();
}
#Bean(name = "primaryDatasource")
#ConfigurationProperties(prefix="spring.datasource")
#Primary
public DataSource primaryDataSource() {
return DataSourceBuilder.create().build();
}
#Override
public JobRepository createJobRepository() throws Exception {
JobRepositoryFactoryBean factory = new JobRepositoryFactoryBean();
factory.setDataSource(batchDataSource());
// factory.setDataSource(dataSource);
factory.setTransactionManager(transactionManager);
factory.setTablePrefix("schema1"+ ".BATCH_");
factory.afterPropertiesSet();
return factory.getObject();
}
/* Job and step bean definitions here */
My main class is the one annotated with #EnableBatchProcessing
#SpringBootApplication
#EnableBatchProcessing
public class SpringBatchExample1Application {
public static void main(String[] args) {
SpringApplication.run(SampleApplication.class, args);
}
}
I'm getting this Requested bean is currently in creation: Is there an unresolvable circular reference? when trying to configure two datasources. It works fine when using a single datasource by autowiring(refer the commented out lines of code) instead of creating multiple beans.
Following is the exception snippet:
Error creating bean with name 'springBatchConfig': Unsatisfied dependency expressed through method 'setDataSource' parameter 0; nested exception is org.springframework.beans.factory.BeanCurrentlyInCreationException: Error creating bean with name 'batchDatasource': Requested bean is currently in creation: Is there an unresolvable circular reference?
I looked up and found out this occurs when there's a dependency on a bean which is still not created or is being created. I just see it in the createJobRepository method where datasource is being plugged. I still face the error even if I don't have the createJobRepository method.
It seems like the requirement is for the datasource beans to be created before others. I tried using the #Order annotation, but no luck.
EDIT:
I tried the solution from #Mykhailo Skliar's Accepted answer below, and serparated the Datasource beans into a new Configuration class. Though it resolved the initial Unresolveble circular reference issue anymore, it led me to the following error:
Error creating bean with name 'springBatchConfig': Invocation of init method failed; nested exception is org.springframework.batch.core.configuration.BatchConfigurationException: java.lang.IllegalArgumentException: jdbcUrl is required with driverClassName.
Based on this answer, I changed my url property names as follows:
spring.datasource.jdbc-url=
spring.datasource.jdbc-url=
Though it solved the jdbcUrl error, it posed another issue:
Caused by: com.microsoft.sqlserver.jdbc.SQLServerException: Reference to database and/or server name in 'sample-sql-server.schema1.MY_TABLE_NAME' is not supported in this version of SQL Server.
Both my data sources are Azure SQL server instances.
I looked up and found it was not possible to use multiple Azure SQL databases years ago, but based on this answer it should not be the case anymore.
The issue is most probably because of
factory.setDataSource(batchDataSource());
You should use autowired bean here, instead of calling batchDataSource()
I would split SpringBatchConfig in two beans:
#Configuration
public class DataSourceConfig {
#Bean(name = "batchDatasource")
#ConfigurationProperties(prefix="spring.batchdatasource")
public DataSource batchDataSource(){
return DataSourceBuilder.create().build();
}
#Bean(name = "primaryDatasource")
#ConfigurationProperties(prefix="spring.datasource")
#Primary
public DataSource primaryDataSource() {
return DataSourceBuilder.create().build();
}
}
#Configuration
public class SpringBatchConfig extends DefaultBatchConfigurer{
#Autowired
private JobBuilderFactory jobs;
#Autowired
private StepBuilderFactory steps;
#Qualifier("batchDataSource")
#Autowired
private DataSource batchDataSource;
#Autowired
private PlatformTransactionManager transactionManager;
#Override
public JobRepository createJobRepository() throws Exception {
JobRepositoryFactoryBean factory = new JobRepositoryFactoryBean();
factory.setDataSource(batchDataSource);
factory.setTransactionManager(transactionManager);
factory.setTablePrefix("schema1"+ ".BATCH_");
factory.afterPropertiesSet();
return factory.getObject();
}
}

Can't Autowire SessionFactory with Java Configuration

The configuration class name is marked in yellow color and it says Application context not configured for this file
My Config Class :
#Configuration
#EnableTransactionManagement
public class DatabaseConfig {
#Bean
public LocalSessionFactoryBean sessionFactory() {
LocalSessionFactoryBean sessionFactory = new LocalSessionFactoryBean();
sessionFactory.setDataSource(dataSource());
sessionFactory.setPackagesToScan("com.tornikeshelia.model");
sessionFactory.setHibernateProperties(getHibernateProperties());
return sessionFactory;
}
private Properties getHibernateProperties(){
Properties prop = new Properties();
Properties properties = new Properties();
properties.setProperty("hibernate.hbm2ddl.auto", "update");
properties.setProperty("hibernate.dialect", "org.hibernate.dialect.MySQL57InnoDBDialect");
return properties;
}
#Bean(name = "dataSource")
public BasicDataSource dataSource(){
BasicDataSource ds = new BasicDataSource();
ds.setDriverClassName("com.mysql.cj.jdbc.Driver");
ds.setUrl("jdbc:mysql://localhost:3306/test?allowPublicKeyRetrieval=true&useSSL=false&useUnicode=true&characterEncoding=utf-8");
ds.setUsername("username");
//ds.setPassword("");
return ds;
}
#Bean
#Autowired
public HibernateTransactionManager transactionManager(SessionFactory sessionFactory){
HibernateTransactionManager txManager = new HibernateTransactionManager();
txManager.setSessionFactory(sessionFactory);
return txManager;
}
#Bean
public PersistenceExceptionTranslationPostProcessor exceptionTranslation(){
return new PersistenceExceptionTranslationPostProcessor();
}
}
I've commented out ds.setPassword because my test database doesn't have any password protection. Also the hibernate doesn't auto create tables from my Entity class , I think thats because of this configuration file not being read by Spring
When trying to autowire SessionFactor in my DaoImpl class via :
#Resource(name = "sessionFactory")
private SessionFactory session;
The error says Exception encountered during context initialization - cancelling refresh attempt: org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'personDaoImpl': Injection of autowired dependencies failed; nested exception is org.springframework.beans.factory.BeanCreationException: Could not autowire field: private org.hibernate.SessionFactory com.tornikeshelia.dao.PersonDaoImpl.sessionFactory; nested exception is org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type [org.hibernate.SessionFactory] found for dependency: expected at least 1 bean which qualifies as autowire candidate for this dependency. Dependency annotations: {#org.springframework.beans.factory.annotation.Autowired(required=true)}
SOLUTION :
The problem was 1)The location of config package and 2) in the warning message that was on the class name (yellowed out with message - Application context not configured for this file
So if anybody has same problem here's the solution -
1) move your config package inside the parent package where the model package is
2) Step on the class name (click anywhere on the class name) the yellow light bulb will appear, click on it and then click on configure application context, a new tab will appear where you should choose this class and press okay.
Try to edit your config class and update the transationManager() bean :
#Bean
public HibernateTransactionManager transactionManager(){
HibernateTransactionManager txManager = new HibernateTransactionManager();
txManager.setSessionFactory(sessionFactory().getObject());
return txManager;
}
Your error may occur because you try to inject a bean that has not bean created yet.

Adding beans breaks Spring Configuration

EDIT 1:
I'm currently calling this from a Main class like so:
public class Main
{
public static void main(String[] args)
{
ApplicationContext ctx = new AnnotationConfigApplicationContext(SpringAppConfig.class);
DataSource dSource = ctx.getBean(DataSource.class);
System.out.println(dSource.getClass().toString());
if (dSource instanceof Log4jdbcProxyDataSource)
{
Log4jdbcProxyDataSource log4jdbcProxyDataSource = (Log4jdbcProxyDataSource) dSource;
Object lf = log4jdbcProxyDataSource.getLogFormatter();
System.out.println(lf.getClass().toString());
}
System.exit(0);
}
}
Original:
Code follows after explanation:
I have a Spring application with a JavaConfig, call it the primary app, that imports another Spring JavaConfig class from a library. This imported JavaConfig is supposed to wrap any DataSource created in the primary app with an Aspect, which has an autowired LogDelegator.
As long as the primary app contains only a DataSource, everything works. But as soon as I add an EntityManager to the primary app, I get a nested IllegalArgumentException saying that the LogDelegator is null.
Primary App's Config:
#Configuration
#Import(MonitoringConfig.class)
public class SpringAppConfig
{
#Bean
public DataSource dataSource()
{
EmbeddedDatabaseBuilder builder = new EmbeddedDatabaseBuilder();
EmbeddedDatabase db = builder.setType(EmbeddedDatabaseType.H2).build();
return db;
}
}
Imported Library Config:
#Configuration
#EnableSpringConfigured
public class MonitoringConfig extends WebMvcConfigurerAdapter
{
#Bean
public LogDelegator logDelegator()
{
return new LogDelegator();
}
#Bean
public ConfigurationAspect configurationAspect()
{
return Aspects.aspectOf(ConfigurationAspect.class);
}
}
The Aspect:
#Configurable
public aspect ConfigurationAspect
{
#Autowired
LogDelegator logDelegator;
Object around() : execution(public DataSource (#Configuration *).*(..)) {
Object ret = proceed();
if (ret instanceof DataSource) {
Log4jdbcProxyDataSource dataSource = new Log4jdbcProxyDataSource((DataSource) ret);
dataSource.setLogFormatter(logDelegator);
return dataSource;
} else {
return ret;
}
}
This code works great until I add the following,
#Bean
public LocalContainerEntityManagerFactoryBean entityManagerFactory()
{
HibernateJpaVendorAdapter vendorAdapter = new HibernateJpaVendorAdapter();
vendorAdapter.setGenerateDdl(true);
vendorAdapter.setDatabase(Database.H2);
vendorAdapter.setShowSql(true);
LocalContainerEntityManagerFactoryBean factory = new LocalContainerEntityManagerFactoryBean();
factory.setDataSource(dataSource());
factory.setJpaVendorAdapter(vendorAdapter);
return factory;
}
#Bean
public EntityManager entityManager()
{
return entityManagerFactory().getObject().createEntityManager();
}
#Bean
public PlatformTransactionManager transactionManager()
{
JpaTransactionManager jpaTransactionManager = new JpaTransactionManager();
jpaTransactionManager.setEntityManagerFactory(entityManagerFactory().getObject());
return jpaTransactionManager;
}
and then I get:
java.lang.reflect.InvocationTargetException at java.lang.Thread.run(Thread.java:722)
Caused by: org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'entityManagerFactory' defined in class SpringAppConfig: Instantiation of bean failed; nested exception is org.springframework.beans.factory.BeanDefinitionStoreException: Factory method [publ
ic org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean com.fl.sas.configurable.config.SpringAppConfig.entityManagerFactory()] threw exception; ne
sted exception is org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'dataSource' defined in class SpringAppConfig: Instantiation of bean failed; nested exception is org.springframework.beans.factory.BeanDefinitionStoreException: Factory method [public
javax.sql.DataSource SpringAppConfig.dataSource()] threw exception; nested exception is java.lang.IllegalArgumentException: log4j
dbc: logDelegator cannot be null.
Can anyone help?
I needed to add
#Depends(value="configurationAspect") on dataSource()
Luca Basso Ricci answered the question. If he ever adds the answer, I'll give him the credit. :)

#Autowired bean not being found

I'm getting errors trying to inject resource dependencies into my unit testing.
My approach has been to write a TestConfig.java to replace the applicationContext.xml for production which manages the connections of the beans. So that I can run it with an in-memory database and just test components.
TestConfig.java
#Configuration
#EnableTransactionManagement
public class TestConfig {
#Bean
public DataSource dataSource() {
DriverManagerDataSource ds = new DriverManagerDataSource();
ds.setDriverClassName("org.hsqldb.jdbcDriver");
ds.setUrl("jdbc:hsqldb:mem:testdb");
ds.setUsername("sa");
ds.setPassword("");
return ds;
}
#Bean
public LocalContainerEntityManagerFactoryBean entityManagerFactoryBean(){
LocalContainerEntityManagerFactoryBean lcemfb
= new LocalContainerEntityManagerFactoryBean();
lcemfb.setDataSource(this.dataSource());
lcemfb.setPackagesToScan(new String[] {"com.dao","com.data"});
lcemfb.setPersistenceUnitName("MyTestPU");
HibernateJpaVendorAdapter va = new HibernateJpaVendorAdapter();
lcemfb.setJpaVendorAdapter(va);
Properties ps = new Properties();
ps.put("hibernate.dialect", "org.hibernate.dialect.HSQLDialect");
ps.put("hibernate.hbm2ddl.auto", "create");
lcemfb.setJpaProperties(ps);
lcemfb.afterPropertiesSet();
return lcemfb;
}
#Bean
public PlatformTransactionManager transactionManager() {
JpaTransactionManager tm = new JpaTransactionManager();
tm.setEntityManagerFactory(this.entityManagerFactoryBean().getObject());
return tm;
}
#Bean
public PersistenceExceptionTranslationPostProcessor exceptionTranslation(){
return new PersistenceExceptionTranslationPostProcessor();
}
#Bean
public AutowiredAnnotationBeanPostProcessor autowiredAnnotationBeanPostProcessor()
{
return new AutowiredAnnotationBeanPostProcessor();
}
}
ProductsDaoTest.java
#ContextConfiguration(classes = { TestConfig.class })
#RunWith(SpringJUnit4ClassRunner.class)
public class ProductsDaoTest {
#Resource(name="com.dao.ProductsDao")
private ProductsDao testDao;
#Test
public void testSaveProduct() {
Product productA = new Product();
testDao.save(productA);
Set<Product> products = testDao.getAllProducts();
assertNotNull(products);
}
}
The error is Error creating bean with name 'com.dao.ProductsDaoTest': Injection of resource dependencies failed
So it can't find the ProductDao Bean which is a #Repository with a #Autowired sessionFactory.
So my guess is that because I'm not naming the beans using xml it can't find it, though I thought it should automatically pick it up from setPackagesToScan(). So is there a way to manually insert the Bean mapping so that it can be found?
Also more generally is this a reasonable way to go about testing Spring DAO configurations?
Regards,
Iain
I think you are trying to use wrong name of your DAO bean in #Resource annotation. Have you explicitly specify name of the ProductsDao bean using #Qualifier? If no, then as I remember by default the name of the bean will be productsDao. So you should inject your DAO like:
#Resource(name="productsDao")
private ProductsDao testDao;
If you have only one ProductDAO implementation then simply write:
#Autowired
private ProductsDao testDao;
or
#Inject
private ProductsDao testDao;
In case if you want to give specific name to DAO then use next construction:
#Respository
#Qualifier(name="specificName")
public class ProductDAO...
EDIT:
As Boris noted you should also specify which package to scan for defined beans (classes annotated with #Component, #Service, #Repository...). For this you should add #ComponentScan annotation to your configuration class definition.
#Configuration
#EnableTransactionManagement
#ComponentScan(basePackages = {"package_to_scan"})
public class TestConfig {...}

FactoryBeans and the annotation-based configuration in Spring 3.0

Spring provides the FactoryBean interface to allow non-trivial initialisation of beans. The framework provides many implementations of factory beans and -- when using Spring's XML config -- factory beans are easy to use.
However, in Spring 3.0, I can't find a satisfactory way of using factory beans with the annotation-based configuration (née JavaConfig).
Obviously, I could manually instantiate the factory bean and set any required properties myself, like so:
#Configuration
public class AppConfig {
...
#Bean
public SqlSessionFactory sqlSessionFactory() throws Exception {
SqlSessionFactoryBean factory = new SqlSessionFactoryBean();
factory.setDataSource(dataSource());
factory.setAnotherProperty(anotherProperty());
return factory.getObject();
}
However, this would fail if the FactoryBean implemented any Spring-specific callback interfaces, like InitializingBean, ApplicationContextAware, BeanClassLoaderAware, or #PostConstruct for example. I also need to inspect the FactoryBean, find out what callback interfaces it implements, then implement this functionality myself by calling setApplicationContext, afterPropertiesSet() etc.
This feels awkward and back-to-front to me: application-developers should not have to implement the callbacks of the IOC container.
Does anyone know of a better solution to using FactoryBeans from Spring Annotation configs?
I think that this is best solved when you rely on auto-wiring. If you are using Java configuration for the beans, this would like:
#Bean
MyFactoryBean myFactory()
{
// this is a spring FactoryBean for MyBean
// i.e. something that implements FactoryBean<MyBean>
return new MyFactoryBean();
}
#Bean
MyOtherBean myOther(final MyBean myBean)
{
return new MyOtherBean(myBean);
}
So Spring will inject for us the MyBean instance returned by the myFactory().getObject() as it does with XML configuration.
This should also work if you are using #Inject/#Autowire in your #Component/#Service etc classes.
As far as I understand your problem is what you want a result of sqlSessionFactory() to be a SqlSessionFactory (for use in other methods), but you have to return SqlSessionFactoryBean from a #Bean-annotated method in order to trigger Spring callbacks.
It can be solved with the following workaround:
#Configuration
public class AppConfig {
#Bean(name = "sqlSessionFactory")
public SqlSessionFactoryBean sqlSessionFactoryBean() { ... }
// FactoryBean is hidden behind this method
public SqlSessionFactory sqlSessionFactory() {
try {
return sqlSessionFactoryBean().getObject();
} catch (Exception ex) {
throw new RuntimeException(ex);
}
}
#Bean
public AnotherBean anotherBean() {
return new AnotherBean(sqlSessionFactory());
}
}
The point is that calls to #Bean-annotated methods are intercepted by an aspect which performs initialization of the beans being returned (FactoryBean in your case), so that call to sqlSessionFactoryBean() in sqlSessionFactory() returns a fully initialized FactoryBean.
Spring JavaConfig had a ConfigurationSupport class that had a getObject() method for use with FactoryBean's.
You would use it be extending
#Configuration
public class MyConfig extends ConfigurationSupport {
#Bean
public MyBean getMyBean() {
MyFactoryBean factory = new MyFactoryBean();
return (MyBean) getObject(factory);
}
}
There is some background in this jira issue
With Spring 3.0 JavaConfig was moved into Spring core and it was decided to get rid of the ConfigurationSupport class. Suggested approach is to now use builder pattern instead of factories.
An example taken from the new SessionFactoryBuilder
#Configuration
public class DataConfig {
#Bean
public SessionFactory sessionFactory() {
return new SessionFactoryBean()
.setDataSource(dataSource())
.setMappingLocations("classpath:com/myco/*.hbm.xml"})
.buildSessionFactory();
}
}
Some background here
This is what I'm doing, and it works:
#Bean
#ConfigurationProperties("dataSource")
public DataSource dataSource() { // Automatically configured from a properties file
return new BasicDataSource();
}
#Bean
public SqlSessionFactoryBean sqlSessionFactory(DataSource dataSource) throws Exception {
SqlSessionFactoryBean factory = new SqlSessionFactoryBean();
factory.setDataSource(dataSource); // Invoking dataSource() would get a new instance which won't be initialized
factory.setAnotherProperty(anotherProperty());
return factory;
}
#Bean
public AnotherBean anotherBean(SqlSessionFactory sqlSessionFactory) { // This method receives the SqlSessionFactory created by the factory above
return new AnotherBean(sqlSessionFactory);
}
Any bean you have declared can be passed as an argument to any other #Bean method (invoking the same method again will create a new instance which is not processed by spring).
If you declare a FactoryBean, you can use the bean type it creates as an argument for another #Bean method, and it will receive the right instance.
You could also use
#Autowired
private SqlSessionFactory sqlSessionFactory;
Anywhere and it will work too.
Why do you not inject the Factory in your AppConfiguration?
#Configuration
public class AppConfig {
#Resource
private SqlSessionFactoryBean factory;
#Bean
public SqlSessionFactory sqlSessionFactory() throws Exception {
return factory.getObjectfactory();
}
}
But may I did not understand your question correct. Because it looks to me that you are trying something strange - go a step back and rethink what are you really need.
Here is how I am doing it:
#Bean
def sessionFactoryBean: AnnotationSessionFactoryBean = {
val sfb = new AnnotationSessionFactoryBean
sfb.setDataSource(dataSource)
sfb.setPackagesToScan(Array("com.foo.domain"))
// Other configuration of session factory bean
// ...
return sfb
}
#Bean
def sessionFactory: SessionFactory = {
return sessionFactoryBean.getObject
}
The sessionFactoryBean gets created and the proper post-create lifecycle stuff happens to it (afterPropertiesSet, etc).
Note that I do not reference the sessionFactoryBean as a bean directly. I autowire the sessionFactory into my other beans.

Categories

Resources