Cách tránh ngoại lệ "Đường dẫn chế độ xem hình tròn" với kiểm tra Spring MVC


117

Tôi có mã sau trong một trong các bộ điều khiển của mình:

@Controller
@RequestMapping("/preference")
public class PreferenceController {

    @RequestMapping(method = RequestMethod.GET, produces = "text/html")
    public String preference() {
        return "preference";
    }
}

Tôi chỉ đang thử kiểm tra nó bằng cách sử dụng thử nghiệm Spring MVC như sau:

@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());
    }
}

Tôi nhận được ngoại lệ sau:

Đường dẫn dạng xem hình tròn [tùy chọn]: sẽ gửi lại URL trình xử lý hiện tại [/ tùy chọn] một lần nữa. Kiểm tra thiết lập ViewResolver của bạn! (Gợi ý: Đây có thể là kết quả của một chế độ xem không xác định, do việc tạo tên chế độ xem mặc định.)

Điều tôi thấy lạ là nó hoạt động tốt khi tôi tải cấu hình ngữ cảnh "đầy đủ" bao gồm các trình phân giải mẫu và chế độ xem như được hiển thị bên dưới:

<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>

Tôi biết rõ rằng tiền tố do trình phân giải mẫu thêm vào đảm bảo rằng không có "đường dẫn chế độ xem hình tròn" khi ứng dụng sử dụng trình phân giải mẫu này.

Nhưng sau đó làm cách nào để kiểm tra ứng dụng của mình bằng cách sử dụng thử nghiệm Spring MVC?


1
Bạn có thể đăng cái ViewResolverbạn sử dụng khi nó bị lỗi không?
Sotirios Delimanolis

@SotiriosDelimanolis: Tôi không chắc liệu có bất kỳ viewResolver nào được sử dụng bởi Spring MVC Test hay không. tài liệu
balteo

8
Tôi đã gặp phải vấn đề tương tự nhưng vấn đề là tôi chưa thêm phụ thuộc bên dưới. <dependency> <groupId> org.springframework.boot </groupId> <artifactId> spring-boot-starter-thymeleaf </artifactId> </dependency>
aamir

sử dụng @RestControllerthay vì@Controller
MozenRath

Câu trả lời:


65

Điều này không liên quan gì đến thử nghiệm Spring MVC.

Khi bạn không khai báo a ViewResolver, Spring sẽ đăng ký một mặc định InternalResourceViewResolvertạo ra các thể hiện JstlViewđể hiển thị View.

Các JstlViewlớp học kéo dài InternalResourceViewđó là

Gói cho JSP hoặc tài nguyên khác trong cùng một ứng dụng web. Hiển thị các đối tượng mô hình dưới dạng thuộc tính yêu cầu và chuyển tiếp yêu cầu đến URL tài nguyên được chỉ định bằng cách sử dụng javax.servlet.RequestDispatcher.

URL cho chế độ xem này được cho là chỉ định một tài nguyên trong ứng dụng web, phù hợp với phương thức chuyển tiếp hoặc bao gồm của RequestDispatcher.

In đậm là của tôi. Nói cách khác, chế độ xem, trước khi hiển thị, sẽ cố gắng đi RequestDispatcherđến đó forward(). Trước khi làm điều này, nó kiểm tra những điều sau

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.)");
}

nơi pathlà tên xem, những gì bạn trở về từ @Controller. Trong ví dụ này, đó là preference. Biến urigiữ nguyên yêu cầu đang được xử lý, đó là /context/preference.

Đoạn mã trên nhận ra rằng nếu bạn chuyển tiếp đến /context/preference, cùng một servlet (vì cùng một lần xử lý trước đó) sẽ xử lý yêu cầu và bạn sẽ đi vào một vòng lặp vô tận.


Khi bạn khai báo a ThymeleafViewResolvervà a ServletContextTemplateResolvervới một prefixvà cụ thể suffix, nó xây dựng một Viewcách khác, tạo cho nó một đường dẫn như

WEB-INF/web-templates/preference.html

ThymeleafViewcác trường hợp định vị tệp liên quan đến ServletContextđường dẫn bằng cách sử dụng ServletContextResourceResolver

