web-dev-qa-db-ja.com

Spring MVCでリクエストをHTMLファイルにマップする方法は?

基本的な設定ファイルは直感的ではありません。

単純な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プロパティの意味

[〜#〜] update [〜#〜]

私のコントローラーは次のとおりです。ご覧のとおり、ファイル拡張子は含まれていません

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

}
25
Suzan Cioc

問題の背景

最初に理解することは次のとおりです。jspファイルをレンダリングするのは春ではありません。それを行うのはJspServlet(org.Apache.jasper.servlet.JspServlet)です。このサーブレットには、SpringではなくTomcat(jasperコンパイラ)が付属しています。このJspServletは、jspページをコンパイルする方法と、それをHTMLテキストとしてクライアントに返す方法を認識しています。 TomcatのJspServletはデフォルトで、*。jspと* .jspxの2つのパターンに一致するリクエストのみを処理します。

スプリングがInternalResourceView(またはJstlView)を使用してビューをレンダリングすると、次の3つのことが実際に行われます。

  1. モデルからすべてのモデルパラメーターを取得します(コントローラーハンドラーメソッドによって返されます。つまり、"public ModelAndView doSomething() { return new ModelAndView("home") }"
  2. これらのモデルパラメーターを要求属性として公開する(JspServletで読み取れるようにするため)
  3. 要求をJspServletに転送します。 RequestDispatcherは、各* .jspリクエストをJspServletに転送する必要があることを知っています(これがデフォルトのTomcatの構成であるため)

ビュー名を単にhome.htmlに変更すると、Tomcatはnotリクエストの処理方法を認識しません。これは、*。htmlリクエストを処理するサーブレットがないためです。

ソリューション

これを解決する方法。最も明白な解決策は3つあります。

  1. htmlをリソースファイルとして公開する
  2. * .htmlリクエストも処理するようにJspServletに指示します
  3. 独自のサーブレットを作成します(または別の既存のサーブレットリクエストを* .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;
  }

}

上記の例では、バッキングビュークラスJstlViewでUrlBasedViewResolverを使用していますが、例のようにInternalResourceViewResolverを使用してもかまいません。

上記の例は、.jspで終わるjspファイルを処理するビューリゾルバを1つだけ使用してアプリケーションを構成します。注:冒頭で述べたように、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にアクセスすると同様の方法でアクセスできますが、サーバー上の実際のパスを使用する必要があります。パスはnotは/ 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;
  }

}

ここで、*。htmlファイルも処理するためにJspServletを追加します。

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の方法でやりたい場合。 * .htmlリクエストを処理するにはjspサーブレットを登録する必要があるため、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の実装

3番目のソリューションは、ソリューション1とソリューション2の組み合わせです。したがって、ここでは、*。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;
  }

}

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();
        }
      }
    }
  }
}

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

}
30
walkeros

リゾルバークラスは、ビュークラスのリソースを解決するために使用され、ビュークラスはリソースからビューを生成します。たとえば、次のような典型的な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」を取得できますが、その生成方法はわかりません。

5
Megan Nguyen

プレーンな.htmlファイルは静的であり、特別なViewResolverは必要ありません。 here のように、htmlページ用の静的フォルダーを設定する必要があります。

例えば:

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

さて、あなたはしなかったようですビューの順序を設定

たとえば、プロジェクトにjsp、json、velocity、freemarkerなどのビューがある場合は、それらすべてを使用できます(新しいバージョンのspring、3.1以降が必要な場合があります)が、1つのビューのみが選択されますrenderクライアントへ、これはビューの順序に依存します順序が低いほど、ビューを優先します

たとえば、jsp viewの順序は1、freemarker viewの順序は2に設定します。両方のビュー名は「home」で、春はview.jspを選択します(設定した場合.jspの接尾辞)。ビュー名が「index」で、index.jspではなくindex.ftl(freemarkerのビューを.ftlに設定した場合)の場合、springは後者を選択します。

spring 's 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 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メソッドでは、リダイレクト it!

まあ、リダイレクトが必要ない場合、htmlビューをdynamicビュー(フリーマーク、速度など)に設定するだけです。

役に立つことを願っています!

2
longkai

Spring MVCでは、コントローラー上で静的リソースをレンダリングすることはできません。アルンが言ったように、それはresourcesを通して提供されるべきです。

間違っている場合は修正してください。ただし、index.htmlがフロントページとして必要なようです。これを実現するには、/index.htmlにマップされたコントローラー(IndexControllerなど)が必要です。次に、web.xmlでウェルカムファイルがindex.htmlであると言うように設定する必要があります。この方法では、アプリケーションのルートをポイントするたびに、コンテナは「/index.html」を探し、次に/index.html URLにマップされたコントローラーを探します。

したがって、コントローラーは次のようになります。

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

お役に立てれば。

1
gnc

InternalResourceViewResolver はサーブレットとjspファイルをサポートすると思います。 SpringのAPI javadocsによるサフィックスは、「URLを作成するときにビュー名に追加される」ものです。非常に誤解を招くものですが、ファイルの拡張子ではありません。 rlBasedViewResolver setSuffix() クラスをチェックしました。

多分彼らがそれをviewSuffixと名付ければ、もっと理にかなっているかもしれません。

0
Indu Devanath

この問題は、*。htmlのマッピング用に登録されているサーブレットがない可能性があるためです。
したがって、呼び出しは「//」のサーブレットマッピングで登録された「デフォルトサーブレット」になります。これはおそらくDispatcherServletです。
現在、Dispatcherサーブレットは、home.htmlのリクエストを処理するコントローラーを検出していないため、表示されているメッセージを検出しません。
この問題を解決するには、*。html拡張子を登録してJSPServletで処理すると、正常に機能するはずです。

0
Renganathan V