Ambiguous mapping found with ControllerClassNameHandlerMapping - spring

How to map requests to methods without explicit annotations on top of methods? For instance, the following request:
http://somedomain:8080/SampleSpring/access/loginFailed
should be mapped to
"loginFailed" method of "AccessController" without the need of explicit annotation on method like:
#RequestMapping("/access/loginFailed")
Here is my spring configuration:
<context:component-scan base-package="com.robikcodes.samplespring"/>
<mvc:annotation-driven/>
<bean class="org.springframework.web.servlet.mvc.support.ControllerClassNameHandlerMapping">
<property name="basePackage" value="com.robikcodes.samplespring.controller"/>
<property name="caseSensitive" value="true"/>
<property name="defaultHandler">
<bean class="org.springframework.web.servlet.mvc.UrlFilenameViewController"/>
</property>
</bean>
<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<property name="prefix" value="/WEB-INF/views/"/>
<property name="suffix" value=".jsp"/>
</bean>
Here is my controller:
#Controller
public class AccessController{
#RequestMapping(method = RequestMethod.GET)
public void login(ModelMap m) {}
#RequestMapping(method = RequestMethod.GET)
public String loginFailed(ModelMap m) {
m.addAttribute("error", "true");
return "access/login";
}
#RequestMapping(method = RequestMethod.GET)
public String logout(ModelMap m) {
m.addAttribute("logoutStatus","true");
return "access/login";
}
}
I got the following error (seems like only login method was mapped properly):
org.apache.catalina.LifecycleException: 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 'accessController' bean method
public java.lang.String com.robikcodes.samplespring.controller.AccessController.logout(org.springframework.ui.ModelMap)
to {[],methods=[GET],params=[],headers=[],consumes=[],produces=[],custom=[]}: There is already 'accessController' bean method
public void com.robikcodes.samplespring.controller.AccessController.login(org.springframework.ui.ModelMap) mapped.

You're using ControllerClassNameHandlerMapping with an assumption that is not correct; from Java doc:
Implementation of HandlerMapping that follows a simple convention for
generating URL path mappings from the class names of registered
Controller beans as well as #Controller annotated beans.
The documentation does not say that it also follows method names. The main reference of comparing "handler mappings" for your controller is the #RequestMapping annotations put on your methods. So, with your controller Spring reads them as:
{methods=[GET],params=[],headers=[],consumes=[],produces=[],custom=[]}
for all the defined methods in AccessController that has the following #RequestMapping:
#RequestMapping(method = RequestMethod.GET)
That's why you see the ambiguous exception.
To my understanding, the cleanest solution is to use value attribute of #RequestMapping to define different request URIs. It's not really recommended to go for a solution that tries to map request URIs to method names.

Related

The DispatcherServlet configuration needs to include a HandlerAdapter that supports this handler

I wanted to use both annotation mapping and xml mapping in Spring MVC. My application-context.xml as follows:
<bean class="org.springframework.web.servlet.handler.SimpleUrlHandlerMapping">
<property name="mappings">
<props>
<prop key="personal/account/history">accountHistoryController</prop>
</props>
</property>
</bean>
<bean id="accountHistoryController" class="com.fg.banking.ib.controller.AccountHistoryController" />
<bean
class="org.springframework.web.servlet.mvc.SimpleControllerHandlerAdapter"></bean>
<context:annotation-config />
<mvc:annotation-driven />
<context:component-scan base-package="com.fg.banking.ib.controller, com.fg.banking.ib.helper, com.fg.banking.corporate.controller" />
I am getting the following error when I try to access the url. I have configured the SimpleControllerHandlerAdapter as above.
javax.servlet.ServletException: No adapter for handler
[com.fg.banking.ib.controller.AccountHistoryController#218531e6]: The DispatcherServlet configuration needs to include a HandlerAdapter that supports this handler
org.springframework.web.servlet.DispatcherServlet.getHandlerAdapter(DispatcherServlet.java:1128)
org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:903)
org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:856)
org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:936)
org.springframework.web.servlet.FrameworkServlet.doGet(FrameworkServlet.java:827)
What to do?
This error also occurs when you define a restController but forget to define the requestMapping.
#RestController
#RequestMapping("/api/orders") // <---- dont't forget the requestMapping
This problem occurred for me when I tried to define RestController path by using in this way:
#RestController("/test")
public class TestController {}
In the above section, the meaning of this declaration is different. Here actually "/test" is defined as bean name rather than path for the controller.
After defining the path in this way it worked for me:
#RestController
#RequestMapping("/test")
public class TestController {}
I resolved the issue. I forgot to add the #Controller annotation in controller class. There are fore we can use the both methods(annotation mapping & XML mapping) together in an application.
Try adding the following as a handler mapper(Worked for me):
<bean id="HandlerMapping" class="org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping"/>
Make sure you have implemented Controller in your controller classes and overrided handleRequest method.
Here our controller class should extends
import org.springframework.web.servlet.mvc.AbstractController;
public class AppController extends AbstractController{ }
Here we need to implement the abstract method as :
protected modelandview handleRequestInternal(HttpServletRequest arg0, HttpServletResponse arg1) throws Exception { return null; }

