Ответ 1
Предыстория проблемы
Первое, что нужно понять, следующее: это НЕ spring, который отображает файлы jsp. Это делает JspServlet (org.apache.jasper.servlet.JspServlet). Этот сервлет поставляется с компилятором Tomcat (jasper), а не с spring. Этот JspServlet знает, как скомпилировать jsp-страницу и как вернуть ее в виде html-текста клиенту. JspServlet в tomcat по умолчанию обрабатывает запросы, соответствующие двум шаблонам: *.jsp и *.jspx.
Теперь, когда spring отображает представление с помощью InternalResourceView
(или JstlView
), три вещи действительно имеют место:
- получить все параметры модели из модели (возвращается методом обработчика контроллера, т.е.
"public ModelAndView doSomething() { return new ModelAndView("home") }"
) - выставить эти параметры модели в качестве атрибутов запроса (чтобы их можно было прочитать JspServlet)
- запрос вперед в JspServlet.
RequestDispatcher
знает, что каждый запрос *.jsp должен быть перенаправлен в JspServlet (поскольку это настройка по умолчанию tomcat)
Когда вы просто меняете имя вида на home.html, tomcat не будет знать, как обрабатывать запрос. Это связано с тем, что обработчики сервлета не обрабатываются *.html.
Решение
Как это решить. Существует три наиболее очевидных решения:
- выставить html в качестве файла ресурсов
- поручить JspServlet также обрабатывать запросы *.html
- напишите свой собственный сервлет (или перейдите к другим существующим запросам сервлетов в *.html).
Начальная конфигурация (только обработка jsp)
Сначала предположим, что мы конфигурируем spring без XML файлов (только на основе аннотации @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/, сопоставляется с каталогом ресурсов в вашем веб-каталоге. Поэтому, если вы поместили свой каталог 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 для обработки также статических файлов. Как следствие, вы можете использовать теги jsp в ваших html файлах:) Конечно, ваш выбор, если вы это сделаете или нет. Скорее всего, вы хотите использовать простой html, поэтому просто не используйте теги jsp, и контент будет подан так, как если бы он был статическим html.
Сначала мы удаляем префикс и суффикс для вида 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;
}
}
Теперь мы добавляем 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");
}
}
Важно, чтобы для этого класса вам нужно было добавить jasper.jar из вашей установки tomcat только для времени компиляции. Если у вас есть приложение maven, это реально легко, используя 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. Вам нужно будет зарегистрировать сервлет ssp для обработки запросов *.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 на другой сервлет. Вы можете написать свой собственный или найти хорошего кандидата уже существующего сервлета.
Как указано выше, мы очищаем префикс и суффикс для распознавателя вида:
@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();
}
}
}
}
}
Теперь мы проинструктируем spring передать все запросы в *.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");
}
@Controller
public class HomeController {
@RequestMapping(value = "/", method = RequestMethod.GET)
public ModelAndView home(Locale locale, Model model) {
// (...)
return new ModelAndView("/resources/home_dynamic.jsp");
}