3

I'm in the early stages of learning to use Spring MVC. I've created a controller and applied a RequestMapping annotation to it. When I run the project, the index page displays as expected at index.htm, but when I navigate to the URI that should be pointing to my controller, I get a 404 error, even though the controller seems to have been detected by Spring and started. Please help me understand what I am failing to grasp here:

Here is my web.xml:

<?xml version="1.0" encoding="UTF-8"?>
<web-app version="3.0" xmlns="http://java.sun.com/xml/ns/javaee" 
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd">
<context-param>
    <param-name>contextConfigLocation</param-name>
    <param-value>/WEB-INF/applicationContext.xml</param-value>
</context-param>
<listener>
    <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
<servlet>
    <servlet-name>dispatcher</servlet-name>
    <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
    <load-on-startup>2</load-on-startup>
</servlet>
<servlet-mapping>
    <servlet-name>dispatcher</servlet-name>
    <url-pattern>/</url-pattern>
</servlet-mapping>
<session-config>
    <session-timeout>
        30
    </session-timeout>
</session-config>
<welcome-file-list>
    <welcome-file>redirect.jsp</welcome-file>
</welcome-file-list>

Here is my applicationContext.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" 

   xmlns:p="http://www.springframework.org/schema/p"
   xmlns:aop="http://www.springframework.org/schema/aop"
   xmlns:tx="http://www.springframework.org/schema/tx"
   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.2.xsd  
   http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-3.1.xsd
   http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-3.1.xsd>

    <context:spring-configured/>

<context:component-scan base-package="org.blah.blah"/>

Here is my dispatcher-servlet.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" 
   xmlns:p="http://www.springframework.org/schema/p"
   xmlns:aop="http://www.springframework.org/schema/aop"
   xmlns:tx="http://www.springframework.org/schema/tx"
   xmlns:mvc="http://www.springframework.org/schema/mvc"
   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.2.xsd  
                       http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc-3.0.xsd        
                       http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-3.1.xsd
                       http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-3.1.xsd">

<mvc:annotation-driven/>
<bean class="org.springframework.web.servlet.mvc.support.ControllerClassNameHandlerMapping"/>

<bean id="urlMapping" class="org.springframework.web.servlet.handler.SimpleUrlHandlerMapping">
    <property name="mappings">
        <props>
            <prop key="index.htm">indexController</prop>
        </props>
    </property>
</bean>

<bean id="viewResolver"
      class="org.springframework.web.servlet.view.InternalResourceViewResolver"
      p:prefix="/WEB-INF/jsp/"
      p:suffix=".jsp" />

<bean name="indexController"
      class="org.springframework.web.servlet.mvc.ParameterizableViewController"
      p:viewName="index" />

And, finally, here is my controller.

@Controller
@RequestMapping(value = "/hello")
public class Ctrl {

    @RequestMapping(method = RequestMethod.GET)
    @ResponseBody
    public String hello(){
    return "hello!";
    }

}

EDIT: This is what my browser displays: enter image description here

drew moore
  • 31,565
  • 17
  • 75
  • 112
  • What is your context path and what is the URL you hit that caused the 404? – Sotirios Delimanolis Feb 18 '14 at 00:54
  • @SotiriosDelimanolis My context path is `/blah`, and I've tried a dozen different URLs - `/blah/hello` is the one I would expect to work... right? – drew moore Feb 18 '14 at 00:56
  • I know you're trying to hide the real names, but please make everything consistent in your controller class and the configuration. Also, the 404 you get, what does the error message from Tomcat say? – Sotirios Delimanolis Feb 18 '14 at 00:57
  • @SotiriosDelimanolis - that's the interesting thing: There is no error message - it's just blank. The description says "The requested resource is not available". And I'm actually not trying to hide anything here - in an effort to figure out what's going on, I created an actual package called `blah`. What is above was c/p'd directly from my source files – drew moore Feb 18 '14 at 01:00
  • What does your browser say is missing when it gives the 404? It should give a resource path. – Jake Haller-Roby Feb 18 '14 at 01:04
  • @gravityplanx see my edit - that's all it gives me – drew moore Feb 18 '14 at 01:07
  • Taking a closer look, there's definitely something wrong about the code you listed. If you're even able to run it in your local, I'm guessing that it looks very different from what you've posted here. Take a harder look at that "Hello()" function in your controller... And then look at the rest of the files and make sure what you're posting is accurate. We can't help you if you give us wrong data. – Jake Haller-Roby Feb 18 '14 at 01:23
  • @gravityplanx - whoops, sorry. I messed it up in an edit. If you check it out now, that's exactly how I have it, and it is indeed running locally. – drew moore Feb 18 '14 at 01:27
  • not that this solves your problem, but 1. if you're not setting a value, feel free not to include that part of requestMapping at all, and 2. There's no need to put the request method type on an entire class, it's better to do that function by function. – Jake Haller-Roby Feb 18 '14 at 01:30

