FactoryBeans and the annotation-based configuration in Spring 3.0 - java

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.

Related

Error creating bean with name 'batchDataSource': Requested bean is currently in creation: Is there an unresolvable circular reference?

I have a batch configuration. I saw the batch process is default using InMemoryMap. Instead I need to use the MySQL to send all the execution details by Batch. But when I use the following code I am getting the following error,
Error creating bean with name 'batchDataSource': Requested bean is
currently in creation: Is there an unresolvable circular reference?
#Configuration
#EnableBatchProcessing
public class BatchProcess extends DefaultBatchConfigurer {
private #Autowired Environment env;
#Bean
#StepScope
public ItemReader reader() {
...
}
#Bean
#StepScope
public ItemProcessor processor() {
...
}
#Bean
#StepScope
public ItemWriter writer() {
...
}
#Bean
#Primary
public DataSource batchDataSource() {
HikariDataSource hikari = new HikariDataSource();
hikari.setDriverClassName(env.getProperty("spring.datasource.driver-class-name"));
hikari.setJdbcUrl(env.getProperty("spring.datasource.url"));
hikari.setUsername(env.getProperty("spring.datasource.username"));
hikari.setPassword(env.getProperty("spring.datasource.password"));
return hikari;
}
public JobRepository getJobRepository() {
JobRepositoryFactoryBean factory = new JobRepositoryFactoryBean();
factory.setDataSource(batchDataSource());
factory.setTransactionManager(manager());
factory.afterPropertiesSet();
return factory.getObject();
}
public PlatformTransactionManager manager() {
return new ResourcelessTransactionManager();
}
#Bean
public Step step() {
return stepBuilderFactory.get("step")
.chunk(1000)
.reader(reader())
.processor(processor())
.writer(writer())
.build();
}
#Bean
public Job job() {
return jobBuilderFactory.get("job")
.flow(step())
.end()
.build();
}
#Bean
public JobLauncher getJobLauncher() {
SimpleJobLauncher launcher = new SimpleJobLauncher();
launcher.setJobRepository(createJobRepository());
return launcher;
}
}
In property file I am using,
spring.batch.job.enabled=false
spring.batch.initialize-schema=always
So what I missed? I am using JPA. And even why it is not using available JPA datasource? How can I force the Spring batch to use default MySQL instead InMemoryMap?
The error message you are receiving may not be the clearest, but it should point you in the right direction. You appear to have a circular dependency within your code.
This happens when you have two (or more) beans that mutually depend upon one another, preventing the creation of one without the existence of the other (and vice versa) - the proverbial chicken and egg problem. You can generally avoid this with setter injection and some kind of post-construction initialization.
I think you have created this situation by extending DefaultBatchConfigurer and then defining the #Bean annotated method getJobLauncher() which directly calls DefaultBatchConfigurer's createJobRepository() method without ensuring that the DataSource is first set within DefaultBatchConfigurer.
This is entirely unnecessary, because DefaultBatchConfigurer already creates JobRepository, JobExplorer, and JobLauncher for you in the proper order.
From DefaultBatchConfigurer:
#PostConstruct
public void initialize() {
try {
this.jobRepository = createJobRepository();
this.jobExplorer = createJobExplorer();
this.jobLauncher = createJobLauncher();
} catch (Exception e) {
throw new BatchConfigurationException(e);
}
}
If you are going to extend DefaultBatchConfigurer, then I suggest you eliminate the following methods from your code:
getJobRepository()
manager()
getJobLauncher()
From your code sample, it appears that you are already setting the following properties (in your application.properties file?):
spring.datasource.jdbcUrl=...
spring.datasource.username=...
spring.datasource.password=...
spring.datasource.driverClassName=...
That should be sufficient to allow Spring's AutoConfiguration to create an Hikari DataSource for you automatically, and this is the approach I usually take. The Spring Bean name will be dataSource, and this will be autowired into DefaultBatchConfigurer via setDataSource().
However, in your code sample, you have also defined a #Bean annotated method named batchDataSource(), which looks no different to what you should receive from Spring AutoConfiguration. As long as you have the spring.datasource properties mentioned earlier configured, you should be able to eliminate batchDataSource() as well, but I don't think that's necessary, so your choice.
If you still want to manually configure your DataSource, then I suggest that you not extend DefaultBatchConfigurer, but instead define a custom bean for it in a configuration class where you can directly pass in your custom DataSource (based on what I currently know of your use case).
#Bean
public BatchConfigurer batchConfigurer(){
return new DefaultBatchConfigurer( batchDataSource() );
}
First of all, explicitly define the mysql-connector dependency in the pom.xml and remove anything related to in-memory map from the project.
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
If you want to define your own configuration with beans manually, then you can't use the AutoConfiguration classes because they create the required beans for you on startup automatically and that might cause an issue if you are defining own custom DB configuration classes. Therefore, you have to exclude DataSourceAutoConfiguration, HibernateJpaAutoConfiguration and DataSourceTransactionManagerAutoConfiguration to resolve the issue.
Just update the #SpringBootApplication class :
#SpringBootApplication(
exclude = {DataSourceAutoConfiguration.class, HibernateJpaAutoConfiguration.class,
DataSourceTransactionManagerAutoConfiguration.class
}
)
public class App {
public static void main(String[] args) {
SpringApplication.run(App.class, args);
}
}

