25

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

如果我创建简单的 hello world 示例,然后重命名home.jsphome.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";
    }

}
4

7 回答 7

32

问题的背景

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

InternalResourceView现在,当 spring 用(or )渲染视图时JstlView,真正发生了三件事:

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

当您简单地将视图名称更改为 home.html 时,tomcat 将知道如何处理请求。这是因为没有 servlet 处理 *.html 请求。

解决方案

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

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

初始配置(只处理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 prefix 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=provided 非常容易。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 文件,就像在前面的示例中一样。优点是解决方案 1 中不需要“/someurl/”额外映射。您的控制器将如下所示:

@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;
  }

}

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

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");

}
于 2015-09-17T10:53:51.657 回答
5

解析器类用于解析视图类的资源,视图类依次从资源中生成视图。例如,一个典型的 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”,但不知道如何生成。

于 2013-06-13T04:40:26.463 回答
4

Plain .html files are static and do not need a special ViewResolver. You should set up a static folder for your html pages as shown here.

For example:

<mvc:resources mapping="/static/**" location="/static/" />
于 2013-12-03T10:25:19.257 回答
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 config,您可以轻松转换为 xml 样式。

@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 视图完全是静态文件,spring 默认不支持。我想静态文件不需要由 java 渲染。您可以使用以下代码使用静态映射:

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

或使用 java 配置:

@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 等),就可以了!

希望它有用!

于 2014-02-08T14:05:00.070 回答
1

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

如果我错了,请纠正我,但您似乎想要一个index.html作为首页。要实现这一点,您应该有一个控制器(比如 IndexController)映射到/index.html. 然后,你应该在你web.xml的哟中配置你的欢迎文件是index.html. 这样,每当您指向应用程序的根目录时,您的容器都会查找“/index.html”,然后会查找映射到/index.htmlURL 的控制器。

因此,您的控制器应如下所示:

@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>

希望这可以帮助。

于 2014-01-17T03:55:03.210 回答
0

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

于 2014-02-18T05:35:25.443 回答
0

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

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

于 2013-05-17T03:18:07.047 回答