Apologies if this feels repetitive, I gather alot of people ask about this but I haven't been able to find an answer that works.
I have a web-app, built using maven. I use Spring 4 MVC to deliver a RESTful Json API. I also have a lot of static content (html, css, js) were I use Angular.js to put a pretty face on the data API.
For the life of me, I can't figure out how to get both of these being served at the same time without messing with their paths.
I'd really like to go to {APP_ROOT}/people/{id} in my browser, and be interacting directly with my REST api without any crap about /api/ or /rest/
I'd really like to go to {APP_ROOT}/css/style.css in my browser, and be served content from src/main/webapp/css/style.css without any crap about resources or static
Additionally, I'd really like to configure all of this with annotated Java classes, and not have any web.xml, application-context.xml, etc.
So, the Spring dispatcher servlet should be handling all of the REST resource paths, and then falling back to the default Tomcat/Jetty handler for the static content. I thought this was exactly the scenario default-servlet-handler was intended for? I can't seem to get it to work.
These are my relevant configuration classes:
WebAppInitializer.java
public class WebAppInitializer extends
AbstractAnnotationConfigDispatcherServletInitializer {
#Override
protected Class<?>[] getRootConfigClasses() {
return new Class<?>[0] ;
}
#Override
protected Class<?>[] getServletConfigClasses() {
return new Class<?>[] { WebConfig.class};
}
#Override
protected String[] getServletMappings() {
return new String[] { "/*" };
}
#Override
protected Filter[] getServletFilters() {
CharacterEncodingFilter characterEncodingFilter = new CharacterEncodingFilter();
characterEncodingFilter.setEncoding("UTF-8");
return new Filter[] { characterEncodingFilter};
}
}
WebConfig.java
#Configuration
#EnableWebMvc
#ComponentScan(basePackages = {"my.example.package"})
public class WebConfig extends WebMvcConfigurerAdapter {
#Override
public void configureDefaultServletHandling(DefaultServletHandlerConfigurer configurer) {
configurer.enable();
}
}
With this configuration I can interact with the REST api, but not the static content. The default servlet handler seems to have no effect.
To access static resource like css js or html keep all these files inside webapp folder of your module. Suppose all you static resources are in src/main/webapp/static path you can do mvc resource mapping like below in context xml
<mvc:resources mapping="/index-dev.html" location="/static/index-dev.html"/>
<mvc:resources mapping="/index.html" location="/static/index.html"/>
<mvc:resources mapping="/app.js" location="/static/app.js"/>
<mvc:resources mapping="/appinit.js" location="/static/appinit.js"/>
<mvc:resources mapping="/extjs/**" location="/static/extjs/"/>
<mvc:resources mapping="/app/**" location="/static/app/"/>
<mvc:resources mapping="/static/**" location="/static/static/"/>
<mvc:resources mapping="/static/**" location="/static/"/>
Now since you want to do it without xml you can do it in your WebConfig class like below example
#Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
registry.addResourceHandler("/index.html").addResourceLocations("/static/index.html");
}
Related
I am trying to build a single page HTML/Angular app backed by Spring MVC 4 without .jsp files.
When the user arrives to the root URL (http://myapp.com/ for example), I want to send the single page HTML document (index.html).
However, I am new to Spring MVC's Java config and cannot work out the proper combination of ServletMapping, ViewResolver, and ResourceHandler.
I know that there may be some additional configuration for the "default" or "index" page, but I may be mistaken.
So how do I make a Java configured Spring MVC 4 application send an html file?
File structure
/src
'-/main
|-/web-inf
'-/webapp
|-index.html (want to send this)
'-/app
|-/partials
'-app.js
WebAppConfig.java
#Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
registry.addResourceHandler("/").addResourceLocations("/index.html");
}
IndexController.java
#RequestMapping("/")
public void index() {
System.out.println("pls"); // executes
// Have tried returning "index" and "index.html"
}
WebInitializer.java
#Override
protected String[] getServletMappings() {
// have tried with "/" as well
return new String[] { "/*" };
}
I think you should add this:
<mvc:view-controller path="/" view-name="index"/>
in your dispathcer-servlet file.
Here are a few working solutions either way - in case anyone needs one for their specific use case.
If you DO NOT want to invoke a controller method and just serve the file.
WebInitializer.java:
#Override
protected String[] getServletMappings() {
return new String[] { "/" };
}
Web.xml
<web-app>
<welcome-file-list>
<welcome-file>index.html</welcome-file>
</welcome-file-list>
</web-app>
WebConfig.java
#Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
registry.addResourceHandler("index.html").addResourceLocations("/index.html ");
}
If you DO want to invoke a controller method on landing.
No welcome file list is required in the web.xml
WebInitializer.java:
#Override
protected String[] getServletMappings() {
return new String[] { "/*" };
}
WebConfig.java
#Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
registry.addResourceHandler("index.html").addResourceLocations("/index.html ");
}
#Bean
public InternalResourceViewResolver viewResolver() {
InternalResourceViewResolver viewResolver = new InternalResourceViewResolver();
viewResolver.setSuffix(".html");
return viewResolver;
}
Controller.java
#RequestMapping("/")
public String index() {
return "index";
}
Hope that helps somebody.
I have an application on Spring and using Java Configs to configure and initialize my application, so that I have no web.xml. Here is how my web initializer looks like,
public class WebInitializer extends AbstractAnnotationConfigDispatcherServletInitializer {
#Override
public void onStartup(ServletContext servletContext) throws ServletException {
super.onStartup(servletContext);
}
#Override
protected Class<?>[] getRootConfigClasses() {
return new Class<?>[]{PublicApiConfig.class, MobileConfig.class};
}
#Override
protected String[] getServletMappings() {
return new String[]{"/*"};
}
#Override
protected Filter[] getServletFilters() {
CharacterEncodingFilter characterEncodingFilter = new CharacterEncodingFilter();
characterEncodingFilter.setEncoding("UTF-8");
LoggingFilter loggingFilter = new LoggingFilter();
return new Filter[]{characterEncodingFilter, loggingFilter};
}
#Override
protected Class<?>[] getServletConfigClasses() {
return new Class<?>[0];
}
}
I need to implement tomcat session replication, and for the sake of purpose I need to have application as distributable. With traditional web.xml I could add <distributable/> attribute and thats it. However as far as I understand there is no way to do this via Java Configs.
My question is if it is possible to have mixed web.xml and java configs, e.g. to have
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://java.sun.com/xml/ns/javaee"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd"
version="3.0">
<distributable/>
</web-app>
and include it in WebInitializer.
According to the Servlet 3.0 specification its possible to mix web.xml with Programmatic servlet registration as long as web-app version >= 3.0 and metadata-complete attribute is false (default). With your current configuration it should work
You can use a TomcatEmbeddedServletContainerFactory, and there
#Override
public void customize(Context context){
context.setDistributable(true);
}
You find a complete code example in this thread
spring-boot-application-with-embedded-tomcat-session-clustering
Edit : I am not using Spring Boot in this case, and TomcatEmbeddedServletContainerFactory is not available
The javadoc of WebApplicationInitializer says, its possible to use it together with a web.xml :
WEB-INF/web.xml and WebApplicationInitializer use are not mutually exclusive; for example, web.xml can register one servlet, and a WebApplicationInitializer can register another. An initializer can even modify registrations performed in web.xml through methods such as ServletContext#getServletRegistration(String).
In a spring MVC application i tried to use sitemesh 3 with java configurations. but it only render template for jsp files inside WEB-INF.
public class MySiteMeshFilter extends ConfigurableSiteMeshFilter {
#Override
protected void applyCustomConfiguration(SiteMeshFilterBuilder builder) {
builder.addDecoratorPath("/*", "/decorators/default.jsp").
addDecoratorPath("/index.jsp", "/decorators/default.jsp");
}
}
Configuration File
#Override
protected Filter[] getServletFilters() {
return new Filter[]{new ErrorHandlerFilter(), new MySiteMeshFilter()};
}
My Problem is sitemesh 3 working for login.jsp But not for index.jsp?
I am running a Spring 4 web mvc project:
Issue:
My controlleradvice for 404 exception handler is not working. However, if I comment the "addResourceHandlers" method in WebConfig class, it will work. (I can't remove that as it resolves my static resources)_
This is my web config:
#EnableWebMvc
#Configuration
public class WebConfig extends WebMvcConfigurerAdapter {
/*
* Resource handler for static resources
*/
#Override
public void addResourceHandlers(final ResourceHandlerRegistry registry) {
registry.addResourceHandler("/**").addResourceLocations("classpath:/static/");
}
}
And this is my 404 exception handler:
#ControllerAdvice
public class ExceptionController {
#ExceptionHandler(NoHandlerFoundException.class)
public String handle404(Exception e) {
return "error/404";
}
}
If your webapp is using web.xml it's very simple - just add the following (assuming usage of InternalResourceViewResolver with prefix pointing at your WEB-INF view folder and suffix .jsp). You can have multiple error-page elements of other error codes too.
<error-page>
<error-code>404</error-code>
<location>/error</location>
</error-page>
If you are not using web.xml it's a bit more complicated.
If you want to catch the NoHandlerFound exception you first have to tell Spring to throw it via setting a flag in the DispatcherServlet directly.
To do so, in the class that you are extending AbstractAnnotationConfigDispatcherServletInitializer override the onStartup method to expose the DispatcherServlet definition and add manually the needed flag:
#Override
public void onStartup(ServletContext servletContext) throws ServletException {
//...
WebApplicationContext context = getContext();
DispatcherServlet dispatcherServlet = new DispatcherServlet(context);
//we did all this to set the below flag
dispatcherServlet.setThrowExceptionIfNoHandlerFound(true);
ServletRegistration.Dynamic dispatcher = servletContext.addServlet("DispatcherServlet",dispatcherServlet );
//..
}
Then your existing code within ExceptionController should work and intercept the exception
I have a single-page web app that's using Backbone.js client routing with pushState. In order to get this to work, I have to tell my server (Java, Spring 3, Tomcat) which URLs should be resolved on the server (actual JSP views, API requets), and which should simply be sent to the index page to be handled by the client. Currently I'm using an InternalResourceViewResolver to simply serve JSP views that match the name of the URL request. Since client-side URLs don't have a view on the server, the server returns a 404.
What is the best way to specify to Spring (or Tomcat) that a few specific URLs (my client-side routes) should all resolve to index.jsp, and anything else should fall through to the InternalResourceViewResolver?
I found that Spring MVC 3 added a tag that does exactly what I need, the mvc:view-controller tag. This got it done for me:
<mvc:view-controller path="/" view-name="index" />
<mvc:view-controller path="/admin" view-name="index" />
<mvc:view-controller path="/volume" view-name="index" />
http://static.springsource.org/spring/docs/3.0.x/reference/mvc.html
In theory, to handle navigation via history.pushState you want to return index.html for unhandled resources. If you look at official documentation for modern web frameworks it's often realised based on 404 status.
In spring you should handle resources in order:
path mapped REST controllers
app static resources
index.html for others
To do this you have at least 4 possible solutions.
Using EmbeddedServletContainerCustomizer and custom 404 handler
#Controller
static class SpaController {
#RequestMapping("resourceNotFound")
public String handle() {
return "forward:/index.html";
}
}
#Bean
public EmbeddedServletContainerCustomizer containerCustomizer() {
return container -> container.addErrorPages(new ErrorPage(HttpStatus.NOT_FOUND, "/resourceNotFound"));
}
Using custom default request mapping handler
#Autowired
private RequestMappingHandlerMapping requestMappingHandlerMapping;
static class SpaWithHistoryPushStateHandler {
}
static class SpaWithHistoryPushStateHandlerAdapter implements HandlerAdapter {
#Override
public boolean supports(final Object handler) {
return handler instanceof SpaWithHistoryPushStateHandler;
}
#Override
public ModelAndView handle(final HttpServletRequest request, final HttpServletResponse response, final Object handler) throws Exception {
response.getOutputStream().println("default index.html");
return null;
}
#Override
public long getLastModified(final HttpServletRequest request, final Object handler) {
return -1;
}
}
#Bean
public SpaWithHistoryPushStateHandlerAdapter spaWithHistoryPushStateHandlerAdapter() {
return new SpaWithHistoryPushStateHandlerAdapter();
}
#PostConstruct
public void setupDefaultHandler() {
requestMappingHandlerMapping.setDefaultHandler(new SpaWithHistoryPushStateHandler());
}
Using custom ResourceResolver
#Autowired
private ResourceProperties resourceProperties;
#Override
public void addResourceHandlers(final ResourceHandlerRegistry registry) {
registry.addResourceHandler("/**")
.addResourceLocations(resourceProperties.getStaticLocations())
.setCachePeriod(resourceProperties.getCachePeriod())
.resourceChain(resourceProperties.getChain().isCache())
.addResolver(new PathResourceResolver() {
#Override
public Resource resolveResource(final HttpServletRequest request, final String requestPath, final List<? extends Resource> locations, final ResourceResolverChain chain) {
final Resource resource = super.resolveResource(request, requestPath, locations, chain);
if (resource != null) {
return resource;
} else {
return super.resolveResource(request, "/index.html", locations, chain);
}
}
});
}
Using custom ErrorViewResolver
#Bean
public ErrorViewResolver customErrorViewResolver() {
final ModelAndView redirectToIndexHtml = new ModelAndView("forward:/index.html", Collections.emptyMap(), HttpStatus.OK);
return (request, status, model) -> status == HttpStatus.NOT_FOUND ? redirectToIndexHtml : null;
}
Summary
Fourth option looks simplest but as always it depends what you need. You may also want to restric returning index.html only when request expects text/html (which BasicErrorController already do based on "produces" header).
I hope one of this options will help in your case.
I would give a clear scheme to my urls and separate frontend from backend.
Some suggestions:
Route all requests starting by /server to the backend and all others to the frontend.
Setup two different domains, one for the backend, one for the frontend.