Spring: Multiple controller instances of same class - java

JDK Version: 1.7 (latest update)
Spring: 3.2.16-Release
I have a generic controller class, that can be reused for multiple functionality. Due to the limitations of annotation-based approach for such requirements, I am using the XML-based configuration. Also, I have disabled the component scan in XML.
I have configured multiple bean instances of the same class and used SimpleUrlHandlerMapping for mapping URLs to controller. If I test the project with one controller enabled at a time, it works fine. However, when I enable the second instance, spring complains with following error:
ERROR: org.springframework.web.servlet.DispatcherServlet - Context initialization failed
org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping#0': Invocation of init method failed; nested exception is java.lang.IllegalStateException: Ambiguous mapping found. Cannot map 'deviceController' bean method
public java.lang.String com.smvc.pr05.controllers.SearchController.search(java.util.Locale,org.springframework.ui.ModelMap)
to {[],methods=[POST],params=[],headers=[],consumes=[],produces=[],custom=[]}: There is already 'searchController' bean method
public java.lang.String com.smvc.pr05.controllers.SearchController.search(java.util.Locale,org.springframework.ui.ModelMap) mapped.
...
Caused by: java.lang.IllegalStateException: Ambiguous mapping found. Cannot map 'installerController' bean method
public java.lang.String com.smvc.pr05.controllers.SearchController.search(java.util.Locale,org.springframework.ui.ModelMap)
to {[],methods=[POST],params=[],headers=[],consumes=[],produces=[],custom=[]}: There is already 'deviceController' bean method
public java.lang.String com.smvc.pr05.controllers.SearchController.search(java.util.Locale,org.springframework.ui.ModelMap) mapped.
...
I have tried it with scope=singleton and scope=prototype for the controller bean definition. I have tried with enabling component scan (keeping manually defined bean in XML) and disabling the same. The error persists.
While this may be fixed, if I create concrete class per instance, I really want to keep it as last option. I have a strong belief in Spring capabilities, as I have used similar technique for non-controller classes.
Please let me know, what is that I am missing.
The spring configuration (EDITED with controller as singleton)
...
<beans:bean class="org.springframework.web.servlet.handler.SimpleUrlHandlerMapping">
<beans:property name="mappings">
<beans:props>
<beans:prop key="/">homeController</beans:prop>
<beans:prop key="/deviceSearch/">deviceController</beans:prop>
<beans:prop key="/installerSearch/">installerController</beans:prop>
<beans:prop key="/customerSearch/">customerController</beans:prop>
</beans:props>
</beans:property>
</beans:bean>
...
<beans:bean id="homeController" class="com.smvc.pr05.controllers.HomeController" >
</beans:bean>
<beans:bean id="deviceController" class="com.smvc.pr05.controllers.SearchController">
<beans:property name="metaModel" ref="deviceModel"/>
<beans:property name="searchService" ref="deviceService" />
</beans:bean>
<beans:bean id="installerController" class="com.smvc.pr05.controllers.SearchController" >
<beans:property name="metaModel" ref="installerModel"/>
<beans:property name="searchService" ref="installerService" />
</beans:bean>
<beans:bean id="customerController" class="com.smvc.pr05.controllers.SearchController" >
<beans:property name="metaModel" ref="customerModel"/>
<beans:property name="searchService" ref="customerService" />
</beans:bean>
The Java Controller Class:
...
#Controller
public class SearchController {
private static final Logger LOG = LoggerFactory.getLogger(SearchController.class);
private SearchService searchService; //Has explicit set() method
private MetaModel metaModel; //Has explicit set() method
#SuppressWarnings({ "unchecked" })
#RequestMapping(method = RequestMethod.POST)
public String search(Locale locale, ModelMap modelMap) {
...
}
public void setSearchService(SearchService searchService) {
this.searchService = searchService;
}
public void setMetaModel(MetaModel metaModel) {
this.metaModel = metaModel;
}
}