Get session proxy bean in JSP

I have a session bean like this:
#Component
#Scope(value="session", proxyMode = ScopedProxyMode.TARGET_CLASS)
public class MySession { ... }
How can I enable access to this bean in JSP?
I displayed session data in JSP and I got this:
org.springframework.web.context.request.ServletRequestAttributes.DESTRUCTION_CALLBACK.scopedTarget.mySession
scopedTarget.mySession
So, I tried using ${scopedTarget.mySession.qualites}, but it didn't work.
If you expose spring beans as context properties using this view resolver configuration, then it should be possible to access the bean with a value expression such as #{mySession.qualites} directly:
<bean id="viewResolver" class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<property name="exposeContextBeansAsAttributes" value="true"/>
</bean>
As an alternative, expose the spring bean to the view model, by using in the #Controller the #ModelAttribute annotation.

Spring dependency injection issue

I have a bean I am trying to configure in Spring context using Constructor injection. When I pass subclass for one of the constructor arguments, the bean is instantiated by Spring container only if I do not specify the "type" attribute. Would anybody have any idea what's wrong? Below are more specifics.
class MyClass{
public MyClass(SomeAbstractBase absObject){
//do stuff
}
}
class ConcreteClass extends SomeAbstractBase{
//
}
Spring configs (First and second do not work but the third one using type attribute works)-
Config I-
<bean id="concreteclass"
class="ConcreteClass"/>
<bean id="myclass"
class="MyClass">
<constructor-arg type="ConcreteClass" ref="concreteclass"/>
</bean>
Config II-
<bean id="concreteclass"
class="ConcreteClass"/>
<bean id="myclass"
class="MyClass">
<constructor-arg type="SomeAbstractBase" ref="concreteclass"/>
</bean>
Config III-
<bean id="concreteclass"
class="ConcreteClass"/>
<bean id="myclass"
class="MyClass">
<constructor-arg ref="concreteclass"/>
</bean>
I get the following exception at initialization-
Exception in thread "main"
org.springframework.beans.factory.BeanCreationException: Error
creating bean with name 'jedispool' defined in class path resource
[cache-spring-config.xml]: Could not resolve matching constructor
(hint: specify index/type/name arguments for simple parameters to
avoid type ambiguities)
Why would neither of the first or second config work?
Thank you
Type argument accept only the full qualified type (because if not spring cannot determine exactly the package and the type will not match your class):
So you need to use the canonical name of your class to be a type
ConcreteClass => com.your.app.ConcreteClass
A class is not just a name, is a package + name (too be short)
I believe the error you had appeared because you had static nested classes. Here's an SSCCE
package test;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class Test {
public static void main(String[] args) throws Exception {
System.out.println(MyClass.class.getName());
System.out.println(ConcreteClass.class.getName());
System.out.println(SomeAbstractBase.class.getName());
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("spring.xml");
}
static class MyClass {
public MyClass(SomeAbstractBase absObject) {
}
}
static class ConcreteClass extends SomeAbstractBase {
//
}
static abstract class SomeAbstractBase {
}
}
With spring.xml containing
<bean id="concreteclass" class="test.Test.ConcreteClass" />
<bean id="myclass" class="test.Test.MyClass">
<constructor-arg type="test.Test.SomeAbstractBase" ref="concreteclass" />
</bean>
The above fails with a UnsatisfiedDependencyException. In this specific example, the class attribute isn't used correctly. The fully qualified class name for ConcreteClass is test.Test$ConcreteClass, not test.Test.ConcreteClass as I've specified. The same applies to MyClass and SomeAbstractBase.
However, specifying test.Test.ConcreteClass in the class attribute for the concreteclass bean doesn't fail because at some point during processing of bean declarations, Spring tries to resolve the class String into a Class object using Class.forName(String). It will call ClassUtils.forName(String, ClassLoader) to do this. Initially it will fail because there is no such class test.Test.ConcreteClass. However, this is done in a try-catch which on ClassNotFoundException will transform the String class name from test.Test.ConcreteClass to test.Test$ConcreteClass and try again. It will work and correctly create a bean of type test.Test$ConcreteClass for your concreteclass bean.
When it tries to create your myclass bean, however, it does not apply such logic for resolving which constructor to use and therefore cannot understand that with the type attribute value of test.Test.SomeAbstractBase, you actually meant test.Test$SomeAbstractBase, so it fails saying the type is ambiguous.
Change your bean declarations to correct types
<bean id="concreteclass" class="test.Test$ConcreteClass" />
<bean id="myclass" class="test.Test$MyClass">
<constructor-arg type="test.Test$SomeAbstractBase" ref="concreteclass" />
</bean>
and it will work.
Take a look at kakawait's answer, you need to specify the fully qualified class name for the bean you are trying to instantiate.

Is default constructor required in Spring injection?

