如何在 Spring MVC 中将请求映射到 HTML 文件? [英] How to map requests to HTML file in Spring MVC?

查看:103
本文介绍了如何在 Spring MVC 中将请求映射到 HTML 文件?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

Basic configuration files looks unintuitive.

如果我创建一个简单的 hello world 示例,然后将 home.jsp 重命名为 home.html 并编辑 servlet-context.xml 文件来自

If I create simple hello world example, and then rename home.jsp to home.html and edit servlet-context.xml file from

<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 属性是什么意思?

Why? What suffix property means?

更新

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

My controller is follows. As you see it does not contain file extension

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

}

推荐答案

问题背景

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

First thing to understand is following: it is NOT spring which renders the jsp files. It is JspServlet (org.apache.jasper.servlet.JspServlet) which does it. This servlet comes with Tomcat (jasper compiler) not with spring. This JspServlet is aware how to compile jsp page and how to return it as html text to the client. The JspServlet in tomcat by default only handles requests matching two patterns: *.jsp and *.jspx.

现在,当 spring 使用 InternalResourceView(或 JstlView)渲染视图时,会发生三件事:

Now when spring renders the view with InternalResourceView (or JstlView), three things really takes place:

  1. 从模型中获取所有模型参数(由您的控制器处理程序方法返回,即 "public ModelAndView doSomething() { return new ModelAndView("home") }")
  2. 将这些模型参数作为请求属性公开(以便 JspServlet 可以读取)
  3. 将请求转发到 JspServlet.RequestDispatcher 知道每个 *.jsp 请求都应该转发到 JspServlet(因为这是 tomcat 的默认配置)
  1. get all the model parameters from model (returned by your controller handler method i.e. "public ModelAndView doSomething() { return new ModelAndView("home") }")
  2. expose these model parameters as request attributes (so that it can be read by JspServlet)
  3. forward request to JspServlet. RequestDispatcher knows that each *.jsp request should be forwarded to JspServlet (because this is default tomcat's configuration)

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

When you simply change the view name to home.html tomcat will not know how to handle the request. This is because there is no servlet handling *.html requests.

解决方案

如何解决这个问题.最明显的解决方案有以下三种:

How to solve this. There are three most obvious solutions:

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

初始配置(只处理jsp)

首先假设我们在没有xml文件的情况下配置spring(仅基于@Configuration注解和spring的WebApplicationInitializer接口).

First let's assume we configure spring without xml files (only basing on @Configuration annotation and spring's WebApplicationInitializer interface).

基本配置如下

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,因为在您的示例中这无关紧要.

In above example I'm using UrlBasedViewResolver with backing view class JstlView, but you can use InternalResourceViewResolver as in your example it does not matter.

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

Above example configures application with only one view resolver which handles jsp files ending with .jsp. NOTE: As stated in the beginning JstlView really uses tomcat's RequestDispatcher to forward the request to JspSevlet to compile the jsp to html.

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

Implementation on solution 1 - expose the html as a resource file:

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

We modify the WebConfig class to add new resources matching. Also we need to modify the jstlViewResolver so that it does not take neither prefix nor suffix:

@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 文件将被提供.要由您的控制器处理此问题,您需要返回资源的完整路径:

By adding this we say that every that every request going to http://my.server/someurl/resources/ is mapped to resources directory under your web directory. So if you put your home.html in resources directory and point your browser to http://my.server/someurl/resources/home.html the file will be served. To handle this by your controller you then return the full path to the resource:

@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放在同一个resources目录下你可以用类似的方式访问它,但你需要使用服务器上的实际路径.该路径以/someurl/开头,因为这是仅适用于以 .html 结尾的 html 资源的映射).在这种情况下,jsp 是动态资源,最终由 JspServlet 使用磁盘上的实际路径访问.所以访问jsp的正确方法是:

If you place in the same directory some jsp files (not only *.html files), say home_dynamic.jsp in the same resources directory you can access it similar way, but you need to use the actual path on the server. The path does not start with /someurl/ because this is the mapping only for html resources ending with .html). In this context jsp is dynamic resource which in the end is accessed by JspServlet using actual path on disk. So correct way to access the jsp is:

@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 的配置中实现这一点,您需要使用:

To achieve this in xml based config you need to use:

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

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

and modify your jstl view resolver:

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

In this option we use the tomcat's JspServlet to handle also static files. As a consequence you can use jsp tags in your html files:) It's of course your choice if you do it or not. Most probably you want to use plain html so simply do not use jsp tags and the content will be served as if it was static html.

首先我们删除前面例子中视图解析器的前缀和后缀:

First we delete prefix and suffix for view resolver as in previous example:

@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 文件:

Now we add JspServlet for handling also *.html files:

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 中的依赖项如下所示:

Important is that to make this class available you need to add the jasper.jar from your tomcat's installation just for compilation time. If you have maven app this is realtively easy by using the scope=provided for the jar. The dependency in maven will look like:

<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

If you want to do it in xml way. You would need to register jsp servlet to handle *.html requests, so you need to add following entry to your 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/"额外映射.您的控制器将如下所示:

Now in your controller you can access both html and jsp files just like in previous example. The advantage is that there is no "/someurl/" extra mapping which was needed in Solution 1. Your controller will look like:

@Controller
public class HomeController {

    @RequestMapping(value = "/", method = RequestMethod.GET)
    public ModelAndView home(Locale locale, Model model) {
        // (...)

        return new ModelAndView("/resources/home.html"); 

}

指向你的jsp你所做的完全一样:

To point to your jsp you are doing exactly the same:

@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 的一些好的候选者.

Third solution is somewhat a combination of solution 1 and solution 2. So in here we want to pass all the requests to *.html to some other servlet. You can write your own or look for some good candidate of already existing servlet.

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

As above first we clean up the prefix and suffix for the view resolver:

@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(或重用一些现有的):

Now instead of using the tomcat's JspServlet we write our own servlet (or reuse some existing):

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

We now instruct the spring to pass all requests to *.html to our 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标签显然不会被处理.您的控制器看起来像往常一样:

The advantage (or disadvantage, depends on what you want) is that jsp tags will obviously not be processed. You controller looks as usual:

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

}

这篇关于如何在 Spring MVC 中将请求映射到 HTML 文件?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