Extending DataSource with a sub class in Spring Boot AutoConfiguration

I'm trying to write an auto-configuration library that adds functionality to any DataSource. I've written a sub-class that I'll call CustomDataSource here and which overrides some of the methods of DataSource.
#Configuration
#ConditionalOnBean(DataSource.class)
#AutoConfigureAfter(DataSourceAutoConfiguration.class)
public class CustomDataSourceAutoConfiguration {
private final DataSource dataSource;
public CustomDataSourceAutoConfiguration(DataSource dataSource) {
this.dataSource = dataSource;
}
#Primary
#Bean
public CustomDataSource customDataSource() {
return new CustomDataSource(dataSource);
}
}
But I can't find a way that allows me to do what I want. It will always result in a circular reference and the exception:
BeanCurrentlyInCreationException: Error creating bean with name 'customDataSource': Requested bean is currently in creation: Is there an unresolvable circular reference?
Is there a way around this?
I found a way to work around this issue by implementing a BeanPostProcessor:
public class DataSourcePostProcessor implements BeanPostProcessor {
#Override
public Object postProcessAfterInitialization(Object bean, String beanName) {
if (bean instanceof DataSource && !(bean instanceof CustomDataSource)) {
return new CustomDataSource((DataSource) bean);
} else {
return bean;
}
}
}
The postProcessAfterInitialization method can explicitly be used for wrapping beans in a proxy, citing from the BeanPostProcessor documentation:
[...] post-processors that wrap beans with proxies will normally implement postProcessAfterInitialization(java.lang.Object, java.lang.String).

Eliminating Spring.xml from Spring Framework

In Spring Framework is it possible to eliminate the entire Spring.xml and use a configuration class with #Configuration and #Bean annotation for creating bean, and for all other purpose use a spring.xml?
Yes, you can have pure java configuration in Spring. You have to create a class and annotate it with #Configuration. We annotate methods with #Bean and instantiate the Spring bean and return it from that method.
#Configuration
public class SomeClass {
#Bean
public SomeBean someBean() {
return new SomeBean();
}
}
If you want to enable component scanning, then you can give #ComponentScan(basePackages="specify_your_package") under the #Configuration. Also the method name as someBean serves as bean id. Also if you have to inject a dependency, you can use constructor injection and do as following:
#Configuration
public class SomeClass {
#Bean
public SomeDependency someDependency() {
return new SomeDependency();
}
#Bean
public SomeBean someBean() {
return new SomeBean(someDependency());
}
}
Yes,most of (maybe all of)official guides uses absolutely no xml configuration file,just annotations.

mybatis #MapperScan not working