I'm trying to inject a constructor that takes some arguments. After compiling Spring complains it couldn't find a default constructor (I haven't defined it) and throws BeanInstatiationException and NoSuchMethodException.
After defining a default constructor the exceptions don't appear anymore, however my object is never initialized with the argument constructor, only the default one is called. Does Spring really require a default constructor in this case? And if yes, how can I make it use the argument constructor instead of the default one?
This is how I wire everything:
public class Servlet {
#Autowired
private Module module;
(code that uses module...)
}
#Component
public class Module {
public Module(String arg) {}
...
}
Bean configuration:
<beans>
<bean id="module" class="com.client.Module">
<constructor-arg type="java.lang.String" index="0">
<value>Text</value>
</constructor-arg>
</bean>
...
</beans>
Stack trace:
WARNING: Could not get url for /javax/servlet/resources/j2ee_web_services_1_1.xsd
ERROR initWebApplicationContext, Context initialization failed
[tomcat:launch] org.springframework.beans.factory.BeanCreationException: Error
creating bean with name 'module' defined in URL [...]: Instantiation of bean failed;
nested exception is org.springframework.beans.BeanInstantiationException: Could not
instantiate bean class [com.client.Module]: No default constructor found; nested
exception is java.lang.NoSuchMethodException: com.client.Module.<init>()
Spring only "requires" a default constructor if you plan on instantiating it without any arguments.
for example, if your class is like this;
public class MyClass {
private String something;
public MyClass(String something) {
this.something = something;
}
public void setSomething(String something) {
this.something = something;
}
}
and you set it up in Spring like this;
<bean id="myClass" class="foo.bar.MyClass">
<property name="something" value="hello"/>
</bean>
you're going to get an error. the reason is that Spring instantiates your class new MyClass() then tries to set call setSomething(..).
so instead, the Spring xml should look like this;
<bean id="myClass" class="foo.bar.MyClass">
<constructor-arg value="hello"/>
</bean>
so have a look at your com.client.Module and see how its configured in your Spring xml
Most probably you are using component-scanning and since you define annotation #Component for class Module it tries to instantiate the bean. You do not need #Component annotation if You are using XML for bean definition.
Just faced the same problem, i guess till now you might have solved the problem.
Below is what you could have changed your bean configuration to,
<bean id="module" class="com.client.Module">
<constructor-arg value="Text"/>
</bean>

Inject a file resource into Spring bean

What is a good way to inject some file resource into Spring bean ?
Now i autowire ServletContext and use like below. Is more elegant way to do that in Spring MVC ?
#Controller
public class SomeController {
#Autowired
private ServletContext servletContext;
#RequestMapping("/texts")
public ModelAndView texts() {
InputStream in = servletContext.getResourceAsStream("/WEB-INF/file.txt");
// ...
}
}
Something like this:
#Controller
public class SomeController {
private Resource resource;
public void setResource(Resource resource) {
this.resource = resource;
}
#RequestMapping("/texts")
public ModelAndView texts() {
InputStream in = resource.getInputStream();
// ...
in.close();
}
}
In your bean definition:
<bean id="..." class="x.y.SomeController">
<property name="resource" value="/WEB-INF/file.txt"/>
</bean>
This will create a ServletContextResource using the /WEB-INF/file.txt path, and inject that into your controller.
Note you can't use component-scanning to detect your controller using this technique, you need an explicit bean definition.
Or just use the #Value annotation.
For single file:
#Value("classpath:conf/about.xml")
private Resource about;
For multiple files:
#Value("classpath*:conf/about.*")
private Resource[] abouts;
What do you intend to use the resource for? In you example you don't do anything with it.
From it's name, however, it looks like you are trying to load internationalisation / localisation messages - for which you can you a MessageSource.
If you define some beans (possibly in a separate messages-context.xml) similar to this:
<bean id="messageSource"
class="org.springframework.context.support.ReloadableResourceBundleMessageSource">
<property name="basenames">
<list>
<value>WEB-INF/messages/messages</value>
</list>
</property>
</bean>
<bean id="localeChangeInterceptor"
class="org.springframework.web.servlet.i18n.LocaleChangeInterceptor">
<property name="paramName" value="lang" />
</bean>
<bean id="localeResolver"
class="org.springframework.web.servlet.i18n.CookieLocaleResolver">
<property name="defaultLocale" value="en_GB" />
</bean>
Spring will load your resource bundle when you application starts. You can then autowire the MessageSource into your controller and use it to get localised messages:
#Controller
public class SomeController {
#Autowired
private MessageSource messageSource;
#RequestMapping("/texts")
public ModelAndView texts(Locale locale) {
String localisedMessage = messageSource.getMessage("my.message.key", new Object[]{}, locale)
/* do something with localised message here */
return new ModelAndView("texts");
}
}
NB. adding Locale as a parameter to your controller method will cause Spring to magically wire it in - that's all you need to do.
You can also then access the messages in your resource bundle in your JSPs using:
<spring:message code="my.message.key" />
Which is my preferred way to do it - just seems cleaner.

Resources