The main issue is that when using #Controller and <mvc:annotation-driven /> is that the RequestMappingHandlerMapping and RequestMappingHandlerAdapter will kick in. The first will detect all #Controller annotated beans and based on the #RequestMapping create a mapping for it.
As you have registered 3 beans of the same type it will result in 3 of the same mappings and thus it will stop with an exception telling you that. Basically with the introduction of RequestMappingHandlerAdapter/RequestMappingHandlerMapping the ability to use a SimpleUrlHandlerMapping and an annotation way of selecting the method was lost.
You could however remove the <mvc:annotation-driven /> and add the AnnotationMethodHandlerAdapter however that class is more or less deprecated (and will at least be removed in future versions of Spring).
I would suggest to use the old trusty Controller interface instead of the annotation. You only have a single method you want to use and hence using the old support classes is a viable option.
public class SearchController extends AbstractController {
private static final Logger LOG = LoggerFactory.getLogger(SearchController.class);
private SearchService searchService; //Has explicit set() method
private MetaModel metaModel; //Has explicit set() method
protected ModelAndView handleRequestInternal(HttpServletRequest request, HttpServletResponse response) throws Exception;
if (!("post".equalsIgnoreCase(request.getMethod()))) {
return null; // or throw exception or ....
}
final Locale locale = LocaleContextHolder.getLocale(); // retrieve current locale.
ModelAndView mav = new ModelAndView("your-view");
// prepare your model instead of adding to ModelMap
mav.addObject("name", object);
return mav;
}
// Omitted setters.
}
This will prevent the annotation scanning from kicking in and saves you from refactoring (again) when you upgrade to a version of Spring that removed the deprecated classes.

Seems to be component scan still working. Because somebody created instance of SearchController depending on #Controller annotation. That is why you get Cannot map 'deviceController' bean method.
Another problem, if you use <mvc:annotation-driven/> in xml config, mvc engine will look for all beans marked with #Controller annotation, and will attempt to map this beans depending on methods annotation. Because you have three controllers of same class, and this class marked with #Controller, mvc engine will try to map all of this controllers. Since they will have same methods annotation they will be mapped to same path (in your case it is empty path). That is why you get Cannot map 'installerController' bean method.
Solution for both cases: remove #Controller annotation from SearchController class.

The Controller is just a stereotype annotation which works in conjuction with component scanning. THe culprit here is #RequestMapping which maps all the methods to the same url. For your configuration to work remove <mvc:annotation-driven/> element which registers a RequestMappingHandlerMapping bean which uses #RequestMapping for url mapping. Now your SimpleUrlHandlerMapping will be used instead of the one configured through mvc:annotation-driven or #EnableWebMvc
However you will need to register a HandlerAdapter which knows how to handler #RequestMapping methods i.e org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter
as follows
<bean class="org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter"/>

Related

SpringFramework: set bean name programmatically

For springframwork based application, when using xml to declare beans, bean id can be configured by passing a unique value and even a parameter and then solve the value at runtime.
Now I hope to replace all xml configuration to java annotation.
Say I want to create two database beans with different id.
bean.xml
<bean id="A.database" class="org.apache.commons.dbcp.BasicDataSource">
<!-- collaborators and configuration for this bean go here -->
</bean>
<bean id="B.database" class="org.apache.commons.dbcp.BasicDataSource">
<!-- collaborators and configuration for this bean go here -->
</bean>
Then I optimize the upper code to one bean with two property file for two application
bean.xml
<bean id="${appName.database}" class="org.apache.commons.dbcp.BasicDataSource">
<!-- collaborators and configuration for this bean go here -->
</bean>
applicationA.properties
appName.database=A.database
applicationB.properties
appName.database=B.database
The whole application is composed of "framework" module which provides beans common for each application, like database bean, jdbcTemplate bean, and "application" module which provides property value for placeholder and initializes beans with unique id. So even if I start multiple application at the same time, they will find corresponding bean from the context.
Generally speaking, I hope to do
#Bean(name = "${beanName}")
public ABean getBean() {}
and resolve ${beanName} at application level.
By reading SpringFramwork document, I found the answer: BeanNameGenerator
NameGenerator.class
public class NameGenerator implements BeanNameGenerator{
#Override
public String generateBeanName(BeanDefinition definition,
BeanDefinitionRegistry registry) {
if(definition.getBeanClassName().contains("Toto")) {
return "toto";
}
return return definition.getBeanClassName();
}
}
AppConfiguration.class
#Configuration
#ComponentScan(basePackages = {"com.example.domain"}, nameGenerator = NameGenerator.class)
public class Config {
}
Domain class with #Component
#Component
public class Toto {
private int id;
}
BootApplication with domain bean name : toto
#SpringBootApplication
public class DemoApplication {
public static void main(String[] args) {
ConfigurableApplicationContext ctx = SpringApplication.run(
DemoApplication.class, args);
for (String name : ctx.getBeanNamesForType(Toto.class)) {
System.out.println(name);
}
}
}
If you want to follow that type of approach create multiple configuration classes you annotated with different Spring profiles.
At start up you can pass a parameter on which profile to use and hence what beans to load within the associated profile.
A more efficient way to do it is use the same property naming convention across all application .properties files. Set a parameter placeholder for the file name which resolves to a JVM arg passed at runtime which is loaded by the#PropertySource annotation.
There's no need to have duplicate beans defined for different environments if it's just properties that are changing.