templateInputStream = resourceResolver.getResourceAsStream(templateProcessingParameters, resourceName);`

mà cuối cùng

return servletContext.getResourceAsStream(resourceName);

Điều này nhận được một tài nguyên có liên quan đến ServletContextđường dẫn. Sau đó, nó có thể sử dụng TemplateEngineđể tạo HTML. Không có cách nào một vòng lặp vô tận có thể xảy ra ở đây.


1
Cảm ơn bạn đã trả lời chi tiết. Tôi hiểu tại sao vòng lặp không xảy ra khi tôi sử dụng Thymeleaf và tại sao nó xảy ra khi tôi không sử dụng trình giải quyết chế độ xem Thymeleaf. Tuy nhiên, tôi vẫn không chắc chắn làm thế nào để thay đổi cấu hình của tôi để tôi có thể thử nghiệm ứng dụng của tôi ...
balteo

1
@balteo Khi bạn sử dụng ThymleafViewResolver, Viewtệp được giải quyết dưới dạng tệp liên quan đến prefixsuffixbạn cung cấp. Khi bạn không sử dụng giải pháp đó, Spring sẽ sử dụng mặc định InternalResourceViewResolvertìm tài nguyên với a RequestDispatcher. Tài nguyên này có thể là một Servlet. Trong trường hợp này, đó là do đường dẫn /preferenceđến của bạn DispatcherServlet.
Sotirios Delimanolis

2
@balteo Để kiểm tra ứng dụng của bạn, hãy cung cấp giá trị chính xác ViewResolver. Hoặc ThymeleafViewResolvernhư trong câu hỏi của bạn, của riêng bạn cấu hình InternalResourceViewResolverhoặc thay đổi tên xem bạn đang quay trở lại trong điều khiển của bạn.
Sotirios Delimanolis

Cảm ơn bạn, cảm ơn bạn, cảm ơn bạn! Tôi không thể tìm ra lý do tại sao trình giải quyết chế độ xem tài nguyên nội bộ lại thích chuyển tiếp hơn là "bao gồm" nhưng bây giờ với giải thích của bạn, có vẻ như việc sử dụng "tài nguyên" trong tên hơi mơ hồ. Lời giải thích này là tuyệt vời.
Chris Thompson

2
@ShirgillFarhanAnsari Một @RequestMappingphương thức xử lý có chú thích với Stringkiểu trả về (và không @ResponseBody) có giá trị trả về của nó được xử lý bởi một phương thức ViewNameMethodReturnValueHandlerdiễn giải Chuỗi dưới dạng tên dạng xem và sử dụng nó để thực hiện quy trình mà tôi giải thích trong câu trả lời của mình. Với @ResponseBody, Spring MVC thay vào đó sẽ sử dụng RequestResponseBodyMethodProcessormà thay vào đó ghi Chuỗi trực tiếp vào phản hồi HTTP, tức là. không có độ phân giải chế độ xem.
Sotirios Delimanolis

97

Tôi đã giải quyết vấn đề này bằng cách sử dụng @ResponseBody như bên dưới:

@RequestMapping(value = "/resturl", method = RequestMethod.GET, produces = {"application/json"})
    @ResponseStatus(HttpStatus.OK)
    @Transactional(value = "jpaTransactionManager")
    public @ResponseBody List<DomainObject> findByResourceID(@PathParam("resourceID") String resourceID) {

10
Họ muốn trả về HTML bằng cách phân giải một chế độ xem, không trả về phiên bản tuần tự của a List<DomainObject>.
Sotirios Delimanolis

2
Điều này đã giải quyết vấn đề của tôi trong khi trả lại phản hồi JSON cho dịch vụ web Spring rest ..
Joe

Tốt, nếu tôi không chỉ định sản xuất = {"application / json"}, nó vẫn hoạt động. Nó có tạo ra json theo mặc định không?
Jay

74

@Controller@RestController

Tôi đã gặp vấn đề tương tự và tôi nhận thấy rằng bộ điều khiển của tôi cũng được chú thích bằng @Controller. Thay thế nó bằng sự cố đã @RestControllerđược giải quyết. Đây là lời giải thích từ Spring Web MVC :

@RestController là một chú thích được soạn thảo tự nó được siêu chú thích với @Controller và @ResponseBody chỉ ra một bộ điều khiển có mọi phương thức kế thừa chú thích @ResponseBody kiểu cấp và do đó ghi trực tiếp vào phần nội dung phản hồi so với độ phân giải chế độ xem và hiển thị bằng mẫu HTML.


1
@TodorTodorov Nó đã làm cho tôi
Igor Rodriguez

@TodorTodorov và cho tôi!
Ran

3
Cũng làm việc cho tôi. Tôi đã @ControllerAdvicecó một handleXyExceptionphương thức trong đó, nó trả về đối tượng của riêng tôi thay vì một ResponseEntity. Thêm @RestControllervào đầu @ControllerAdvicechú thích đã hoạt động và sự cố đã biến mất.
Igor

36

Đây là cách tôi giải quyết vấn đề này:

@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();
    }

1
Điều này chỉ dành cho các hộp thử nghiệm. Không dành cho bộ điều khiển.
cst1992

2
Đang giúp ai đó khắc phục sự cố này trong một trong những bài kiểm tra đơn vị mới của họ, đây chính xác là những gì chúng tôi đang tìm kiếm.
Bradford2000

Tôi đã sử dụng điều này, nhưng mặc dù đưa ra tiền tố và hậu tố sai cho trình phân giải của tôi trong thử nghiệm, nó vẫn hoạt động. Bạn có thể cung cấp lý do đằng sau điều này, tại sao điều này là cần thiết?
dushyantashu

câu trả lời này sẽ được bình chọn là đúng và cụ thể nhất
Caffeine Coder

20

Tôi đang sử dụng Spring Boot để thử và tải một trang web, không phải thử nghiệm và gặp sự cố này. Giải pháp của tôi hơi khác so với những giải pháp ở trên khi xét đến các trường hợp hơi khác nhau. (mặc dù những câu trả lời đó giúp tôi hiểu.)

Tôi chỉ cần thay đổi sự phụ thuộc vào bộ khởi động Spring Boot của mình trong Maven từ:

<dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
</dependency>

đến:

<dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>

Chỉ cần thay đổi 'web' thành 'thymeleaf' đã khắc phục được sự cố cho tôi.


1
Đối với tôi, không cần thiết phải thay đổi trang web khởi động, nhưng tôi có sự phụ thuộc vào thymeleaf với <scope> test </scope>. Khi tôi loại bỏ phạm vi "thử nghiệm", nó đã hoạt động. Cảm ơn vì manh mối!
Georgina Diaz

16

Đây là một cách khắc phục dễ dàng nếu bạn không thực sự quan tâm đến việc hiển thị chế độ xem.

Tạo một lớp con của InternalResourceViewResolver không kiểm tra các đường dẫn chế độ xem hình tròn:

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;
    }
}

Sau đó, thiết lập thử nghiệm của bạn với nó:

MockMvc mockMvc;

@Before
public void setUp() {
    final MyController controller = new MyController();

    mockMvc =
            MockMvcBuilders.standaloneSetup(controller)
                    .setViewResolvers(new StandaloneMvcTestViewResolver())
                    .build();
}

Điều này đã khắc phục sự cố của tôi. Tôi vừa thêm một lớp StandaloneMvcTestViewResolver trong cùng một thư mục của các bài kiểm tra và sử dụng nó trong MockMvcBuilders như được mô tả ở trên. Cảm ơn
Matheus Araujo

Tôi đã gặp vấn đề tương tự và điều này đã khắc phục nó cho tôi. Cảm ơn rất nhiều!
Johan

Đây là một giải pháp tuyệt vời mà (1) không cần thay đổi bộ điều khiển và (2) có thể được sử dụng lại trong tất cả các lớp thử nghiệm với một lần nhập đơn giản cho mỗi lớp. +1
Nander Speerstra

Cũ nhưng dùng vẫn tốt! Đã lưu ngày của tôi. Cảm ơn bạn đã workaround này 1
Raistlin

13

Nếu bạn đang sử dụng Spring Boot, hãy thêm phụ thuộc thymeleaf vào pom.xml của bạn:

    <dependency>
        <groupId>org.thymeleaf</groupId>
        <artifactId>thymeleaf-spring4</artifactId>
        <version>2.1.6.RELEASE</version>
    </dependency>

1
Ủng hộ. Thiếu phụ thuộc Thymeleaf là nguyên nhân gây ra lỗi này trong dự án của tôi. Tuy nhiên, ff bạn đang sử dụng Spring Boot, sau đó phụ thuộc sẽ trông như thế này thay vì:<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-thymeleaf</artifactId> </dependency>
peterh

8

Thêm /sau khi /preferencegiải quyết vấn đề cho tôi:

@Test
public void circularViewPathIssue() throws Exception {
    mockMvc.perform(get("/preference/"))
           .andDo(print());
}

8

Trong trường hợp của tôi, tôi đang thử khởi động Kotlin + Spring và tôi gặp sự cố Đường dẫn Chế độ xem Hình tròn. Tất cả các đề xuất tôi nhận được trên mạng đều không thể giúp được gì, cho đến khi tôi thử cách dưới đây:

Ban đầu tôi đã chú thích bộ điều khiển của mình bằng @Controller

import org.springframework.stereotype.Controller

Sau đó tôi thay thế @Controllerbằng@RestController

import org.springframework.web.bind.annotation.RestController

Va no đa hoạt động.


6

nếu bạn chưa sử dụng @RequestBody và chỉ đang sử dụng @Controller, cách đơn giản nhất để khắc phục điều này là sử dụng @RestControllerthay vì@Controller


điều này không được khắc phục, bây giờ nó sẽ hiển thị tên tệp của bạn, thay vào đó hiển thị mẫu
Ashish Kamble

1
điều đó phụ thuộc vào vấn đề thực tế. lỗi này có thể xảy ra vì nhiều lý do
MozenRath

4

Thêm chú thích @ResponseBodyvào phương thức trả về của bạn.


Vui lòng kèm theo lời giải thích về cách thức và lý do điều này giải quyết được vấn đề sẽ thực sự giúp cải thiện chất lượng bài đăng của bạn và có thể dẫn đến nhiều phiếu bầu hơn.
Android

3

Tôi đang sử dụng Spring Boot với Thymeleaf. Đây là những gì làm việc cho tôi. Có những câu trả lời tương tự với JSP nhưng lưu ý rằng tôi đang sử dụng HTML, không phải JSP và chúng nằm trong thư mục src/main/resources/templatesgiống như trong một dự án Spring Boot tiêu chuẩn như được giải thích ở đây . Đây cũng có thể là trường hợp của bạn.

@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;
}

Hi vọng điêu nay co ich.


3

Khi chạy Spring Boot + Freemarker nếu trang xuất hiện:

Trang lỗi nhãn trắng Ứng dụng này không có ánh xạ rõ ràng cho / lỗi, vì vậy bạn đang xem đây là dự phòng.

Trong phiên bản spring-boot-starter-parent 2.2.1.RELEASE trình tự do nhãn hiệu không hoạt động:

  1. đổi tên các tệp Freemarker từ .ftl thành .ftlh
  2. Thêm vào application.properties: spring.freemarker.expose-request-properties = true

spring.freemarker.suffix = .ftl


1
Chỉ cần đổi tên tệp Freemarker từ .ftl thành .ftlh đã giải quyết được vấn đề cho tôi.
jannnik

Anh bạn ... Tôi nợ bạn một ly bia. Tôi đã mất cả ngày vì việc đổi tên này.
julianobrasil

2

Đối với Thymeleaf:

Tôi vừa mới bắt đầu sử dụng spring 4 và thymeleaf, khi tôi gặp lỗi này, nó đã được giải quyết bằng cách thêm:

<bean class="org.thymeleaf.spring4.view.ThymeleafViewResolver">
  <property name="templateEngine" ref="templateEngine" />
  <property name="order" value="0" />
</bean> 

1

Khi sử dụng @Controllerchú thích, bạn cần @RequestMapping@ResponseBodychú thích. Hãy thử lại sau khi thêm chú thích@ResponseBody


0

Tôi sử dụng chú thích để định cấu hình ứng dụng web mùa xuân, vấn đề được giải quyết bằng cách thêm InternalResourceViewResolverbean vào cấu hình. Hy vọng nó sẽ hữu ích.

@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;
    }
}

Cảm ơn điều này làm việc tốt cho tôi. Ứng dụng của tôi bị lỗi sau khi nâng cấp lên Spring boot 1.3.1 từ 1.2.7 và chỉ có dòng này là không thành công registry.addViewController ("/ login"). SetViewName ("login"); Khi đăng ký bean đó, ứng dụng đã hoạt động trở lại ... ít nhất là đăng nhập sẽ ổn.
le0diaz

0

Điều này xảy ra bởi vì Spring đang xóa "tùy chọn" và thêm "tùy chọn" một lần nữa, tạo đường dẫn giống như Uri yêu cầu.

Diễn ra như thế này: request Uri: "/ prefer"

loại bỏ "tùy chọn": "/"

nối thêm đường dẫn: "/" + "tùy chọn"

kết thúc chuỗi: "/ tùy chọn"

Điều này đang đi vào một vòng lặp mà Spring sẽ thông báo cho bạn bằng cách ném ngoại lệ.

Tốt nhất bạn nên đặt một tên chế độ xem khác như "Chế độ xem tùy chọn" hoặc bất cứ thứ gì bạn thích.


0

thử thêm phần phụ thuộc biên dịch ("org.springframework.boot: spring-boot-starter-thymeleaf") vào tệp gradle của bạn.Thymeleaf giúp ánh xạ các chế độ xem.


0

Trong trường hợp của tôi, tôi đã gặp sự cố này khi cố gắng cung cấp các trang JSP bằng ứng dụng khởi động Spring.

Đây là những gì đã làm việc cho tôi:

application.properties

spring.mvc.view.prefix=/WEB-INF/views/
spring.mvc.view.suffix=.jsp

pom.xml

Để kích hoạt hỗ trợ cho JSP, chúng tôi sẽ cần thêm một phụ thuộc vào tomcat-nhúng-jasper.

<dependency>
    <groupId>org.apache.tomcat.embed</groupId>
    <artifactId>tomcat-embed-jasper</artifactId>
    <scope>provided</scope>
</dependency>

-2

Một cách tiếp cận đơn giản khác:

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);
    }
}
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.