如何在Spring MVC中将请求映射到HTML文件?

时间:2013-05-16 22:32:25

标签: java jsp servlets spring-mvc

基本配置文件看起来不直观。

如果我创建简单的hello world示例,然后将home.jsp重命名为home.html并从

编辑servlet-context.xml文件
<beans:bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
    <beans:property name="prefix" value="/WEB-INF/views/" />
    <beans:property name="suffix" value=".jsp" />
</beans:bean> 

<beans:bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
    <beans:property name="prefix" value="/WEB-INF/views/" />
    <beans:property name="suffix" value=".html" />
</beans:bean>

我开始收到错误

WARN : org.springframework.web.servlet.PageNotFound - No mapping found for HTTP request with URI [/myapp/WEB-INF/views/home.html] in DispatcherServlet with name 'appServlet'

为什么呢? suffix属性意味着什么?

更新

我的控制器如下。如您所见,它不包含文件扩展名

@Controller
public class HomeController {

    private static final Logger logger = LoggerFactory.getLogger(HomeController.class);

    /**
     * Simply selects the home view to render by returning its name.
     */
    @RequestMapping(value = "/", method = RequestMethod.GET)
    public String home(Locale locale, Model model) {
        logger.info("Welcome home! The client locale is {}.", locale);

        Date date = new Date();
        DateFormat dateFormat = DateFormat.getDateTimeInstance(DateFormat.LONG, DateFormat.LONG, locale);

        String formattedDate = dateFormat.format(date);

        model.addAttribute("serverTime", formattedDate );

        return "home";
    }

}

7 个答案:

答案 0 :(得分:29)

问题的背景

首先要理解的是:它不是渲染jsp文件的spring。它是JspServlet(org.apache.jasper.servlet.JspServlet)。这个servlet附带Tomcat(jasper编译器)而不是spring。这个JspServlet知道如何编译jsp页面以及如何将它作为html文本返回给客户端。默认情况下,tomcat中的JspServlet只处理匹配两种模式的请求:* .jsp和* .jspx。