I'm trying to set up the Java config for MyBatis & the #MapperScan does not appear to be accomplishing anything. Note, I can get the application to work with XML config.
What am I missing? The com.test.mapper package definitely exists & has a file/iterface called TestMapper. The corresponding xml is in the correct location in the resources folder.
*************************** APPLICATION FAILED TO START
Description:
Field templateMapper in
com.test.TestController required a
bean of type 'com.test.mapper.TestMapper' that
could not be found.
Action:
Consider defining a bean of type
'com.test.mapper.TestMapper' in your
configuration.
Autowired that is failing
#Autowired
TestMapper _testMapper;
config
#Configuration
#MapperScan("com.test.mapper")
public class AppConfig {
#Bean
public DataSource dataSource() {
SimpleDriverDataSource dataSource = new SimpleDriverDataSource();
try {
dataSource.setDriverClass(com.microsoft.sqlserver.jdbc.SQLServerDriver.class);
//dataSource.setDriverClassName("com.microsoft.sqlserver.jdbc.SQLServerDriver");
dataSource.setUrl("jdbc:sqlserver://server;databaseName=db1;integratedSecurity=true;");
} catch (Exception e) {
}
return dataSource;
}
#Bean
public DataSourceTransactionManager transactionManager() {
return new DataSourceTransactionManager(dataSource());
}
#Bean
public SqlSessionFactoryBean sqlSessionFactory() throws Exception {
SqlSessionFactoryBean sqlSessionFactoryBean = new SqlSessionFactoryBean();
sqlSessionFactoryBean.setTypeAliasesPackage("com.test.domain");
sqlSessionFactoryBean.setDataSource(dataSource());
return sqlSessionFactoryBean;
}
}
I solved this. My issue wasn't with Mybatis. It was with Spring. This link to the Spring docs says to "...locate your main application class in a root package above other classes".
I had not done that. Once I moved application class ( annotated with SpringBootApplication) then the #MapperScan annotation worked.

Spring Batch Table Prefix when using Java Config

My Spring Batch repository (deployed on an Oracle database) lies in a different schema such that I need to prepend the schema name.
When using XML configuration, this would be easy to do:
<job-repository id="jobRepository" table-prefix="GFA.BATCH_" />
However, as I use Java Config, this turns out to be more tricky.
The best solution I found is to have my Java Config class extend DefaultBatchConfigurer and override the createJobRepository() method:
#Configuration
#EnableBatchProcessing
public class BatchConfiguration extends DefaultBatchConfigurer{
#Autowired
private DataSource dataSource;
#Autowired
private PlatformTransactionManager transactionManager;
#Override
protected JobRepository createJobRepository() throws Exception {
JobRepositoryFactoryBean factory = new JobRepositoryFactoryBean();
factory.setDataSource(dataSource);
factory.setTransactionManager(transactionManager);
factory.setTablePrefix("GFA.BATCH_");
factory.afterPropertiesSet();
return factory.getObject();
}
...
}
Compared to the XML solution, that's pretty much code! And it's not too logical either - my first guess was to provide an #Bean method as follows:
#Bean
public JobRepository jobRepository() throws Exception {
JobRepositoryFactoryBean factory = new JobRepositoryFactoryBean();
factory.setDataSource(dataSource);
factory.setTransactionManager(transactionManager);
factory.setTablePrefix("GFA.BATCH_");
factory.afterPropertiesSet();
return factory.getObject();
}
but this wouldn't work.
My question is:
Is my solution optimal or is there a better one? I would prefer to define a Bean instead of having to override some method of some class which is not very intuitive...
And obviously it would be even better if we could shorten the code to be somewhat close to the one-line code in the XML configuration.
Simply add this line to any of your properties files registered on you batch configuration:
spring.batch.table-prefix= GFA.BATCH_
FYI, the prefix spring.batch is mapped with org.springframework.boot.autoconfigure.batch.BatchProperties provided with Spring boot. See source code on github.
You should define in java the BatchConfigurer and in this bean you define the spring batch tables prefix:
#Bean
public BatchConfigurer batchConfigurer() {
return new DefaultBatchConfigurer() {
#Autowired
PlatformTransactionManager platformTransactionManager;
#Override
protected JobRepository createJobRepository() throws Exception {
JobRepositoryFactoryBean factory = new JobRepositoryFactoryBean();
factory.setDataSource(secondaryDataSource(secondaryDataSourceProperties()));
factory.setTransactionManager(platformTransactionManager);
factory.setIsolationLevelForCreate("ISOLATION_READ_COMMITTED");
factory.setTablePrefix(batchTablePrefix);
factory.setDatabaseType("POSTGRES");
factory.setMaxVarCharLength(maxVarcharSize);
return factory.getObject();
}
};
The accepted answer is not valid anymore, the Spring Boot properties is changed to
spring.batch.jdbc.table-prefix=something.prefix_
You can always refer to Spring-boot doc for latest update.

Categories

Resources