问题
I have the following code in one of my controllers:
@Controller
@RequestMapping(\"/preference\")
public class PreferenceController {
@RequestMapping(method = RequestMethod.GET, produces = \"text/html\")
public String preference() {
return \"preference\";
}
}
I am simply trying to test it using Spring MVC test as follows:
@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());
}
}
I am getting the following exception:
Circular view path [preference]: would dispatch back to the current handler URL [/preference] again. Check your ViewResolver setup! (Hint: This may be the result of an unspecified view, due to default view name generation.)
What I find strange is that it works fine when I load the \"full\" context configuration that includes the template and view resolvers as shown below:
<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>
I am well aware that the prefix added by the template resolver ensures that there are not \"circular view path\" when the app uses this template resolver.
But then how I am supposed to test my app using Spring MVC test? Has anyone got any clue?
回答1:
This has nothing to do with Spring MVC testing.
When you don't declare a ViewResolver
, Spring registers a default InternalResourceViewResolver
which creates instances of JstlView
for rendering the View
.
The JstlView
class extends InternalResourceView
which is
Wrapper for a JSP or other resource within the same web application. Exposes model objects as request attributes and forwards the request to the specified resource URL using a javax.servlet.RequestDispatcher.
A URL for this view is supposed to specify a resource within the web application, suitable for RequestDispatcher's forward or include method.
Bold is mine. In otherwords, the view, before rendering, will try to get a RequestDispatcher
to which to forward()
. Before doing this it checks the following
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.)");
}
where path
is the view name, what you returned from the @Controller
. In this example, that is preference
. The variable uri
holds the uri of the request being handled, which is /context/preference
.
The code above realizes that if you were to forward to /context/preference
, the same servlet (since the same handled the previous) would handle the request and you would go into an endless loop.
When you declare a ThymeleafViewResolver
and a ServletContextTemplateResolver
with a specific prefix
and suffix
, it builds the View
differently, giving it a path like
WEB-INF/web-templates/preference.html
ThymeleafView
instances locate the file relative to the ServletContext
path by using a
ServletContextResourceResolver
templateInputStream = resourceResolver.getResourceAsStream(templateProcessingParameters, resourceName);`
which eventually
return servletContext.getResourceAsStream(resourceName);
This gets a resource that is relative to the ServletContext
path. It can then use the TemplateEngine
to generate the HTML. There's no way an endless loop can happen here.
回答2:
I solved this problem by using @ResponseBody like below:
@RequestMapping(value = "/resturl", method = RequestMethod.GET, produces = {"application/json"})
@ResponseStatus(HttpStatus.OK)
@Transactional(value = "jpaTransactionManager")
public @ResponseBody List<DomainObject> findByResourceID(@PathParam("resourceID") String resourceID) {
回答3:
@Controller
→ @RestController
I had the same issue and I noticed that my controller was also annotated with @Controller
. Replacing it with @RestController
solved the issue. Here is the explanation from Spring Web MVC:
@RestController is a composed annotation that is itself meta-annotated with @Controller and @ResponseBody indicating a controller whose every method inherits the type-level @ResponseBody annotation and therefore writes directly to the response body vs view resolution and rendering with an HTML template.
回答4:
This is how I solved this problem:
@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();
}
回答5:
Here's an easy fix if you don't actually care about rendering the view.
Create a subclass of InternalResourceViewResolver which doesn't check for circular view paths:
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;
}
}
Then set up your test with it:
MockMvc mockMvc;
@Before
public void setUp() {
final MyController controller = new MyController();
mockMvc =
MockMvcBuilders.standaloneSetup(controller)
.setViewResolvers(new StandaloneMvcTestViewResolver())
.build();
}
回答6:
If you are using Spring Boot, then add thymeleaf dependency into your pom.xml:
<dependency>
<groupId>org.thymeleaf</groupId>
<artifactId>thymeleaf-spring4</artifactId>
<version>2.1.6.RELEASE</version>
</dependency>
回答7:
I am using Spring Boot to try and load a webpage, not test, and had this problem. My solution was a bit different than those above considering the slightly different circumstances. (although those answers helpled me understand.)
I simply had to change my Spring Boot starter dependency in Maven from:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
to:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
Just changing the 'web' to 'thymeleaf' fixed the problem for me.
回答8:
Adding /
after /preference
solved the problem for me:
@Test
public void circularViewPathIssue() throws Exception {
mockMvc.perform(get("/preference/"))
.andDo(print());
}
回答9:
In my case, I was trying out Kotlin + Spring boot and I got into the Circular View Path issue. All the suggestions I got online could not help, until I tried the below:
Originally I had annotated my controller using @Controller
import org.springframework.stereotype.Controller
I then replaced @Controller
with @RestController
import org.springframework.web.bind.annotation.RestController
And it worked.
回答10:
I am using Spring Boot with Thymeleaf. This is what worked for me. There are similar answers with JSP but note that I am using HTML, not JSP, and these are in the folder src/main/resources/templates
like in a standard Spring Boot project as explained here. This could also be your case.
@InjectMocks
private MyController myController;
@Before
public void setup()
{
MockitoAnnotations.initMocks(this);
this.mockMvc = MockMvcBuilders.standaloneSetup(myController)
.setViewResolvers(viewResolver())
.build();
}
private ViewResolver viewResolver()
{
InternalResourceViewResolver viewResolver = new InternalResourceViewResolver();
viewResolver.setPrefix("classpath:templates/");
viewResolver.setSuffix(".html");
return viewResolver;
}
Hope this helps.
回答11:
For Thymeleaf:
I just began using spring 4 and thymeleaf, when I encountered this error it was resolved by adding:
<bean class="org.thymeleaf.spring4.view.ThymeleafViewResolver">
<property name="templateEngine" ref="templateEngine" />
<property name="order" value="0" />
</bean>
回答12:
When using @Controller
annotation, you need @RequestMapping
and @ResponseBody
annotations.
Try again after adding annotation @ResponseBody
回答13:
Add the annotation @ResponseBody
to your method return.
回答14:
I use the annotation to configure spring web app, the problem solved by adding a InternalResourceViewResolver
bean to the configuration. Hope it would be helpful.
@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;
}
}
回答15:
This is happening because Spring is removing "preference" and appending the "preference" again making the same path as the request Uri.
Happening like this : request Uri: "/preference"
remove "preference": "/"
append path: "/"+"preference"
end string: "/preference"
This is getting into a loop which the Spring notifies you by throwing exception.
Its best in your interest to give a different view name like "preferenceView" or anything you like.
回答16:
try adding compile("org.springframework.boot:spring-boot-starter-thymeleaf") dependency to your gradle file.Thymeleaf helps mapping views.
回答17:
Another simple approach:
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);
}
}
来源:https://stackoverflow.com/questions/18813615/how-to-avoid-the-circular-view-path-exception-with-spring-mvc-test