2 Answers2

3

I was half-wrong in the comments to gravityplanx' answer.

When you specify a <mvc:annotation-driven /> in your servlet configuration, Spring registers a RequestMappingHandlerMapping bean which is meant to collect and map all your @Controller handler methods, ie. those annotated with @RequestMapping.

It does this by looking at all the beans in the current context. Remember that when you load a context with DispatcherServlet, call it the servlet context, if a context was loaded by a ContextLoaderListener, call it the root context, the root context is made a parent of the servlet context. By default, the RequestMappingHandlerMapping bean does not look at the root context, which is a parent of the servlet context.

In your case, the @Controller beans are declared implicitly in the root context (your applicationContext.xml) because of the <context:component-scan/> which scans classes annotated with @Component or any of its specializations (@Controller for instance) and creates beans for them. So these beans are registered in the root context, where the RequestMappingHandlerMapping can't find them.

If instead you declare the <context:component-scan /> in the servlet context, then the @Controller (and other) beans are created in the servlet context where they are available to the RequestMappingHandlerMapping which can then register them to handle requests.

Note that <context:component-scan /> on its own doesn't do anything for the MVC stack. It needs further configuration, like <mvc:annotation-driven />. You should aim to specify packages that contain servlet specific beans in the servlet context and application wide beans in the root context.

Here's some literature:

Community
  • 1
  • 1
Sotirios Delimanolis
  • 274,122
  • 60
  • 696
  • 724
  • Thanks so much for the explanation, that helps a lot. I accepted the other answer because it was what solved the problem, but upvoted yours! – drew moore Feb 18 '14 at 04:38
2

Your dispatcher servlet doesn't seem to be referenced correctly.

Replace:

<servlet>
    <servlet-name>dispatcher</servlet-name>
    <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
    <load-on-startup>2</load-on-startup>
</servlet>

With:

<servlet>
    <servlet-name>dispatcher</servlet-name>
    <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
    <init-param>
        <param-name>contextConfigLocation</param-name>
        <param-value>[insert the path to your dispatcher-servlet here]</param-value>
    </init-param>
    <load-on-startup>1</load-on-startup>
</servlet>

And then move:

<context:spring-configured/>

<context:component-scan base-package="org.blah.blah"/>

From applicationContext to dispatcher-servlet

Jake Haller-Roby
  • 6,335
  • 1
  • 18
  • 31
  • No, by default `DispatcherServlet` will look for a file named `-servlet.xml`. The load on startup of 2 will have the same effect as 1 in this case, though it is weird. The component-scan doesn't need to be in the servlet context necessarily. As long as it creates a bean which `mvc:annotation-driven` can see. – Sotirios Delimanolis Feb 18 '14 at 01:52
  • Typically I agree, but I always find it useful to be as explicit as possible with errors like this. Relying on the build to handle things automatically sometimes results in issues, but explicitly declaring your targets means that if there's a problem it's with something you typed. – Jake Haller-Roby Feb 18 '14 at 02:01
  • After moving the two `` lines to dispatcher-servlet, it worked! In the interest of helping me wrap my head around what's going on here, can you explain what exactly I did by moving those two lines, and why it worked? – drew moore Feb 18 '14 at 02:07