Как отобразить запросы в HTML-файл в Spring MVC?
Основные конфигурационные файлы выглядят не интуитивно.
Если я создам простой пример Привет мир, а затем переименовать 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
собственность значит?
ОБНОВИТЬ
Мой контроллер выглядит следующим образом. Как вы видите, он не содержит расширение файла
@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";
}
}
7 ответов
Предыстория проблемы
Первое, что нужно понять, это следующее: это не весна, которая рендерит файлы jsp. Это делает JspServlet (org.apache.jasper.servlet.JspServlet). Этот сервлет поставляется с Tomcat (Jasper Compiler), а не с пружиной. Этот JspServlet знает, как скомпилировать страницу jsp и как вернуть ее в виде HTML-текста клиенту. JspServlet в tomcat по умолчанию обрабатывает только запросы, соответствующие двум шаблонам: *.jsp и *.jspx.
Теперь, когда весна делает вид с 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 и интерфейса SpringApplicationInitializer).
Базовая конфигурация будет следующей
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:
В этом варианте мы используем JspServlet томата для обработки также статических файлов. Как следствие, вы можете использовать теги jsp в ваших html-файлах:) Это, конечно, ваш выбор, делать это или нет. Скорее всего, вы хотите использовать обычный 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");
}
}
Важно, чтобы сделать этот класс доступным, вам нужно добавить jasper.jar из инсталляции вашего tomcat только на время компиляции. Если у вас есть приложение Maven, это действительно легко с помощью области видимости, предусмотренной для банки. Зависимость в 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 для обработки *.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/", которое требуется в решении 1. Ваш контроллер будет выглядеть так:
@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;
}
}
Теперь вместо использования JspServlet tomcat мы напишем наш собственный сервлет (или повторно используем некоторые существующие):
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");
}
Класс Resolver используется для разрешения ресурсов класса представления, в свою очередь, просмотра класса, генерирования представлений из ресурсов. Например, с типичным 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", а затем переведено в представление JSP с использованием класса представления InternalResourceView (который предназначен для JSP). Если вы замените значение суффикса на ".html", Spring может получить конкретный ресурс "/WEB-INT/views/home.html", но не знает, как его сгенерировать.
Простые файлы.html являются статическими и не нуждаются в специальном ViewResolver. Вы должны настроить статическую папку для ваших HTML-страниц, как показано здесь.
Например:
<mvc:resources mapping="/static/**" location="/static/" />
Ну, похоже, вы не установили порядок представления.
например, если у вашего проекта есть такие представления, как jsp, json, speed, freemarker и т. д., вы можете использовать их все (возможно, вам нужна новая версия spring, 3.1+), но для визуализации на клиенте будет выбран только один вид, это зависит от порядка вашего представления, чем ниже порядок, тем предпочтительнее представление.
например, вы устанавливаете порядок представления jsp равным 1, а порядок представления свободного маркера равен 2, оба их имени представления являются "home", пружина выберет view.jsp(если вы установите суффикс в.jsp). Что ж, если ваше имя представления "index", не index.jsp, а index.ftl(предположим, вы установили представление freemarker в.ftl), Spring выберет позже.
Пример кода, используя Java-конфигурацию Spring, вы можете легко преобразовать в 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, я имею в виду полностью статические файлы, которые по умолчанию не поддерживаются. Я полагаю, статический файл не нужно визуализировать с помощью 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 и т. д.), что будет хорошо!
надеюсь, что это полезно!
Spring MVC не позволяет отображать статические ресурсы через контроллеры. Как сказал Арун, это должно быть подано через resources
,
Поправьте меня, если я ошибаюсь, но, кажется, вы хотите index.html
в качестве первой страницы. Для достижения этого у вас должен быть Controller (скажем, IndexController), сопоставленный с /index.html
, Затем вы должны настроить в своем 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>
Надеюсь это поможет.
Я думаю, что InternalResourceViewResolver поддерживает сервлеты и файлы JSP. Суффикс согласно javadocs API Spring - это то, что "добавляется для просмотра имен при создании URL". Это не расширение файла, хотя это очень вводит в заблуждение. Я проверил на классе UrlBasedViewResolver setSuffix().
Вероятно, если они назовут его как viewSuffix, это может иметь больше смысла, я думаю.
У вас есть эта проблема, потому что не может быть никакого зарегистрированного сервлета для отображения *.html.
таким образом, вызов заканчивается "сервлетом по умолчанию", который зарегистрирован с помощью сервлет-отображения /, что, вероятно, ваш DispatcherServlet.
Теперь сервлет Dispatcher не находит контроллер для обработки запроса home.html и, следовательно, сообщения, которое вы видите.
Чтобы решить эту проблему, вы можете зарегистрировать расширение *.html, которое будет обрабатываться JSPServlet, и тогда оно должно работать без ошибок.