generation - Cómo evitar la excepción "Ruta de vista circular" con la prueba Spring MVC
circular view path error spring boot (12)
Tengo el siguiente código en uno de mis controladores:
@Controller
@RequestMapping("/preference")
public class PreferenceController {
@RequestMapping(method = RequestMethod.GET, produces = "text/html")
public String preference() {
return "preference";
}
Simplemente estoy tratando de probarlo usando la prueba Spring MVC de la siguiente manera:
@ContextConfiguration
@WebAppConfiguration
@RunWith(SpringJUnit4ClassRunner.class)
public class PreferenceControllerTest {
@Autowired
private WebApplicationContext ctx;
private MockMvc mockMvc;
@Before
public void setup() {
mockMvc = webAppContextSetup(ctx).build();
}
@Test
public void circularViewPathIssue() throws Exception {
mockMvc.perform(get("/preference"))//
.andDo(print());
}
Recibo la siguiente excepción:
Ruta de vista circular [preferencia]: se volvería a enviar a la URL del controlador actual [/ preference]. ¡Comprueba tu configuración de ViewResolver! (Sugerencia: puede ser el resultado de una vista no especificada, debido a la generación de nombre de vista predeterminada).
Lo que me parece extraño es que funciona bien cuando cargo la configuración de contexto "completa" que incluye la plantilla y los resolvedores de vista como se muestra a continuación:
<bean class="org.thymeleaf.templateresolver.ServletContextTemplateResolver" id="webTemplateResolver">
<property name="prefix" value="WEB-INF/web-templates/" />
<property name="suffix" value=".html" />
<property name="templateMode" value="HTML5" />
<property name="characterEncoding" value="UTF-8" />
<property name="order" value="2" />
<property name="cacheable" value="false" />
</bean>
Soy muy consciente de que el prefijo agregado por la resolución de la plantilla garantiza que no haya una "ruta de vista circular" cuando la aplicación utiliza esta resolución de plantilla.
Pero ¿cómo se supone que debo probar mi aplicación usando la prueba Spring MVC? Alguien tiene alguna pista?
Al usar la anotación @Controller
, necesita las anotaciones @RequestMapping
y @ResponseBody
. Vuelve a intentarlo luego de agregar la anotación @ResponseBody
Aquí hay una solución fácil si no te importa renderizar la vista.
Cree una subclase de InternalResourceViewResolver que no compruebe las rutas de vista circular:
public class StandaloneMvcTestViewResolver extends InternalResourceViewResolver {
public StandaloneMvcTestViewResolver() {
super();
}
@Override
protected AbstractUrlBasedView buildView(final String viewName) throws Exception {
final InternalResourceView view = (InternalResourceView) super.buildView(viewName);
// prevent checking for circular view paths
view.setPreventDispatchLoop(false);
return view;
}
}
Luego configura tu prueba con ella:
MockMvc mockMvc;
@Before
public void setUp() {
final MyController controller = new MyController();
mockMvc =
MockMvcBuilders.standaloneSetup(controller)
.setViewResolvers(new StandaloneMvcTestViewResolver())
.build();
}
Así es como resolví este problema:
@Before
public void setup() {
InternalResourceViewResolver viewResolver = new InternalResourceViewResolver();
viewResolver.setPrefix("/WEB-INF/jsp/view/");
viewResolver.setSuffix(".jsp");
mockMvc = MockMvcBuilders.standaloneSetup(new HelpController())
.setViewResolvers(viewResolver)
.build();
}
Esto está sucediendo porque Spring está eliminando "preferencia" y añadiendo nuevamente la "preferencia" haciendo la misma ruta que la solicitud Uri.
Pasando así: solicite Uri: "/ preferencia"
eliminar "preferencia": "/"
anexar ruta: "/" + "preferencia"
cadena final: "/ preferencia"
Esto se está metiendo en un ciclo que Spring te notifica lanzando una excepción.
Lo mejor es que le interese dar un nombre de vista diferente como "preferenceView" o cualquier cosa que desee.
Esto no tiene nada que ver con las pruebas Spring MVC.
Cuando no declara un ViewResolver
, Spring registra un InternalResourceViewResolver
predeterminado que crea instancias de JstlView
para renderizar la View
.
La clase JstlView
extiende InternalResourceView
que es
Wrapper para un JSP u otro recurso dentro de la misma aplicación web. Expone objetos de modelo como atributos de solicitud y reenvía la solicitud a la URL de recurso especificada utilizando javax.servlet.RequestDispatcher.
Se supone que una URL para esta vista especifica un recurso dentro de la aplicación web, adecuado para el método forward o include de RequestDispatcher.
Bold es mío. En otras palabras, la vista, antes de la representación, intentará obtener un RequestDispatcher
al que forward()
. Antes de hacer esto, comprueba lo siguiente
if (path.startsWith("/") ? uri.equals(path) : uri.equals(StringUtils.applyRelativePath(uri, path))) {
throw new ServletException("Circular view path [" + path + "]: would dispatch back " +
"to the current handler URL [" + uri + "] again. Check your ViewResolver setup! " +
"(Hint: This may be the result of an unspecified view, due to default view name generation.)");
}
donde path
es el nombre de la vista, lo que devolvió del @Controller
. En este ejemplo, eso es preference
. La variable uri
contiene el uri de la solicitud que se maneja, que es /context/preference
.
El código anterior se da cuenta de que si tuviera que reenviar a /context/preference
, el mismo servlet (ya que el mismo manejaba el anterior) manejaría la solicitud y entraría en un ciclo sin fin.
Cuando declara un ThymeleafViewResolver
y un ServletContextTemplateResolver
con un prefix
y suffix
específicos, construye la View
diferente, dándole una ruta como
WEB-INF/web-templates/preference.html
ThymeleafView
instancias de ThymeleafView
ubican el archivo relativo a la ruta ServletContext
utilizando un ServletContextResourceResolver
templateInputStream = resourceResolver.getResourceAsStream(templateProcessingParameters, resourceName);`
que eventualmente
return servletContext.getResourceAsStream(resourceName);
Esto obtiene un recurso que es relativo a la ruta ServletContext
. Luego puede usar TemplateEngine
para generar el HTML. No hay forma de que un ciclo sin fin pueda suceder aquí.
Estoy usando Spring Boot para intentar cargar una página web, no para probar, y tuve este problema. Mi solución fue un poco diferente a las anteriores considerando las circunstancias ligeramente diferentes. (Aunque esas respuestas me ayudaron a entenderlo.)
Simplemente tuve que cambiar mi dependencia de arranque Spring Boot en Maven desde:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
a:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
Simplemente cambiando la ''web'' a ''thymeleaf'' me arregló el problema.
Otro enfoque simple:
package org.yourpackagename;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.builder.SpringApplicationBuilder;
import org.springframework.boot.context.web.SpringBootServletInitializer;
@SpringBootApplication
public class Application extends SpringBootServletInitializer {
@Override
protected SpringApplicationBuilder configure(SpringApplicationBuilder application) {
return application.sources(PreferenceController.class);
}
public static void main(String[] args) {
SpringApplication.run(PreferenceController.class, args);
}
}
Para Thymeleaf:
Acabo de empezar a usar la primavera 4 y la hoja de tomillo, cuando encontré este error, se resolvió añadiendo:
<bean class="org.thymeleaf.spring4.view.ThymeleafViewResolver">
<property name="templateEngine" ref="templateEngine" />
<property name="order" value="0" />
</bean>
Resolví este problema usando @ResponseBody como a continuación:
@RequestMapping(value = "/resturl", method = RequestMethod.GET, produces = {"application/json"})
@ResponseStatus(HttpStatus.OK)
@Transactional(value = "jpaTransactionManager")
public @ResponseBody List<DomainObject> findByResourceID(@PathParam("resourceID") String resourceID) {
Si está utilizando Spring Boot, agregue la dependencia de thymeleaf en su pom.xml:
<dependency>
<groupId>org.thymeleaf</groupId>
<artifactId>thymeleaf-spring4</artifactId>
<version>2.1.6.RELEASE</version>
</dependency>
Utilizo la anotación para configurar la aplicación web Spring, el problema se solucionó agregando un bean InternalResourceViewResolver
a la configuración. Espero que sea útil.
@Configuration
@EnableWebMvc
@ComponentScan(basePackages = { "com.example.springmvc" })
public class WebMvcConfig extends WebMvcConfigurerAdapter {
@Bean
public InternalResourceViewResolver internalResourceViewResolver() {
InternalResourceViewResolver resolver = new InternalResourceViewResolver();
resolver.setPrefix("/jsp/");
resolver.setSuffix(".jsp");
return resolver;
}
}
@Controller
→ @RestController
Tuve el mismo problema y noté que mi controlador también estaba anotado con @Controller
. Reemplazarlo con @RestController
resolvió el problema. Aquí está la explicación de Spring Web MVC :
@RestController es una anotación compuesta que está meta-anotada con @Controller y @ResponseBody que indica un controlador cuyos métodos heredan la anotación @ResponseBody de nivel de tipo y, por lo tanto, escribe directamente en el cuerpo de respuesta frente a la resolución y representación de la vista con una plantilla HTML.