I have following configuration in the Spring based project -
web.xml -
<servlet>
<servlet-name>spring</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>spring</servlet-name>
<url-pattern>/api/*</url-pattern>
</servlet-mapping>
<servlet-mapping>
<servlet-name>spring</servlet-name>
<url-pattern>/img/*</url-pattern>
</servlet-mapping>
<servlet-mapping>
<servlet-name>spring</servlet-name>
<url-pattern>/test</url-pattern>
</servlet-mapping>
Handler mapping from spring-servlet.xml -
<bean class="org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping">
<property name="alwaysUseFullPath" value="true" />
</bean>
and Spring Controller as -
#RestController
public class TestController {
#PostMapping("/test")
public String test() {
return "hello";
}
}
Now when I send request to
GET http://localhost:8080/test
I get response as hello which is fine but when I send request on following URL's -
GET http://localhost:8080/api/test
GET http://localhost:8080/img/test
then also I get hello response i.e. controller code gets executed for above mentioned wrong URLs.
Is there anything wrong in the configuration or it's expected behaviour?
The mapping of an URL to a handler (a method) in Spring MVC is a two-step process:
first the servlet container must decide whether to forward the request to the DispatcherServlet according to the <servlet-mapping>,
then the DispatcherServlet consults its HandlerMappings to see which one matches the request. A HandlerMapping can do anything, but usually calls UrlPathHelper#getLookupPathForRequest to transform the request into a path.
The default behavior of the UrlPathHelper depends on the <servlet-mapping> used:
if you used an exact mapping as in the case of /test, a request for /test will give a path of /test,
if you used a prefix mapping /api/*, a request for /api/foo/bar will result in a path of only /foo/bar.
As mentioned in the comment by M. Deinum you need to set the alwaysUseFullPath property to true on the RequestMappingHandlerMapping instance used by the DispatcherServlet.
In your XML application context configuration you define a bean of class RequestMappingHandlerMapping, while you should overwrite the bean used by the DispatcherServlet. Therefore you need to use the same id internally used by Spring:
<mvc:annotation-driven/>
<bean id="org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping"
class="org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping">
<property name="alwaysUseFullPath" value="true"/>
</bean>
Remark: This solution should work unless you invert the order of the definitions in your XML. In such a case the default definition will overwrite yours. Set the logger org.springframework.beans.factory.support do DEBUG to check which definition overwrites the other. In one case you get:
Overriding user-defined bean definition for bean 'org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping' with a framework-generated bean definition: ...
in the other:
Overriding bean definition for bean 'org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping' with a different definition: ...
Related
I am developing a web app using Spring mvc. I have page which displays all projects in DB. If i click on any of the projects listed, it will display some other additional details of that particular project. This is done by using #PathVariable.
#RequestMapping(value={"/project/{name}"})
public String viewProject(HttpServletRequest request,#PathVariable("name")
String projectName, ModelMap model){
.......
.......
}
Above is my request mapping code. My url will be http://localhost:8083/releaseDashboard/project/CSOB.html (csob is my project name and releaseDashboard is my app name). Till this my app works fine. When i click on the home button from this page, my request is mapped to the above controller method and my url becomes localhost:8083/releaseDashboard/project/home.html. But the expected url is localhost:8083/releaseDashboard/home.html
Can anyone please help me? I read that we can use Interceptor or Filters to change the requested url. But i couldnt see any code snippet for that.
UPDATE
Web.xml
<servlet>
<servlet-name>spring</servlet-name>
<servlet-class>
org.springframework.web.servlet.DispatcherServlet
</servlet-class>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>spring</servlet-name>
<url-pattern>*.html</url-pattern>
</servlet-mapping>
spring-servlet.xml
<context:component-scan base-package="com.suntec.reldashboard.controller" />
<context:component-scan base-package="com.suntec.reldashboard.service" />
<context:component-scan base-package="com.suntec.reldashboard.dao" />
<mvc:annotation-driven/>
<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<property name="prefix" value="/WEB-INF/jsp/" />
<property name="suffix" value=".jsp" />
</bean>
Your config in web.xml in the context of Spring MVC is incorrect.
Edit it as <url-pattern>/</url-pattern>. By this, all the requests to your project will pass through the 'dispatcher servlet'.
(You can also use like this <url-pattern>something-here</url-pattern>. Then your base url should have an extra 'something-here').
Now you can access the resource,
#RequestMapping(value={"/project/{name}"})
public String viewProject(HttpServletRequest request,#PathVariable("name")
String projectName, ModelMap model){
.......
.......
return "hello";
}
by the URL http://localhost:8083/releaseDashboard/project/CSOB. Then projectName will be CSOB.
You must have a 'jsp' file under /WEB-INF/jsp/ having the name hello.jsp. In that jsp file, you can access model values.
You must not use .html/.jsp within the URL, when using Spring MVC. All the resource is bounded to a VIEW, using view resolver. That is how this must be implemented. That's for it is 'MVC' and 'view-resolving'.
NOTE:
As per your current configuration, "you have to change a requested URL". NO, you can't. Then your URL may be http://localhost:8083/releaseDashboard/project/CSOB.html; and projectName is "CSOB.html". Then you have to use java substring function to extract "CSOB" from "CSOB.html". And this is an ugly stuff!
this is the current configuration i am using for spring mvc:
1- web.xml:
<servlet>
<servlet-name>spring</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:spring/config/dispatcherServlet.xml</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>spring</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
2- dispatcherServlet.xml:
<context:component-scan base-package="com.app" />
<context:annotation-config />
<mvc:annotation-driven />
<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<property name="prefix" value="/"/>
<property name="suffix" value=".jsp"/>
</bean>
3- Controller: my web pages are under webapp folder directly
#Controller
public class SearchController {
private Log log = LogFactory.getLog(getClass());
#RequestMapping("/search.jsp")
public String search(Model model, HttpServletRequest request,
HttpSession session) {
log.debug("Search Controller");
return "search";
}
ISSUE: when trying to access the search page as follows:
http://localhost:8080/MyAPP/search.jsp
the controller is not invoked, but when i was mapping the dispatcher servlet to /mapping/* and accessing the search page as follows:
http://localhost:8080/MyAPP/mapping/search.jsp
the controller was invoked correctly, i am using spring 3.0.5.RELEASE.
please advise, thanks.
I think you are forgetting about the built in default servlet configured in your web server/servlet container. For example in Tomcat7/conf/web.xml there exists:
<servlet>
<servlet-name>jsp</servlet-name>
<servlet-class>org.apache.jasper.servlet.JspServlet</servlet-class>
<init-param>
<param-name>fork</param-name>
<param-value>false</param-value>
</init-param>
<init-param>
<param-name>xpoweredBy</param-name>
<param-value>false</param-value>
</init-param>
<load-on-startup>3</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>default</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
<!-- The mappings for the JSP servlet -->
<servlet-mapping>
<servlet-name>jsp</servlet-name>
<url-pattern>*.jsp</url-pattern>
<url-pattern>*.jspx</url-pattern>
</servlet-mapping>
which is catching the *.jsp before it ever gets to Spring. I tested this locally by removing all of the Spring configuration and could still get the search.jsp.
How DefaultAnnotationHandlerMapping works should be useful in explaining why this works they way it does.
When you had <url-pattern>/mapping/*</url-pattern> you created a more specific match than the simple / so requests were ignored by the default (i.e. Tomcat) servlet and routed to your correctly configured controller.
One way to fix this is to force everything through your servlet by using <url-pattern>/*</url-pattern> but you will also need to make a few other changes to avoid mapping resolution problems.
I moved the *.jsp files into the (standard?) subdirectory /WEB-INF and added
<servlet-mapping>
<servlet-name>jsp</servlet-name>
<url-pattern>/WEB-INF/*</url-pattern>
</servlet-mapping>
to web.xml and changed dispatcherServlet.xml to match like so:
<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<property name="prefix" value="/WEB-INF/"/>
<property name="suffix" value=".jsp"/>
</bean>
If you do not make these changes, a request to /search.jsp would be resolved by the InternalResourceViewResolver you configured to /search.jsp sending Tomcat into an infinite forwarding loop!
No mapping found for HTTP request with URI [/WEB-INF/pages/apiForm.jsp] may be useful here.
Aside: For most of my Spring XML configured projects I use /WEB-INF/views to keep the view layer separate from any configuration in the /WEB-INF root.
The following mapping will cause the dispatcher servlet to handle urls that were not explicitly mapped by other url mappings within web.xml. Think of this as almost a catch all mapping, as long as the url was not handled by some other mapping.
<servlet-mapping>
<servlet-name>spring</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
When you configure the ViewResolver as follows:
<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<property name="prefix" value="/"/>
<property name="suffix" value=".jsp"/>
</bean>
The ViewMapping must point to a JSP within your project or else the catch all mapping provided for the dispatcher is going to attempt to handle the forward/redirect to the appropriate view. You must make sure that a view exists within your project for the result of the viewresolver, which is /search.jsp. This means there must be a search.jsp within the root of your projects web content folder. It is much more common to see these views placed within the WEB-INF folder and a mapping of:
<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<property name="prefix" value="/WEB-INF/"/>
<property name="suffix" value=".jsp"/>
</bean>
I have mapped DispatcherServlet as follows
<servlet>
<servlet-name>ems</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>ems</servlet-name>
<url-pattern>/*</url-pattern>
</servlet-mapping>
So, as per url it servers every request. But, when I mapped ViewResolver like this
<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<property name="viewClass" value="org.springframework.web.servlet.view.JstlView"/>
<property name="prefix" value="/WEB-INF/jsps/"/>
<property name="suffix" value=".jsp"></property>
</bean>
and as the controller returns "home" as view name. Then I am getting
[PageNotFound] No mapping found for HTTP request with URI [/ems/WEB-INF/jsps/home.jsp] in DispatcherServlet with name 'ems'
As this InternalResourceViewResolver using RequestDispacher, it is going to be another request and that request again getting handled by DispatcherServlet again.
How can we resolve this issue? And what is the best way to define project structure when we use <url-pattern>/*</url-pattern>. might be a dumb question, but I am always confused about defining my project structure when I use <url-pattern>/*</url-pattern>.
Using the "/*" for the URL mapping means that absolutely every request that's sent to that application has to go through the DispatcherServlet, so you'll need mappings for all of them.
If that's not acceptable, find a way to create a subset that should go through the DispatcherSerlet and let the rest be handled by the HTTP server.
I have a class
#Controller
#RequestMapping("/auth")
public class LoginController {
#RequestMapping(value = "/login", method = RequestMethod.GET)
public String getLoginPage(#RequestParam(value="error", required=false) boolean error,
ModelMap model) {
}
}
web.xml
<servlet>
<servlet-name>spring</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>
classpath:META-INF/spring-servlet.xml
</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
appcontext.xml
<mvc:annotation-driven/>
<context:component-scan base-package="mysrc.src"/>
spring servlet.xml
<context:component-scan base-package="mysrc.src"/>
<mvc:annotation-driven/>
I WAS getting an error like "no mapping found in dispatcher blah blah"
But NOW i can see no exception on console.
I only see 404
http://localhost:9090/app/auth/login
I put breakpoint in getLoginPage method in debug mode but no flow.
Beside suggestions i want to ask how can i be sure abour Spring has scanned an realised my #RequestMapping class? Searching on starting up console maybe?
Usually when you get a 404 with no error message from Spring, it usually means your app is deployed under a different context than you are expecting. For example, your code sample indicates you are deploying your webapp to /, are you sure thats where its being deployed? Are you using tomcat? If so, your webapp should be named ROOT.war. Can you provide more details like where/how you are deploying your webapp and maybe the web.xml as well?
As far as your mappings go, Spring will log which controllers it scanned along with their request path at the INFO logging level. The dispatcher servlet will give always you an error message if it cannot match your request to a controller, so no error message indicates to me your requests are not going through the dispatcher servlet.
Your request mapping is looking right.
You can create an constructor to your controller and put a log statement or a break point there to see if the controller is created by spring.
Note that your component-scan would also initialize the controller twice (once for appcontext.xml and once for servlet.xml). You should add filters to make sure controllers are only created from the servlet.xml:
appcontext.xml (everything except controllers):
<context:component-scan base-package="x.y.z">
<context:exclude-filter expression="org.springframework.stereotype.Controller" type="annotation" />
</context:component-scan>
servlet.xml (only controllers):
<context:component-scan base-package="x.y.z" use-default-filters="false">
<context:include-filter expression="org.springframework.stereotype.Controller" type="annotation"/>
</context:component-scan>
And just to be sure: The base-package attribute of component-scan has to be a valid java package and not a path to your src directory (mysrc.src looks a bit like your src folder ;-) )
My dispatcher servlet maps to the root of the app.
<servlet-mapping>
<servlet-name>dispatcher</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
I have a folder called 'static' in my webroot. It contains CSS, JS and image files. However, because of the dispatcher servlet mapping, the requests for static contents end up in 404s.
I know the solutions lying around to address this.
Make dispatcher map to a more specific URL, say :context:/app/, and then write a filter to intercept requests, and map conditionally to the default servlet, or else delegate to the spring dispatcher.
The URL rewrite trick.
using <mvc:resources />
Problem is, my mappings are XML based, and I will absolutely not scatter my mappings config all over the place in the name of using annotations. So if I use <mvc:resources />, my xml based mappings break, and all url mappings to different controllers are lost.
This is becase <mvc:resources /> overrides some settings and applies its own. But it is also the cleanest solution for static content.
Any way available to tell <mvc:resources /> to not override my xml based mappings?
<mvc:resources /> appears to be a perfect fit for your problem.
From what I understand, your DispatcherServlet is handling all requests to your server. So the resource tag should return the files at the location specified in the mvc:resources location attribute. It should not be catching anything other than what's mapped.
Are you using something along the lines of
<mvc:resources mapping="/static/**" location="/static/"/>
If it is overriding settings that aren't configurable in the tag consider instantiating your own org.springframework.web.servlet.resource.ResourceHttpRequestHandler
I do have this in the web.xml
<servlet-mapping>
<servlet-name>default</servlet-name>
<url-pattern>/resources/*</url-pattern>
</servlet-mapping>
<servlet-mapping>
<servlet-name>default</servlet-name>
<url-pattern>*.png</url-pattern>
<url-pattern>*.js</url-pattern>
<url-pattern>*.css</url-pattern>
<url-pattern>*.jpg</url-pattern>
</servlet-mapping>
You can provide a file extention for your controllers, e.g.
<servlet-mapping>
<servlet-name>dispatcher</servlet-name>
<url-pattern>*.do</url-pattern>
</servlet-mapping>
Then all URLs ending in .do will go through springs DispatcherServlet.
add <mvc:default-servlet-handler/> towards the top of your web.xml file
or if you are you using annotations
#Configuration
#EnableWebMvc
public class MVCConfig extends WebMvcConfigurerAdapter {
#Override
public void configureDefaultServletHandling(
DefaultServletHandlerConfigurer configurer) {
configurer.enable("default");
}
}