Service bean not autowired properly in Spring Rest Controller

I have a spring rest application which has a Rest Controller as below
#RestController
public class IngestorController
{
#Autowired
private IngestorService ingestorService;
#RequestMapping( value = "/ingestor/createAndDeploy/{ingestorName}", method = RequestMethod.POST )
public void createAndDeploy( #PathVariable String ingestorName )
{
ingestorService.createAndDeploy( ingestorName );
}
}
Simlilarly I have a Service Bean as below
#Service
public class IngestorService
{
#Autowired
private IngestorCommandBuilder ingestorCommandBuilder;
private String uri;
private DeployTemplate deployTemplate;
public void init() throws URISyntaxException
{
deployTemplate = new DeployTemplate( new URI( uri ) );
}
#Transactional
public void createAndDeploy( Ingestor ingestor )
{
//.....
}
}
I have the Spring config as show below
<bean id="ingestorCommandBuilder" class="org.amaze.server.ingestor.IngestorCommandBuilder" />
<bean id="ingestorService" class="org.amaze.server.service.IngestorService" init-method="init">
<property name="uri" value="http://localhost:15217" />
</bean>
<bean id="ingestorController" class="org.amaze.server.controller.IngestorController"/>
When ever I try to start the application context the application context starts and it hits the init method in the IngestorService, deployTemplate object also initilized for the service bean.
But this bean is not autowired for the IngestorController. When I hit the rest endpoint from postman, the service bean has deployTemplate property as null.. The object that is assigned to the ingestorService variable in the Controller is a different object not the one which was called for the init method...
I tried making the service bean singleton (Even if the default scope is singleton) but dint work...
I am not able to find out the mistake I am doing.. Any suggestions appreciated...
If you use annotation-based configuration, you mostly dont need to describe all your beans in application context xml file. Annotations are all you need to autowire service.
To define your init method properly, use #PostConstruct annotation. Properties can be easily moved to externat .properties file and injected to your code with #Value annotation.
Alternatively, use #Qualifier with #Autowired.
First ensure that you have:
<context:annotation-config />
In your Spring config. Now you have severael alternatives:
<context:component-scan base-package="your.package.com" />
to scan for components, or
<!-- autowiring byName, bean name should be same as the property name annotate your ingestorservice with #Service("ingestorServiceByName") -->
<bean name="ingestorServiceByName" class="your.package.com.IngestorService" autowire="byName" />
<!-- autowiring byType, tif there are more bean of the same "general" type, this will not work and you will have to use qualifier or mark one bean as #Primary -->
<bean name="ingestorServiceByType" class="your.package.com.IngestorService" autowire="byType" />
<!-- autowiring by constructor is similar to type, but the DI will be done by constructor -->
<bean name="ingestorServiceConstructor" class="your.package.com.IngestorService" autowire="constructor" />
Please include your full Spring configuration to make it easier to analyze your problem.
When you are using Annotation based DI, you need not define the beans in XML.
#PostConstruct can be used to replace your init-method of xml config.
Just use
<context:component-scan base-package="..."/> <mvc:annotation-driven/>

How to get a reference to SessionAuthenticationStrategy without configuring the strategy explicit?

In a Spring Security 3.2 based application I have a explicit configured UsernamePasswordAuthenticationFilter, that need an reference to the sessionAuthenticationStrategy (in order to invoke .onAuthentication).*
The sessionAuthenticationStrategy is the default one created by <security:http> (HttpSecurityBeanDefinitionParser).
My question: Is how can I get an reference to the SessionAuthenticationStrategy without configuring the complete SessionAuthenticationStrategy explicite, so that I can inject this reference in XML configuration?
<security:http auto-config="false" use-expressions="true"
entry-point-ref="loginUrlAuthenticationEntryPoint"
access-decision-manager-ref="httpAccessDecisionManager">
...
<security:custom-filter
ref="usernamePasswordAuthenticationFilter"
position="FORM_LOGIN_FILTER"/>
...
</security:http>
...
<bean id="usernamePasswordAuthenticationFilter"
class=" o.s.scurity.web.authentication.UsernamePasswordAuthenticationFilter">
<property name="sessionAuthenticationStrategy" ref="????"> <!-- ?? ->
...
</bean>
*my real UsernamePasswordAuthenticationFilter is a customized subclass, but that should not matter for this question
I have had a look at the HttpSecurityBeanDefinitionParser (and the HttpConfigurationBuilder.createSessionManagementFilters()) that is the class responsible to parse the security:http tag and for creating of SessionAuthenticationStrategy bean.
Therefore I know that Spring Security 3.2.5.RELEASE create (in my configuration) a CompositeSessionAuthenticationStrategy bean and uses this as session strategy. This bean will get the default name: org.springframework.security.web.authentication.session.CompositeSessionAuthenticationStrategy#0
So my current workaround is to have a reference to this bean, by its name:
<bean id="usernamePasswordAuthenticationFilter"
class=" o.s.scurity.web.authentication.UsernamePasswordAuthenticationFilter">
<property name="sessionAuthenticationStrategy">
<ref
bean="org.springframework.security.web.authentication.session.CompositeSessionAuthenticationStrategy#0"/>
</property>
...
</bean>
This workaround has some serious limitations:
when a newer version of spring security works in an other way (creating an other bean) then it will fail.
when there is an other CompositeSessionAuthenticationStrategy thats name is created with ReaderContext.generateBeanName then this approach may fail, because of #0 maybe become #1 (depends on the order in which the beans are created)
I'm afraid there is no obvious way to get it.
But all the examples in Spring-Security reference manual are coherent on that : you should not even want to get it : all show an explicit SessionAuthenticationStrategy injected in the UserNamePasswordAuthenticationFilter and if appropriate in the SessionManagementFilter.
According to the javadocs of these 2 classes, the default SessionAuthenticationStrategy are :
SessionFixationProtectionStrategy for Servlet < 3.1
ChangeSessionIdAuthenticationStrategy for Servlet 3.1+
So the correct way is to create a bean implementing SessionAuthenticationStrategy either one of the above defaults, or another implementation if you have special needs and use it wherever you need to.
Of course, it is always possible to use reflection to access private members of Spring security implementation classes, but you know it is bad and comes with high risk of getting broken on next release of Spring security.
When working with JavaConfig (I'm afraid is not your case) you can get a reference by doing
http.getConfigurer(SessionManagementConfigurer.class).init(http);
http.getSharedObject(SessionAuthenticationStrategy.class);
Expanding on Ralph's answer, you can use a FactoryBean to get a reference to the AuthenticationStrategy.
public class SessionAuthenticationStrategyFactoryBean implements BeanFactoryAware, FactoryBean<SessionAuthenticationStrategy> {
private BeanFactory beanFactory;
#Override
public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
this.beanFactory = beanFactory;
}
#Override
public SessionAuthenticationStrategy getObject() throws Exception {
final CompositeSessionAuthenticationStrategy sas = beanFactory.getBean(CompositeSessionAuthenticationStrategy.class);
return sas;
}
#Override
public Class<?> getObjectType() {
return SessionAuthenticationStrategy.class;
}
#Override
public boolean isSingleton() {
return true;
}
}
... and make it available on you XML configuration:
<bean id="sas" class="com.example.SessionAuthenticationStrategyFactoryBean" />
<bean id="usernamePasswordAuthenticationFilter"
class=" o.s.scurity.web.authentication.UsernamePasswordAuthenticationFilter">
<property name="sessionAuthenticationStrategy" ref="sas">
...
</bean>

ways to inject a object of a class in spring controller?

I need to inject a object of a java class in spring controller through applicaionContext.xml. My controller will be ,
#Controller
public class SpringController{
private MyClass obj;
}
I know I can do it with #Autowired annotation.
Is this really good to create a object for a controller through applicaionContext.xml ? Also can I inject a object of a class in controller using the <property> tag inside a <bean> tag ?
Is this really possible ? or please forgive me if it is a stupid question.
I need to know the possible ways for how to inject a object of a class in Spring controller ?
You can of course use #Autowired annotation to autowire the relationships, which can reduce the need to define the properties and constructor arguments for the controller in your applicationContext.xml file. And also to add a dependency to a class, you don't need to modify the configuration files.
But it has some disadvantages too, like if you use #Autowired, there will not be any explicit documentation for the wiring details between Spring managed beans. And to know the relationships between the beans, you have to go through your managed beans. But, if you use configuration files to define the relationships, the relationship details can be found in one place.
You can inject an object of a class into your controller through your applicaionContext.xml as below:
Constructor based injection:
#Controller
public class SpringController{
private MyClass obj;
public SpringController(MyClass obj){
this.obj=obj;
}
}
<bean id="myClassImpl" class="x.y.z.MyClassImpl"></bean>
<bean id="springController" class="x.y.z.web.controllers.SpringController">
<constructor-arg ref="myClassImpl"></constructor-arg>
</bean>
Setter based injection:
#Controller
public class SpringController{
private MyClass obj;
public void setObj(MyClass obj){
this.obj=obj;
}
public MyClass getObj(){
return obj;
}
}
<bean id="myClassImpl" class="x.y.z.MyClassImpl"></bean>
<bean id="springController" class="x.y.z.web.controllers.SpringController">
<property name="obj" ref="myClassImpl"></property>
</bean>
If you want to inject an object in a controller and you particularly want to you use xml,then instead of component scanning of Controller you should create a bean of the controller class of singleton scope in the application context.
Your controller class need not be annotated with #Controller.
you then have to you extend some Controller also like AbstractCommandController, AbstractController, AbstractFormController, AbstractWizardFormController, BaseCommandController, CancellableFormController, MultiActionController SimpleFormController, UrlFilenameViewController
Now to inject a particular object you can use Either Constructor and Setter based injection.
or you can use Autowring by name or type to auto inject the object.
Make sure that you have also declared the bean of that object also in Application Context.
After a DispatcherServlet has received a request and has done its work to resolve locales, themes and suchlike, it then tries to resolve a Controller, using a HandlerMapping. When a Controller has been found to handle the request, the handleRequest method of the located Controller will be invoked; the located Controller is then responsible for handling the actual request and - if applicable - returning an appropriate ModelAndView.
Thats it.
Actually, injection with xml and annotation is same behind the scene. Xml is old fashion while annotations are newer.
Basically, there are 2 types of injection types.
byName
Autowiring by property name. Spring container looks at the properties
of the beans on which autowire attribute is set to byName in the XML
configuration file. It then tries to match and wire its properties
with the beans defined by the same names in the configuration file.
You can give explicit names to beans both with xml and annotation.
#Service("BeanName")
#Component("BeanName")
#Controller("BeanName")
<bean name="BeanName" class="someclass"></bean>
and inject beans by using #Qualifier annotation.
#Autowired
#Qualifier("BeanName")
and with xml
<bean id="MyBean2" class="MyBean2 class">
<property name="Property of MyBean2 which refers to injecting bean" ref="BeanName" />
</bean>
byType
Autowiring by property datatype. Spring container looks at the
properties of the beans on which autowire attribute is set to byType
in the XML configuration file. It then tries to match and wire a
property if its type matches with exactly one of the beans name in
configuration file. If more than one such beans exists, a fatal
exception is thrown.
Default auto wiring mode is byType, so spring will look for matching type in auto wiring. However, older versions of Spring has default behavior none on injection. If you want to inject byType using xml, you should tell spring contaioner explicitly.
For example MyBean2 has a reference to MyBean, by setting autowired attribute to byType it handles injection automatically.
<bean id="MyBean" class="MyBean class">
<property name="Property of MyBean2 which refers to injecting bean" ref="BeanName" />
</bean>
<bean id="MyBean2" class="MyBean2 class"
autowire="byType">
</bean>
It also depends on where the injection take place in your code. There are 2 types, setter getter injection and constructor injection.
Note : There is no difference in #Controller since they are already in spring context.
See also
Spring Beans Auto wiring
I ran into such problem. I was getting "Ambiguous mapping found". (I use xml configuration as well and i am injecting a bean into my controller)
Then looking at my console i realized that my controller was being instantiated twice.
In more detailed look i noticed that my annotation
#Controller(value = "aController")
(Note value = "aController")
was different from my xml configuration where i was instatiating the same controller with different bean id
<bean id="aControleRRRRR" class="package.ControllerClassName"
p:property-ref="beanToInject" />
(Note id="aControleRRRRR")
So in conclusion your #Controller name (value = "aController") needs to be exactly the same as the name you give in the XML configuration (id="aControleRRRRR"), so that Spring can manage to distinct that they refer to the same bean (instance)
Hope this helps

SpringMVC: Variables in Annotations

I have the following controller defined:
#Controller
#RequestMapping("/test")
public class MyController extends AbstractController
{
#Autowired
public MyController(#Qualifier("anotherController") AnotherController anotherController))
{
...
}
}
I'm wondering if it's possible to use variables in the #Qualifier annotation, so that I can inject different controllers for different .properties files, e.g.:
#Controller
#RequestMapping("/test")
public class MyController extends AbstractController
{
#Autowired
public MyController(#Qualifier("${awesomeController}") AnotherController anotherController))
{
...
}
}
Whenever I try I get:
org.springframework.beans.factory.NoSuchBeanDefinitionException:
No matching bean of type [com.example.MyController] found for dependency:
expected at least 1 bean which qualifies as autowire candidate for this
dependency. Dependency annotations:
{#org.springframework.beans.factory.annotation.Qualifier(value=${awesomeController})
I've included the following bean in my config.xml file:
<bean id="propertyConfigurer"
class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
<property name="locations">
<list>
<value>classpath:config/application.properties</value>
</list>
</property>
</bean>
But the bean doesn't work unless I declare the bean explicitly in the xml file.
How do I do this with annotations??
First I think it is bad practice to make the dependency injection rely on configuration properties. You are probably going a wrong direction trying to do this.
However to answer your question: accessing placeHolder properties requires the dependency injection to be finished. To make sure it is, you can put your code that accesses the property inside a #PostContruct annotated method.
You will need to retrieve the bean manually from the applicationContext using getBean() method.
#Value("${awesomeController}")
private String myControllerName;
#PostConstruct
public void init(){
AnotherController myController = (AnotherController) appContext.getBean(myControllerName);
}
I'm not sure if what you're doing is possible but I can suggest a slightly different approach, but only if you're using Spring 3.1+. You could try using Spring Profiles.
Define the different controllers you want, one per profile:
<beans>
<!-- Common bean definitions etc... -->
<beans profile="a">
<bean id="anotherController" class="...AnotherController" />
</beans>
<beans profile="b">
<!-- Some other class/config here... -->
<bean id="anotherController" class="...AnotherController"/>
</beans>
</beans>
Your Controller would lose the #Qualifier and become something like:
#Autowired
public MyController(AnotherController anotherController) {
...
}
Then at runtime you can specify which controller bean you want to use by activating the corresponding profile using a system property, e.g.:
-Dspring.profiles.active="a"
or:
-Dspring.profiles.active="b"
It may be possible to set profiles based on a property file but you can find out more about Spring Profiles from this post on the Spring blog. I hope that helps somewhat.

Categories