問題の背景
最初に理解しておくべきことは次のとおりです。jsp ファイルをレンダリングするのはスプリングではありません。それを行うのが JspServlet (org.apache.jasper.servlet.JspServlet) です。このサーブレットには、Spring ではなく Tomcat (jasper コンパイラ) が付属しています。この JspServlet は、jsp ページをコンパイルする方法と、それを HTML テキストとしてクライアントに返す方法を認識しています。デフォルトでは、Tomcat の JspServlet は、*.jsp と *.jspx の 2 つのパターンに一致するリクエストのみを処理します。
spring がInternalResourceView
(またはJstlView
) を使用してビューをレンダリングすると、次の 3 つのことが実際に行われます。
- モデルからすべてのモデル パラメーターを取得します (コントローラー ハンドラー メソッドによって返されます
"public ModelAndView doSomething() { return new ModelAndView("home") }"
) 。
- これらのモデル パラメータをリクエスト属性として公開します (JspServlet で読み取れるようにするため)。
- リクエストを JspServlet に転送します。
RequestDispatcher
各 *.jsp リクエストを JspServlet に転送する必要があることを認識します (これがデフォルトの tomcat の構成であるため)
ビュー名を単に home.html に変更すると、Tomcat はリクエストの処理方法を認識しません。これは、*.html 要求を処理するサーブレットがないためです。
解決
これを解決する方法。最も明白な解決策は次の 3 つです。
- html をリソース ファイルとして公開する
- *.html リクエストも処理するように JspServlet に指示する
- 独自のサーブレットを作成します (または、*.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
. 注: 冒頭で述べたように、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 ディレクトリの下の resources ディレクトリにマップされると言います。したがって、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 を同様の方法でアクセスできますが、サーバー上の実際のパスを使用する必要があります。これは .html で終わる html リソースのみのマッピングであるため、パスは/someurl/ で始まりません)。このコンテキストでは、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= を使用すると、これは非常に簡単です。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");
}