现在,当spring使用InternalResourceView(或JstlView)呈现视图时,确实发生了三件事:

  1. 从模型获取所有模型参数(由控制器处理程序方法返回,即"public ModelAndView doSomething() { return new ModelAndView("home") }"
  2. 将这些模型参数公开为请求属性(以便JspServlet可以读取)
  3. 转发请求到JspServlet。 RequestDispatcher知道每个* .jsp请求都应该转发到JspServlet(因为这是默认的tomcat配置)
  4. 当您只是将视图名称更改为home.html时,tomcat将知道如何处理请求。这是因为没有servlet处理* .html请求。

    <强>解决方案

    如何解决这个问题。有三个最明显的解决方案:

    1. 将html公开为资源文件
    2. 指示JspServlet也处理* .html请求
    3. 编写自己的servlet(或传递给另一个现有的servlet请求* .html)。
    4. 初始配置(仅处理jsp)

      首先让我们假设我们配置没有xml文件的spring(仅基于@Configuration注释和spring的WebApplicationInitializer接口)。

      基本配置将遵循

      public class MyWebApplicationContext extends AnnotationConfigWebApplicationContext {
        private static final String CONFIG_FILES_LOCATION = "my.application.root.config";
      
        public MyWebApplicationContext() {
          super();
          setConfigLocation(CONFIG_FILES_LOCATION);
        }
      
      }
      
      public class AppInitializer implements WebApplicationInitializer {
      
        @Override
        public void onStartup(ServletContext servletContext) throws ServletException {
          WebApplicationContext context = new MyWebApplicationContext();
          servletContext.addListener(new ContextLoaderListener(context));
      
          addSpringDispatcherServlet(servletContext, context);
      
        }
      
        private void addSpringDispatcherServlet(ServletContext servletContext, WebApplicationContext context) {
          ServletRegistration.Dynamic dispatcher = servletContext.addServlet("DispatcherServlet",
            new DispatcherServlet(context));
          dispatcher.setLoadOnStartup(2);
          dispatcher.addMapping("/");
          dispatcher.setInitParameter("throwExceptionIfNoHandlerFound", "true");
        }
      }
      
      package my.application.root.config
      // (...)
      
      @Configuration
      @EnableWebMvc
      public class WebConfig extends WebMvcConfigurerAdapter {
        @Autowired
        @Qualifier("jstlViewResolver")
        private ViewResolver jstlViewResolver;
      
        @Bean
        @DependsOn({ "jstlViewResolver" })
        public ViewResolver viewResolver() {
          return jstlViewResolver;
        }
      
        @Bean(name = "jstlViewResolver")
        public ViewResolver jstlViewResolver() {
          UrlBasedViewResolver resolver = new UrlBasedViewResolver();
          resolver.setPrefix("/WEB-INF/internal/");
          resolver.setViewClass(JstlView.class);
          resolver.setSuffix(".jsp");
          return resolver;
        }
      
      }
      

      在上面的示例中,我使用UrlBasedViewResolver和后备视图类JstlView,但您可以像在示例中一样使用InternalResourceViewResolver并不重要。

      上面的示例使用一个视图解析器配置应用程序,该解析器处理以.jsp结尾的jsp文件。注意:如开头所述,JstlView确实使用tomcat的RequestDispatcher将请求转发给JspSevlet以将jsp编译为html。

      解决方案1的实施 - 将html公开为资源文件:

      我们修改WebConfig类以添加匹配的新资源。我们还需要修改jstlViewResolver,使它既不带前缀也不带后缀:

      @Configuration
      @EnableWebMvc
      public class WebConfig extends WebMvcConfigurerAdapter {
        @Autowired
        @Qualifier("jstlViewResolver")
        private ViewResolver jstlViewResolver;
      
        @Override
        public void addResourceHandlers(ResourceHandlerRegistry registry) {
          registry.addResourceHandler("/someurl/resources/**").addResourceLocations("/resources/");
      
        }
      
        @Bean
        @DependsOn({ "jstlViewResolver" })
        public ViewResolver viewResolver() {
          return jstlViewResolver;
        }
      
        @Bean(name = "jstlViewResolver")
        public ViewResolver jstlViewResolver() {
          UrlBasedViewResolver resolver = new UrlBasedViewResolver();
          resolver.setPrefix(""); // NOTE: no preffix here
          resolver.setViewClass(JstlView.class);
          resolver.setSuffix(""); // NOTE: no suffix here
          return resolver;
        }
      
      // NOTE: you can use InternalResourceViewResolver it does not matter 
      //  @Bean(name = "internalResolver")
      //  public ViewResolver internalViewResolver() {
      //    InternalResourceViewResolver resolver = new InternalResourceViewResolver();
      //    resolver.setPrefix("");
      //    resolver.setSuffix("");
      //    return resolver;
      //  }
      }
      

      通过添加这个,我们说每个发送到http://my.server/someurl/resources/的请求都被映射到您的web目录下的资源目录。因此,如果您将home.html放在资源目录中并将浏览器指向http://my.server/someurl/resources/home.html,则会提供该文件。要通过控制器处理此问题,请返回资源的完整路径:

      @Controller
      public class HomeController {
      
          @RequestMapping(value = "/", method = RequestMethod.GET)
          public ModelAndView home(Locale locale, Model model) {
              // (...)
      
              return new ModelAndView("/someurl/resources/home.html"); // NOTE here there is /someurl/resources
          }
      
      }
      

      如果你在同一个目录中放置一些jsp文件(不仅是* .html文件),比如同一资源目录中的home_dynamic.jsp,你可以用类似的方式访问它,但你需要使用服务器上的实际路径。路径以/ someurl /开头,因为这只是以.html结尾的html资源的映射。在这种情况下,jsp是动态资源,最后由JspServlet使用磁盘上的实际路径访问。因此访问jsp的正确方法是:

      @Controller
      public class HomeController {
      
          @RequestMapping(value = "/", method = RequestMethod.GET)
          public ModelAndView home(Locale locale, Model model) {
              // (...)
      
              return new ModelAndView("/resources/home_dynamic.jsp"); // NOTE here there is /resources (there is no /someurl/ because "someurl" is only for static resources 
      
      }
      

      要在基于xml的配置中实现此目的,您需要使用:

      <mvc:resources mapping="/someurl/resources/**" location="/resources/" />
      

      并修改您的jstl视图解析器:

      <beans:bean class="org.springframework.web.servlet.view.InternalResourceViewResolver"> <!-- Please NOTE that it does not matter if you use InternalResourceViewResolver or UrlBasedViewResolver as in annotations example -->
          <beans:property name="prefix" value="" />
          <beans:property name="suffix" value="" />
      </beans:bean>
      

      解决方案2的实施

      在这个选项中,我们使用tomcat的JspServlet来处理静态文件。因此,您可以在html文件中使用jsp标签:)如果您这样做,它当然是您的选择。很可能你想使用普通的html,所以只是不要使用jsp标签,内容将像静态html一样提供。

      首先,我们删除视图解析器的前缀和后缀,如上例所示:

      @Configuration
      @EnableWebMvc
      public class WebConfig extends WebMvcConfigurerAdapter {
        @Autowired
        @Qualifier("jstlViewResolver")
        private ViewResolver jstlViewResolver;
      
        @Bean
        @DependsOn({ "jstlViewResolver" })
        public ViewResolver viewResolver() {
          return jstlViewResolver;
        }
      
        @Bean(name = "jstlViewResolver")
        public ViewResolver jstlViewResolver() {
          InternalResourceViewResolver resolver = new InternalResourceViewResolver(); // NOTE: this time I'm using InternalResourceViewResolver and again it does not matter :)
          resolver.setPrefix("");
          resolver.setSuffix("");
          return resolver;
        }
      
      }
      

      现在我们添加JspServlet来处理* .html文件:

      public class AppInitializer implements WebApplicationInitializer {
      
        @Override
        public void onStartup(ServletContext servletContext) throws ServletException {
          WebApplicationContext context = new MyWebApplicationContext();
          servletContext.addListener(new ContextLoaderListener(context));
      
          addStaticHtmlFilesHandlingServlet(servletContext);
          addSpringDispatcherServlet(servletContext, context);
      
        }
      
       // (...)
      
        private void addStaticHtmlFilesHandlingServlet(ServletContext servletContext) {
          ServletRegistration.Dynamic servlet = servletContext.addServlet("HtmlsServlet", new JspServlet()); // org.apache.jasper.servlet.JspServlet
          servlet.setLoadOnStartup(1);
          servlet.addMapping("*.html");
        }
      
      }
      

      重要的是,要使此类可用,您需要在tomcat的安装中添加jasper.jar,仅用于编译时。如果你有maven应用程序,那么通过使用为jar提供的scope =来实现这一点非常容易。 maven中的依赖关系如下:

      <dependency>
          <groupId>org.apache.tomcat</groupId>
          <artifactId>tomcat-jasper</artifactId>
          <version>${tomcat.libs.version}</version>
          <scope>provided</scope> <!--- NOTE: scope provided! -->
      </dependency>
      <dependency>
          <groupId>org.apache.tomcat</groupId>
          <artifactId>tomcat-jsp-api</artifactId>
          <version>${tomcat.libs.version}</version>
          <scope>provided</scope>
      </dependency>
      

      如果您想以xml方式执行此操作。您需要注册jsp servlet来处理* .html请求,因此您需要在web.xml中添加以下条目

      <servlet>
          <servlet-name>htmlServlet</servlet-name>
          <servlet-class>org.apache.jasper.servlet.JspServlet</servlet-class>
          <load-on-startup>3</load-on-startup>
      </servlet>
      
      <servlet-mapping>
          <servlet-name>htmlServlet</servlet-name>
          <url-pattern>*.html</url-pattern>
      </servlet-mapping>
      

      现在,在您的控制器中,您可以像以前的示例一样访问html和jsp文件。优点是没有&#34; / someurl /&#34;解决方案1中需要的额外映射。您的控制器将如下所示:

      @Controller
      public class HomeController {
      
          @RequestMapping(value = "/", method = RequestMethod.GET)
          public ModelAndView home(Locale locale, Model model) {
              // (...)
      
              return new ModelAndView("/resources/home.html"); 
      
      }
      

      指向你的jsp你完全一样:

      @Controller
      public class HomeController {
      
          @RequestMapping(value = "/", method = RequestMethod.GET)
          public ModelAndView home(Locale locale, Model model) {
              // (...)
      
              return new ModelAndView("/resources/home_dynamic.jsp");
      
      }
      

      解决方案3的实施

      第三种解决方案在某种程度上是解决方案1和解决方案2的组合。所以在这里我们要将所有请求传递给* .html到其他一些servlet。你可以编写自己的或者寻找已经存在的servlet的一些好的候选者。

      如上所述,我们首先清理视图解析器的前缀和后缀:

      @Configuration
      @EnableWebMvc
      public class WebConfig extends WebMvcConfigurerAdapter {
        @Autowired
        @Qualifier("jstlViewResolver")
        private ViewResolver jstlViewResolver;
      
        @Bean
        @DependsOn({ "jstlViewResolver" })
        public ViewResolver viewResolver() {
          return jstlViewResolver;
        }
      
        @Bean(name = "jstlViewResolver")
        public ViewResolver jstlViewResolver() {
          InternalResourceViewResolver resolver = new InternalResourceViewResolver(); // NOTE: this time I'm using InternalResourceViewResolver and again it does not matter :)
          resolver.setPrefix("");
          resolver.setSuffix("");
          return resolver;
        }
      
      }
      

      现在我们不再使用tomcat的JspServlet,而是编写自己的servlet(或重用一些现有的):

      public class StaticFilesServlet extends HttpServlet {
        @Override
        protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
          response.setCharacterEncoding("UTF-8");
      
          String resourcePath = request.getRequestURI();
          if (resourcePath != null) {
            FileReader reader = null;
            try {
              URL fileResourceUrl = request.getServletContext().getResource(resourcePath);
              String filePath = fileResourceUrl.getPath();
      
              if (!new File(filePath).exists()) {
                throw new IllegalArgumentException("Resource can not be found: " + filePath);
              }
              reader = new FileReader(filePath);
      
              int c = 0;
              while (c != -1) {
                c = reader.read();
                if (c != -1) {
                  response.getWriter().write(c);
                }
              }
      
            } finally {
              if (reader != null) {
                reader.close();
              }
            }
          }
        }
      }
      

      我们现在指示spring将所有对* .html的请求传递给我们的servlet

      public class AppInitializer implements WebApplicationInitializer {
      
        @Override
        public void onStartup(ServletContext servletContext) throws ServletException {
          WebApplicationContext context = new MyWebApplicationContext();
          servletContext.addListener(new ContextLoaderListener(context));
      
          addStaticHtmlFilesHandlingServlet(servletContext);
          addSpringDispatcherServlet(servletContext, context);
      
        }
      
       // (...)
      
        private void addStaticHtmlFilesHandlingServlet(ServletContext servletContext) {
          ServletRegistration.Dynamic servlet = servletContext.addServlet("HtmlsServlet", new StaticFilesServlet());
          servlet.setLoadOnStartup(1);
          servlet.addMapping("*.html");
      
        }
      
      }
      

      优点(或缺点,取决于你想要的)是jsp标签显然不会被处理。您的控制器看起来像往常一样:

      @Controller
      public class HomeController {
      
          @RequestMapping(value = "/", method = RequestMethod.GET)
          public ModelAndView home(Locale locale, Model model) {
              // (...)
      
              return new ModelAndView("/resources/home.html");
      
      }
      

      对于jsp:

      @Controller
      public class HomeController {
      
          @RequestMapping(value = "/", method = RequestMethod.GET)
          public ModelAndView home(Locale locale, Model model) {
              // (...)
      
              return new ModelAndView("/resources/home_dynamic.jsp");
      
      }
      

答案 1 :(得分:5)

Resolver类用于解析视图类的资源,依次查看类,从资源生成视图。例如,使用典型的InternalResourceViewResolver,如下所示:

<beans:bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
        <beans:property name="prefix" value="/WEB-INF/views/" />
        <beans:property name="suffix" value=".jsp" />
</beans:bean>

视图名称“home”将映射为“/WEB-INT/views/home.jsp”,然后使用视图类InternalResourceView(用于JSP)转换为JSP视图。如果用“.html”替换后缀值,Spring可以获取特定资源“/WEB-INT/views/home.html”,但不知道如何生成它。

答案 2 :(得分:4)

普通.html文件是静态的,不需要特殊的ViewResolver。您应该为html页面设置一个静态文件夹,如here所示。

例如:

<mvc:resources mapping="/static/**" location="/static/" />

答案 3 :(得分:2)

好吧,看来你没有设置视图的顺序

例如,如果你的项目有像jsp,json,velocity,freemarker等的视图,你可以使用它们(也许你需要新版本的spring,3.1 +),但是只有一个视图将是选择要呈现到客户端,这取决于您的视图的顺序,顺序越低,更喜欢视图

例如,你设置 jsp 视图的顺序是1,而freemarker视图的顺序是2,它们的视图名称都是“home”,spring会选择view.jsp(如果你将后缀设置为.jsp)。好吧,如果您的视图名称是“index”,则没有index.jsp而是index.ftl(假设您将freemarker的视图设置为.ftl),spring将选择后者。

示例代码使用spring的java配置,可以轻松转换为xml-style。

@Bean
public InternalResourceViewResolver jspViewResolver() {
    InternalResourceViewResolver jsp = new InternalResourceViewResolver();
    jsp.setOrder(4);
    jsp.setCache(true);
    jsp.setViewClass(org.springframework.web.servlet.view.JstlView.class);
    jsp.setPrefix("/WEB-INF/jsp/");
    jsp.setSuffix(".jsp");
    return jsp;
}

@Bean
public FreeMarkerViewResolver freeMarkerViewResolver() {
    FreeMarkerViewResolver viewResolver = new FreeMarkerViewResolver();
    viewResolver.setCache(true);
    viewResolver.setPrefix("");
    viewResolver.setSuffix(".ftl");
    viewResolver.setContentType(ViewConstants.MEDIA_TYPE_HTML);
    viewResolver.setRequestContextAttribute("request");
    viewResolver.setExposeSpringMacroHelpers(true);
    viewResolver.setExposeRequestAttributes(true);
    viewResolver.setExposeSessionAttributes(true);
    viewResolver.setOrder(2);
    return viewResolver;
}

请参阅 setOrder()方法!

json,jsonp和其他类型的视图可能会使用ontentNegotiation,你可以在spring的文档中找到它。

最后, html视图,我的意思是,完全静态文件,春季默认不支持。我想静态文件不需要java渲染。您可以使用以下代码使用静态映射:

<mvc:resources mapping="/static/**" location="/static/" />

或使用java config:

@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
    int cachePeriod = 3600 * 24 * 15;
    registry.addResourceHandler("/static/**").addResourceLocations("/static/").setCachePeriod(cachePeriod);
    registry.addResourceHandler("/favicon.ico").addResourceLocations("/").setCachePeriod(cachePeriod);
    registry.addResourceHandler("/robots.txt").addResourceLocations("/").setCachePeriod(cachePeriod);
}

在你的@RequestMapping方法中,你应该重定向它!

嗯,如果你不想重定向,只需将html视图设置为动态视图(freemark,velecity等),这样就行了! / p> 希望它有用!

答案 4 :(得分:1)

Spring MVC不允许您通过控制器呈现静态资源。正如Arun所说,它应该通过resources提供。

如果我错了,请纠正我,但似乎你想要一个index.html作为头版。要实现这一点,您应该将一个Controller(比如IndexController)映射到/index.html。然后,您应该在web.xml中配置您的欢迎文件index.html。这样,无论何时指向应用程序的根目录,容器都会查找“/index.html”,然后查找映射到/index.html URL的Controller。

所以,你的控制器应该是这样的:

@Controller
@RequestMapping("/index.html")
public class MyIndexController {
    @RequestMapping(method=RequestMethod.GET)
    protected String gotoIndex(Model model) throws Exception {      
        return "myLandingPage";
    }
}

在你的web.xml中

<welcome-file-list>
<welcome-file>index.html</welcome-file>
</welcome-file-list>

希望这有帮助。

答案 5 :(得分:0)

我认为InternalResourceViewResolver支持servlet和jsp文件。根据Spring的API javadocs的后缀是“在构建URL时附加到视图名称”。它不是文件的扩展名,即使它非常具有误导性。我查看了UrlBasedViewResolver setSuffix()课程。

可能如果他们将其命名为viewSuffix,我想它可能更有意义。

答案 6 :(得分:0)

您遇到此问题,因为可能没有为映射* .html注册任何servlet。
因此调用以“默认servlet”结束,该默认servlet是使用/可能是DispatcherServlet的servlet映射注册的。
现在,Dispatcher servlet找不到一个控制器来处理home.html的请求,从而找到你看到的消息。
要解决此问题,您可以注册* .html扩展名以由JSPServlet处理,然后它应该干净地工作。