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).
Related
I am trying to create multiple beans that implement the same interface. I have a bean that I want to use as the "default" #Primary bean; however, since it acts as a default, I want another bean to be able to use #Primary (or something similar) to make a primary bean. In other words, this default one should be like... "#PrimaryIfNoPrimaryAlreadyExist" kind of thing. For example, I have this:
#Configuration
public class DefaultCustomObjectMapperConfiguration {
#Bean
#Primary
public ICustomObjectMapper defaultCustomObjectMapper(#Value("${objectmapper.serialize.defaultFormat:JSON}") String defaultMapperFormat,
#Qualifier(DEFAULT_XML_MAPPER_KEY) ICustomObjectMapper xmlMapper,
#Qualifier(DEFAULT_JSON_MAPPER_KEY) ICustomObjectMapper jsonMapper) {
return "XML".equals(defaultMapperFormat) ? xmlMapper : jsonMapper;
}
#Bean
#Qualifier(DEFAULT_JSON_MAPPER_KEY)
public ICustomObjectMapper defaultJSONCustomObjectMapper() {
return new DefaultCustomObjectMapper(new ObjectMapper());
}
#Bean
#Qualifier(DEFAULT_XML_MAPPER_KEY)
public ICustomObjectMapper defaultXMLCustomObjectMapper() {
return new DefaultCustomObjectMapper(new XmlMapper());
}
}
I always want to create the defaultJSONCustomObjectMapper() bean and the defaultXMLCustomObjectMapper() bean, but the defaultCustomObjectMapper() should only be created if there is not another bean in the Spring context that is defined as #Primary. For example, it should not be created (or at least not used as Primary) if someone else defines this in the same context:
#Primary
#Bean
ICustomObjectMapper anotherCustomObjectMapper() {
return new AnotherCustomObjectMapper();
}
I believe you can override a bean by calling it the same name, but I don't want to do it that way because then it requires the service pulling this in to know that it has to call the bean something special.
Is this possible? Or is there a way to do it that is better than this?
Edit:
Looking at the Spring annotations, there's the ConditionalOnSingleCandidate. It would be more accurate for me to say I want the opposite of that, i.e. ConditionalOnMultipleCandidates
Adding #ConditionalOnMissingBean(annotation = Primary.class) to your "default" #Primary bean should do the trick. This will only register your default primary bean if another primary bean of the same type is not already registered.
#Bean
#Primary
#ConditionalOnMissingBean(annotation = Primary.class)
public ICustomObjectMapper defaultCustomObjectMapper(#Value("${objectmapper.serialize.defaultFormat:JSON}") String defaultMapperFormat,
#Qualifier(DEFAULT_XML_MAPPER_KEY) ICustomObjectMapper xmlMapper,
#Qualifier(DEFAULT_JSON_MAPPER_KEY) ICustomObjectMapper jsonMapper) {
// ...
}
Note that depending on the potential order of your other bean creation, you may want to ensure that this default bean is processed last. For example, by adding #Order(Integer.MAX_INT).
Update: Sean correctly points out that #ConditionalOnMissingBean doesn't work in this case because it looks for ANY bean with Primary annotation, not just beans of our type.
A somewhat ugly alternative is to programmatically set a bean to primary after bean creation if no other primary bean of that type was found. This can be done by implementing BeanDefinitionRegistryPostProcessor and BeanFactoryAware. Note that bean name is used, not qualifier.
#Configuration
public class DefaultCustomObjectMapperConfiguration
implements BeanDefinitionRegistryPostProcessor, BeanFactoryAware {
private BeanFactory beanFactory;
#Value("${objectmapper.serialize.defaultFormat:JSON}")
private String defaultMapperFormat;
#Override
public void setBeanFactory(BeanFactory beanFactory) {
this.beanFactory = beanFactory;
}
#Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
// unused
}
#Override
public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException {
String[] beansOfType = BeanFactoryUtils.beanNamesForTypeIncludingAncestors((ListableBeanFactory) beanFactory, ICustomObjectMapper.class);
for(String beanName : beansOfType) {
BeanDefinition beanDef = registry.getBeanDefinition(beanName);
if(beanDef.isPrimary()) {
// found an existing primary bean of same type
return;
}
}
// note that getBeanDefinition retrieves by bean name, which is not necessarily equal to qualifier
BeanDefinition defaultPrimaryBeanDef =
registry.getBeanDefinition("XML".equals(defaultMapperFormat) ? DEFAULT_XML_MAPPER_KEY : DEFAULT_JSON_MAPPER_KEY);
defaultPrimaryBeanDef.setPrimary(true);
}
#Bean(DEFAULT_JSON_MAPPER_KEY)
#Qualifier(DEFAULT_JSON_MAPPER_KEY)
public ICustomObjectMapper defaultJSONCustomObjectMapper() {
return new DefaultCustomObjectMapper(new ObjectMapper());
}
#Bean(DEFAULT_XML_MAPPER_KEY)
#Qualifier(DEFAULT_XML_MAPPER_KEY)
public ICustomObjectMapper defaultXMLCustomObjectMapper() {
return new DefaultCustomObjectMapper(new XmlMapper());
}
}
I have multiple DataSources in my application.
The standard org.springframework.boot.autoconfigure.orm.jpa.HibernateJpaConfiguration is annotated with #ConditionalOnSingleCandidate(DataSource.class)
I am attempting to select a #Primary DataSource programmatically.
I have tried a BeanFactoryPostProcessor that naively selects the first DataSource and marks as primary):
#Bean
public BeanFactoryPostProcessor beanFactoryPostProcessor() {
return this::setPrimaryDataSource;
}
public void setPrimaryDataSource(ConfigurableListableBeanFactory beanFactory) {
// Get all DataSource bean names
String[] dataSourceBeanNames = beanFactory.getBeanNamesForType(DataSource.class);
// Find primaryBeanName
String primaryBeanName = dataSourceBeanNames.length > 0 ? dataSourceBeanNames[0] : null;
// Return appropriate bean
assert primaryBeanName != null;
BeanDefinition beanDefinition = beanFactory.getBeanDefinition(primaryBeanName);
beanDefinition.setPrimary(true);
LOGGER.info("Primary DataSource: {}", primaryBeanName);
}
However, this does not appear to work - the #ConditionalOnSingleCandidate(DataSource.class) check on HibernateJpaConfiguration still fails.
Is there anywhere else I can put this code such that it will be executed before the check for #ConditionalOnSingleCandidate?
BeanFactoryPostProcessor worked for me:
#Component
public class MyBeanFactoryPostProcessor implements BeanFactoryPostProcessor {
#Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
// logic to retrieve your bean name
String beanName = beanFactory.getBeanNamesForType(MyService.class)[0];
BeanDefinition bd = beanFactory.getBeanDefinition(beanName);
bd.setPrimary(true);
}
}
If your code is in a class with #Configuration, the method need to be static in order to update the bean definition before any bean creation.
Here is a sample for PropertySourcesPlaceholderConfigurer.
#Configuration
public class AppConfig {
#Bean
public static PropertySourcesPlaceholderConfigurer propertyPlaceholderConfigurer() {
return new PropertySourcesPlaceholderConfigurer();
}
}
In the documentation:
You may declare #Bean methods as static, allowing for them to be called without creating their containing configuration class as an instance. This makes particular sense when defining post-processor beans (for example, of type BeanFactoryPostProcessor or BeanPostProcessor), since such beans get initialized early in the container lifecycle and should avoid triggering other parts of the configuration at that point.
As the question suggests, how do you Autowire a class with non SpringBoot managed class as constructor args.
The following is a code block illustrating this:
#Component
class Prototype
{
#Autowired
private Repository repository;
private NonSpringBootManagedBean bean;
Prototype(NonSpringBootManagedBean bean)
{
this.bean = bean;
}
}
#Component
class PrototypeClient
{
#Autowired
private ApplicationContext context;
private void createNewPrototype(NonSpringBootManagedBean bean)
{
// This throws an error saying no bean of type NonSpringBootManangedBean found
Prototype prototype = context.getBean(Prototype.class, bean);
}
}
The reason I am using ApplicationContext to obtain an instance of Prototype instead of using #Autowired is because I need a new instance of Prototype within the method createNewPrototype() every time it's invoked and not a singleton instance (Also, please advise if this way obtaining a new instance is incorrect).
Update:
As others have stated to move my creation of bean to a Java configuration class and adding method annotated by #Bean and instantiating the NonSpringBootManagedBean in the #Bean method. But I think this is not possible as this NonSpringBootManagedBean is passed by caller of PrototypeClient.createNewPrototype().
Update
I have updated my above code example with a more clarity. Please refer this now.
#Component
class Prototype
{
#Autowired
private Repository repository;
// Here Session is part of javx.websocket package and cannot be added as part of
// Java configuration class with a #Bean annotation
// In this case how can I use constructor injection?
private Session session;
Prototype(Session session)
{
this.session = session;
}
}
#Component
class PrototypeClient
{
#Autowired
private ApplicationContext context;
private void createNewPrototype(Session session)
{
Prototype prototype = context.getBean(Prototype.class, session);
}
}
#ServerEndpoint(value = "/resources")
class WebSocketController
{
private PrototypeClient client = ApplicationContext.getBean(PrototypeClient.class);
#OnMessage
void handleMessage(Session session, String message)
{
client.createNewPrototype(session);
}
}
Did you know that you can change your bean scope to be a prototype reference instead of a singleton. That way you can scope a single bean definition to any number of object instances.
https://docs.spring.io/spring/docs/3.0.0.M3/reference/html/ch04s04.html
private NonSpringBootManagedBean bean = new NonSpringBootManagedBean();
#Bean
public Prototype getPrototype(){
return new Prototype(bean);
}
Spring can not Autowire an Object if it is not aware of it. Some where there need to be #Component or #Bean or some other annotation like #Service etc to tell spring to manage the instance .
Also it is suggested that if you are using a private variable in Autowire it should be part of constructor(for constructor injection ) or a setter method must be provided(setter injection)
To solve your error : you can create a java config class and place it in you base pkg (same as #SpringBootApplication or add #ComponentScan("pkg in which config is present") on class with #SpringBootApplication)
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Configuration;
#Configuration
public class myconfig {
#Bean
public NonSpringBootManagedBean nonSpringBootManagedBean()
{
return new NonSpringBootManagedBean();
}
}
Define a bean with scope prototype
That is each time injected as new instance.
In SpringBoot you can use the annotation #Scope("prototype") to your bean class Prototype.
#Component
#Scope("prototype")
class Prototype {
#Autowired
private Repository repository;
private NonSpringBootManagedBean bean;
Prototype() {
// you can only modify this 'NonSpringBootManagedBean' later
// because Spring calls constructor without knowing NonSpringBootManagedBean
this.bean = new NonSpringBootManagedBean();
// do something with 'repository' because its defined
}
public void setNonSpringBootManagedBean(NonSpringBootManagedBean bean) {
this.bean = bean;
}
}
Use instances of this bean
Via injection (e.g. #Autowired to constructor) you can use different instances of this prototypical bean within other beans.
#Component
class PrototypeClient {
// ApplicationContext still used?
#Autowired
private ApplicationContext context;
private Prototype prototypeInstance;
#Autowired // injects the new instance of Prototype
public PrototypeClient(Prototype p)
this.prototypeInstance = p;
// here you can change the NSBMB
modifyPrototype();
}
private void modifyPrototype(NonSpringBootManagedBean bean) {
this.prototypeInstance.setNonSpringBootManagedBean( new NonSpringBootManagedBean() );
}
}
Why is your exception thrown?
no bean of type NonSpringBootManangedBean found
Spring complains when trying to instantiate the bean of type Prototype
Prototype prototype = context.getBean(Prototype.class, bean);
because for calling its constructor it needs to pass an argument of type NonSpringBootManagedBean. Since all this bean-instantiating is done internally by Spring(Boot), you can not intercept and tell Spring: "Hey, use this bean of class NonSpringBootManagedBean" like you tried in method createNewPrototype(NonSpringBootManagedBean bean).
Why could'nt the NonSpringBootManagedBean be managed as bean by Spring(Boot)?
Autowiring in SpringBoot is a way of dependency-injection. This means a bean has been previously instantiated by SpringBoot, automatically at startup (when Spring boots). And this bean is now injected as dependency into another bean, etc. because this other bean depends on it.
If you tell us some more background, we could possibly bring light into your situation. This can be some answers to:
What is NonSpringBootManagedBean and why is it no managed bean?
What is Prototype and for which purpose does it use NonSpringBootManagedBean?
What is PrototypeClient and why does it create its own Prototype ?
I am not sure if I have understood the relationship and purpose between your objects/classes.
The simplified version I have looks like this:
#Configuration
#EnableTransactionManagement
public class DatabaseDefaultConfig {
#Bean
#Primary
public DataSource dataSourceDefault(DatabaseConfigurationHelper databaseConfigurationHelper) {
return ...;
}
#Bean
#Primary
public SqlSessionFactoryBean sqlSessionFactoryBeanDefault(DatabaseConfigurationHelper databaseConfigurationHelper, #Value("${datasource.default.cacheEnabled}") boolean cacheEnabled) throws Exception {
return ...;
}
}
#Configuration
#EnableTransactionManagement
public class DatabaseMaintenanceConfig {
#Bean
public DataSource dataSourceMaintenance(DatabaseConfigurationHelper databaseConfigurationHelper) {
return ...;
}
#Bean
public SqlSessionFactoryBean sqlSessionFactoryBeanMaintenance(DatabaseConfigurationHelper databaseConfigurationHelper, #Value("${datasource.maintenance.cacheEnabled}") boolean cacheEnabled) throws Exception {
return ...;
}
}
The classes are very much the same, one uses #Primary. Now let's create two dummy beans:
#Configuration
public class CommonDatabaseConfig {
#Bean
public AtomicInteger a(SqlSessionFactoryBean sqlSessionFactoryBean) {
return new AtomicInteger();
}
#Bean
public AtomicLong b(DataSource dataSource) {
return new AtomicLong();
}
}
While b works fine, a fails and claims that two beans were found:
***************************
APPLICATION FAILED TO START
***************************
Description:
Parameter 0 of method a in sjngm.CommonDatabaseConfig required a single bean, but 2 were found:
- &sqlSessionFactoryBeanDefault: defined by method 'sqlSessionFactoryBeanDefault' in class path resource [sjngm/DatabaseDefaultConfig.class]
- &sqlSessionFactoryBeanMaintenance: defined by method 'sqlSessionFactoryBeanMaintenance' in class path resource [sjngm/DatabaseMaintenanceConfig.class]
Action:
Consider marking one of the beans as #Primary, updating the consumer to accept multiple beans, or using #Qualifier to identify the bean that should be consumed
Note that both beans start with a &. Reading this question and its answer it becomes clear that this is intended. However, that seems to break applying the #Primary as it fails in this area of Spring's DefaultListableBeanFactory:
protected boolean isPrimary(String beanName, Object beanInstance) {
if (containsBeanDefinition(beanName)) {
return getMergedLocalBeanDefinition(beanName).isPrimary();
}
BeanFactory parent = getParentBeanFactory();
return (parent instanceof DefaultListableBeanFactory &&
((DefaultListableBeanFactory) parent).isPrimary(beanName, beanInstance));
}
containsBeanDefinition() in line 2 returns false because of the ampersand.
Now: Am I doing something wrong here? How can I fix this?
This is Spring 4.3.9 (as part of Spring-Boot 1.5.4)
It's fixed within spring-framework PR 22711.
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.