Conditionally load a bean in spring boot using xml configuration? - java

I am using Spring Boot 2.1.
I have some mixed configuration in my project : XML files and java classes with annotations.
We have this current configuration which works :
application.properties :
spring.profiles.active=dev, component1, component2
applicationContext-file.xml :
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd"
profile="component1">
<beans>
<bean id="myserviceimpl"
class="org.blabla.MyServiceImpl">
<property name="mydao">
<ref bean="mydao"></ref>
</property>
</bean>
</beans>
</beans>
We want to extract the component values from the spring.profiles.active property since they have nothing to do with the environment :
application.properties :
spring.profiles.active=dev
component1=true
component2=true
How can i condition the instantiation of the myserviceimpl bean inside the applicationContext-file.xml ?
I can no longer rely on the profile attribute since the spring.profiles.active property no longer includes
the values of the components.
Thanks for helping.

I haven't tried it by myself but I suggest to check the following:
Step 1
Create one XML file per profile (say, dev and prod for the sake of example):
app-config-dev.xml:
------------------
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd"
profile="dev">
<beans>
<!-- here define only beans that you want to load in __dev__ environment -->
<bean id="myserviceimpl"
class="org.blabla.MyServiceImpl">
<property name="mydao">
<ref bean="mydao"></ref>
</property>
</bean>
</beans>
</beans>
app-config-prod.xml:
------------------
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd"
profile="prod">
<beans>
<!-- here define only beans that you want to load in __production__ environment -->
<bean id="myserviceimpl"
class="org.blabla.MyServiceProdImpl">
<property name="mydao">
<ref bean="mydao"></ref>
</property>
</bean>
</beans>
</beans>
Step 2
Now in your "primary" applicationContext-file.xml instead of defining beans directly, include all the xml files that you've created during the step 1:
<import resource="classpath:app-config-dev.xml" />
<import resource="classpath:app-config-prod.xml" />
Read this thread for more details on this step if needed
Step 3
Remove the component1=true and component2=true from aplication properties, you don't need it anymore, the profile itself defines which beans should be loaded.
Update 1
Based OP's first comment:
You probably can try another option but I consider it a "low-level" solution and it requires deep understanding of how does spring loading process work under the hood.
So when spring starts it finds the definitions of beans (in xml, java config, annotations like #Component and so forth) and creates out of all this information a BeanDefinition - a metadata object that aggregates all the information about the bean (for example whether its singleton or prototype). At this point no beans are not created yet. Lets call this point in time a "Bean Definitions Done" point" (for the sake of explanations, its my term out of my head...
Then spring starts to build a graph of dependencies and starts creating beans, initializing them (Autowiring, post-construct methods, and so on) and placing them onto application context (if they're singletons).
Now, in spring there is an option to provide a hook called BeanFactoryPostProcessor that is invoked exactly at the "Bean Definitions Done" point. This is basically a java code that implements some interface and by itself is a spring bean that spring treats in a special way.
So you can implement this interface and manipulate the results of Bean factory.
Namely you can access every bean definition that spring has opened and if you think that the bean of this definition should not be created (here comes your custom logic that would check properties, whatever) - remove the bean definition from the registry:
For technical method of removing bean definitions see this thread
Here is an example of Bean Factory Post processor that actually adds a new bean although it wasn't registered

Related

Spring Framework Unable to Resolve References in IntelliJ

I set up a application configuration file for spring, added it to the facets, and set it up according to another configuration file that works perfectly.
All of the references to the spring components are visible and seem to work, but all references to items within the xml file fail to be found.
An example is with the tasks:
<task:scheduler id="taskScheduler" />
<task:executor id="taskExecutor" pool-size="1" />
<task:annotation-driven executor="taskExecutor" scheduler="taskScheduler" />
Both taskScheduler and taskExecutor cannot be resolved. As a result, the task bean never gets set up so all of my #Scheduled annotations never work.
Setting up the factory provider for services works just fine (all #Service and #Autowired annotations work), so I am completely certain the issue is with some configuration issue in the project.
What else needs to be configured in IntelliJ for the beans to work? Why does spring not rescan the file to find references to beans?
Here is where all of the springframework beans are referenced:
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:task="http://www.springframework.org/schema/task"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:cache="http://www.springframework.org/schema/cache"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.1.xsd
http://www.springframework.org/schema/task
http://www.springframework.org/schema/task/spring-task-3.1.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-3.1.xsd
http://www.springframework.org/schema/cache
http://www.springframework.org/schema/cache/spring-cache.xsd"
default-lazy-init="true">
The issue is having multiple contexts for application configuration files. To fix it, go to the configuration file in IntelliJ, then there is a section at the top where you can select which application context you want to put the context file into (Reads something like "Spring Application Context in module [your module]. File is included in [n] contexts"). Try selecting each one until the references resolve; it will remove it from the other contexts. My particular issue was that it was in the MVC context and it needed to be in the Spring Application Context.

Restlet Framework and Spring - Global filter

I'm having a problem with RESTlet Framework configuration with Spring. I want to have one global filter for all requests and responses. I guess I can use Filter class and it's methods beforeHandle, afterHandle and setNext like this:
Filter.beforeHandle() -> Router -> Filter.afterHandle()
The problem is, that I'm using Spring configured RESTlet and I don't know if the regular Filter will work correctly with SpringRouter from org.restlet.ext.spring package. My current restlet configuration is as follows:
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="root" class="org.restlet.ext.spring.SpringRouter">
<property name="attachments">
<map>
<entry key="/login">
<bean class="org.restlet.ext.spring.SpringFinder">
<lookup-method name="create"
bean="loginResource" />
</bean>
</entry>
</map>
</property>
</bean>
</beans>
I'm thinking about adding a bean with id root and class that extends class Filter, and pass to it as property next a bean with id router (which currently is called root). What do you think about this solution?
Mixing Restlet classes from the Spring extension with other one shouldn't be an issue. Spring* classes only provide additional configurability.

What happens behind the scenes when a property placeholder is defined in a Spring context (JNDI-wise)?

I have a Java web application using Spring and JSF, deployed in Tomcat.
For the record, in web.xml, only the Faces servlet is configured; there is no Spring servlet.
The entry point to my Spring context is applicationContext.xml, which looks like this:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:aop="http://www.springframework.org/schema/aop" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.1.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.1.xsd
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-3.1.xsd">
<context:component-scan base-package="my.pack" annotation-config="true" />
<context:property-placeholder location="file:${admin.config.path}/database.properties"/>
<context:spring-configured />
<context:annotation-config />
<aop:aspectj-autoproxy />
</beans>
In the property-placeholder definition one can see that I'm injecting an external property, which, in this case, is an environment variable. This property is passed via Tomcat context configuration, like this:
<?xml version="1.0" encoding="UTF-8"?>
<Context antiJARLocking="true" path="/cntxt">
<Environment name="admin.config.path" value="d:/conf/"
type="java.lang.String" override="false"/>
</Context>
I'm also injecting this environment variable in one of my Spring-managed beans, like this:
#Value("${java:comp/env/admin.config.path}")
private String confDir;
(This is because I'm also using that directory for some other configuration files, which are not .properties files).
With the configuration above, everything works just fine - all the wirings are performed without any flaw.
But, at some moment, I stopped needing injecting properties via the property placeholder configurer from database.properties (but I still need the other files from the admin.config.path), therefore I removed the <context:property-placeholder> line from applicationContext.xml.
At that moment, the injection using #Value stopped working in my Spring-managed bean.
Does anybody know what actually happened? It seems like the JNDI injection fails if the property placeholder configurer is not defined in the Spring context.
Is there any way I can re-enable this kind of injection (without defining a dummy property placeholder pointing to an empty properties file)?
You need a property placeholder configurer instance, the sole purpose of it is to replace placeholders with actual values. If you remove it no placeholders will be replaced.
Either add a <context:property-placeholder /> and leave out the location field or add a bean of the type PropertySourcesPlaceholderConfigurer. Either way will re-enable your placeholder support.
Small suggestion remove the java:comp/env part of the property, by default it will do a JNDI lookup if the property cannot be resolved in other locations (servlet context, properties, system properties etc.).

Inconsistent NoSuchBeanDefinitionException since upgrading from Spring 3.0 to Spring 3.1

We've been seeing inconsistent NoSuchBeanDefinitionException since upgrading from Spring 3.0 to Spring 3.1. It happens to only about 2% of our hosts and even then the problem is not consistent in a single host since it might not happen after restarting the same server.
Here's the error :
nested exception is org.springframework.beans.factory.BeanCreationException: Could
not autowire field: private com.google.common.util.concurrent.ListeningExecutorService
com.amazon.ms3.container.impl.ExecutionEnvironmentImpl.functionThreadPool;
nested exception is org.springframework.beans.factory.NoSuchBeanDefinitionException:
No matching bean of type [com.google.common.util.concurrent.ListeningExecutorService]
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),
#org.springframework.beans.factory.annotation.Qualifier(value=functionThreadPool)}
| at org.springframework.beans.factory.annotation
.AutowiredAnnotationBeanPostProcessor.postProcessPropertyValues...
Here's the bean code (irrelevant code removed):
public class ExecutionEnvironmentImpl extends ExecutionEnvironment {
#Autowired
#Qualifier("functionThreadPool")
private ListeningExecutorService functionThreadPool;
public ExecutionEnvironmentImpl() {
}
public void setFunctionThreadPool(ListeningExecutorService functionThreadPool) {
this.functionThreadPool = functionThreadPool;
}
}
And here's the configuration file (irrelevant configuration removed):
<?xml version="1.0" encoding="UTF-8"?>
<beans
xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:util="http://www.springframework.org/schema/util"
xsi:schemaLocation="
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util.xsd
">
<bean id="executionEnvironment" class="com.amazon.ms3.container.impl.ExecutionEnvironmentImpl" scope="prototype"/>
<!-- ThreadPool for executing tenant functions asynchronously -->
<bean id="functionThreadPool" class="com.google.common.util.concurrent.MoreExecutors" factory-method="listeningDecorator">
<constructor-arg>
<bean class="java.util.concurrent.Executors" factory-method="newCachedThreadPool"/>
</constructor-arg>
</bean>
</beans>
The Qualifier annotation isn't much of use anymore since we don't have other ListeningExecutorService defined but I don't believe it should be causing any problem.
Any ideas of what might be causing this? I've been thinking about removing the autowiring completely but I'd like to understand why this is happening in the first place.
Thanks!
This is possibly some timing-related issue in the order that Spring brings up beans in the application context. It may be worth explicitly telling Spring to create the functionThreadPool first to ensure that it is available for autowiring into the executionEnvironment. To do that, you can use the depends-on attribute:
<bean id="executionEnvironment"
class="com.amazon.ms3.container.impl.ExecutionEnvironmentImpl"
scope="prototype"
depends-on="functionThreadPool"/>
<!-- ThreadPool for executing tenant functions asynchronously -->
<bean id="functionThreadPool" class="com.google.common.util.concurrent.MoreExecutors" factory-method="listeningDecorator">
<constructor-arg>
<bean class="java.util.concurrent.Executors" factory-method="newCachedThreadPool"/>
</constructor-arg>
</bean>
</beans>
If you intend to use autowiring then you should have the <context:annotation-config> element in the configuration also - so that Spring knows to autowire the functionThreadPool into the executionEnvironment.
After trying depends-on and even wiring the bean directly like shown below :
<bean id="executionEnvironment" class="com.amazon.ms3.container.impl.ExecutionEnvironmentImpl" scope="prototype">
<property name="functionThreadPool" ref="functionThreadPool"/>
</bean>
<!-- ThreadPool for executing tenant functions asynchronously -->
<bean id="functionThreadPool" class="com.google.common.util.concurrent.MoreExecutors" factory-method="listeningDecorator">
<constructor-arg>
<bean class="java.util.concurrent.Executors" factory-method="newCachedThreadPool"/>
</constructor-arg>
</bean>
we were still seeing the same error on some occasions. The only thing that fixed it was to remove the #Autowired annotation in the ExecutionEnvironmentImpl class.
We didn't really fix the problem but more like avoiding it. I wish I could provide a better answer.

Spring MVC annotation error "No adapter for handler - Does your handler implement a supported interface like Controller?"

I have an existing Spring MVC apps written in 2.5.
I wanted to use the new annotation controller. I somewhat see that it is really flexible and would answer my other needs.
My problem is, it seems I cannot mix them both.
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-2.5.xsd">
<context:component-scan
base-package="com.test.web" />
<bean class="org.springframework.web.servlet.mvc.annotation.DefaultAnnotationHandlerMapping" />
<bean class="org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter" />
<!-- Controller Mappings Here -->
<bean id="homeController" class="com.test.web.HomeController">
<property name="cacheSeconds" value="120" />
</bean>
//other plain old spring mvc controller
When I ran my app and hit the home page, I get below error:
javax.servlet.ServletException: No adapter for handler [com.test.web.HomeController#cca07b]: Does your handler implement a supported interface like Controller?
I am not sure but I think something is conflicting. This is a fairly large Spring MVC apps and I don't want to change those modules that were working already using the old Spring Base Controller.
My goal is to only use the annotation controller on my new enhancement.
You don't need to declare DefaultAnnotationHandlerMapping and AnnotationMethodHandlerAdapter. The context has these registered by default, along with adapters for old-style controllers.
When you declare them explicitly like this, the default ones are removed, and the context will only support the ones you declare.

Categories

Resources