Spring MVC testi ile "Dairesel görünüm yolu" istisnasından nasıl kaçınılır?


117

Denetleyicilerimden birinde aşağıdaki kod var:

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

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

Spring MVC testini kullanarak aşağıdaki gibi test etmeye çalışıyorum :

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

Aşağıdaki istisnayı alıyorum:

Dairesel görünüm yolu [tercih]: tekrar geçerli işleyici URL'sine [/ tercih] gönderilir. ViewResolver kurulumunuzu kontrol edin! (İpucu: Bu, varsayılan görünüm adı oluşturma nedeniyle belirtilmemiş bir görünümün sonucu olabilir.)

Garip bulduğum şey , şablonu içeren "tam" bağlam yapılandırmasını yüklediğimde ve aşağıda gösterildiği gibi çözümleyicileri görüntülediğimde sorunsuz çalışması :

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

Şablon çözümleyici tarafından eklenen ön ekin, uygulama bu şablon çözümleyiciyi kullandığında "dairesel görünüm yolu" olmamasını sağladığının farkındayım.

Peki uygulamamı Spring MVC testini kullanarak nasıl test edeceğim?


1
ViewResolverBaşarısız olduğunda kullandığınızı gönderebilir misiniz?
Sotirios Delimanolis

@SotiriosDelimanolis: Spring MVC Testi tarafından herhangi bir viewResolver'ın kullanılıp kullanılmadığından emin değilim. dokümantasyon
balteo

8
Aynı sorunla karşı karşıyaydım ama sorun, bağımlılığın altına eklememiş olmamdı. <dependency> <groupId> org.springframework.boot </groupId> <artifactId> spring-boot-starter-thymeleaf </artifactId> </dependency>
aamir

@RestControlleryerine kullanın@Controller
MozenRath

Yanıtlar:


65

Bunun Spring MVC testiyle hiçbir ilgisi yoktur.

Bir bildirmediğinizde ViewResolver, Spring bir varsayılan kaydeder ve InternalResourceViewResolverbu JstlViewda View.

JstlViewSınıf uzanır InternalResourceViewolan

Aynı web uygulaması içindeki bir JSP veya başka bir kaynak için sarmalayıcı. Model nesnelerini istek öznitelikleri olarak ortaya çıkarır ve isteği bir javax.servlet.RequestDispatcher kullanarak belirtilen kaynak URL'sine iletir.

Bu görünüm için bir URL'nin, web uygulaması içinde RequestDispatcher'ın yönlendirme veya dahil etme yöntemine uygun bir kaynak belirtmesi beklenir.

Bold benimdir. Bir başka deyişle, görünüm, render önce, bir almaya çalışacağım RequestDispatcherhangi için forward(). Bunu yapmadan önce aşağıdakileri kontrol eder

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

pathgörünüm adı nerede , ne döndürdüğünüz @Controller. Bu örnekte, yani preference. Değişken uri, işlenmekte olan talebin uri'sini tutar;/context/preference .

Yukarıdaki kod, yönlendirirseniz /context/preference, aynı sunucu uygulamasının (aynı sunucu bir öncekini ele aldığından) isteği yerine getireceğini ve sonsuz bir döngüye gireceğinizi fark eder.


Bir ilan zaman ThymeleafViewResolverve ServletContextTemplateResolverspesifik bir ile prefixve suffixbu inşa Viewbunu gibi bir yol vererek, farklı

WEB-INF/web-templates/preference.html

ThymeleafViewörnekler, dosyayı ServletContextyola göre bir ServletContextResourceResolver

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

hangisi sonunda

return servletContext.getResourceAsStream(resourceName);

Bu, ServletContextyola göre bir kaynak alır . Daha sonra TemplateEngineHTML'yi oluşturmak için kullanabilir . Burada sonsuz bir döngünün oluşmasına imkan yok.


1
Detaylı cevabınız için teşekkürler. Thymeleaf'i kullandığımda döngünün neden oluşmadığını ve Thymeleaf görünümü çözümleyiciyi kullanmadığımda neden oluştuğunu anlıyorum. Ancak, uygulamamı test edebilmek için yapılandırmamı nasıl değiştireceğimi hâlâ bilmiyorum ...
balteo

1
kullandığınızda @balteo bir dosya göreli olarak çözümlenir ve sağladığınız. Bunu giderir kullanmadığınızda, Bahar bir varsayılan kullanan bir ile kaynak bulur . Bu kaynak bir . Bu durumda, yolun sizin . ThymleafViewResolverViewprefixsuffixInternalResourceViewResolverRequestDispatcherServlet/preferenceDispatcherServlet
Sotirios Delimanolis

2
@balteo Uygulamanızı test etmek için doğru bir ViewResolver. Sorunuzdaki ThymeleafViewResolvergibi, kendi yapılandırmanız InternalResourceViewResolverveya kontrol cihazınızda iade ettiğiniz görünüm adını değiştirin.
Sotirios Delimanolis

Teşekkürler teşekkürler teşekkürler! Dahili kaynak görünümü çözümleyicisinin neden "dahil etme" yerine yönlendirmeyi tercih ettiğini anlayamadım, ancak şimdi açıklamanızla, adda "kaynak" kullanımı biraz belirsiz görünüyor. Bu açıklama yıldızdır.
Chris Thompson

2
@ShirgillFarhanAnsari Bir dönüş türüne sahip (ve no ) @RequestMappingek açıklamalı bir işleyici yönteminin dönüş değeri , String'i bir görünüm adı olarak yorumlayan ve cevabımda açıkladığım süreç boyunca onu kullanan bir tarafından işlenir . İle , Spring MVC bunun yerine String'i doğrudan HTTP yanıtına yazar, yani. görünüm çözünürlüğü yok. String@ResponseBodyViewNameMethodReturnValueHandler@ResponseBodyRequestResponseBodyMethodProcessor
Sotirios Delimanolis

97

Bu sorunu @ResponseBody kullanarak aşağıdaki gibi çözdüm:

@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
Bir görünümü çözümleyerek HTML döndürmek istiyorlar, bir List<DomainObject>.
Sotirios Delimanolis

2
Bu, Spring rest web hizmeti için bir JSON yanıtı döndürürken sorunumu çözdü ..
Joe

Güzel, üretir = {"application / json"} belirtmezsem, yine de çalışıyor. Varsayılan olarak json üretir mi?
Jay

74

@Controller@RestController

Aynı sorunu yaşadım ve denetleyicimin de not aldığını fark ettim @Controller. Değiştirmek @RestControllersorunu çözdü. İşte Spring Web MVC'nin açıklaması :

@RestController, kendisi @Controller ve @ResponseBody ile meta açıklamalı, her yöntemi tür düzeyindeki @ResponseBody ek açıklamasını devralan ve bu nedenle doğrudan yanıt gövdesine, görünüm çözünürlüğüne ve bir HTML şablonuyla işlemeye yazan bir denetleyiciyi belirten, oluşturulmuş bir ek açıklamadır.


1
@TodorTodorov Benim için yaptı
Igor Rodriguez

@TodorTodorov ve benim için!
koştu

3
Benim için de çalıştı. İçinde ResponseEntity yerine kendi nesnemi döndüren @ControllerAdvicebir handleXyExceptionmetodu vardı . Ek açıklamanın @RestControllerüstüne ekleme @ControllerAdviceçalıştı ve sorun ortadan kalktı.
Igor

36

Bu sorunu şu şekilde çözdüm:

@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
Bu sadece test senaryoları içindir. Denetleyiciler için değil.
cst1992

2
Yeni birim testlerinden birinde birine bu sorunu gidermeye yardımcı oluyordu, tam olarak aradığımız şey buydu.
Bradford2000

Bunu kullandım, ancak testte çözücüm için yanlış önek ve sonek vermeme rağmen işe yaradı. Bunun arkasındaki gerekçeyi sağlayabilir misiniz, bu neden gerekli?
dushyantashu

bu cevap en doğru ve spesifik olduğu
Caffeine Coder

20

Bir web sayfasını denemek ve yüklemek için Spring Boot kullanıyorum, test etmek değil ve bu sorunu yaşadım. Benim çözümüm, biraz farklı koşullar göz önüne alındığında yukarıdakilerden biraz farklıydı. (bu cevaplar anlamama yardımcı olsa da.)

Maven'deki Spring Boot başlangıç ​​bağımlılığımı şundan değiştirmek zorunda kaldım:

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

için:

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

"Web" i "thymeleaf" olarak değiştirmek benim için sorunu çözdü.


1
Benim için, başlangıç ​​ağını değiştirmek gerekli değildi, ancak <scope> test </scope> ile timeleaf bağımlılığım vardı. "Test" kapsamını kaldırdığımda işe yaradı. İpucu için teşekkürler!
Georgina Diaz

16

Görünümü gerçekten işlemekle ilgilenmiyorsanız işte size kolay bir düzeltme.

Dairesel görünüm yollarını kontrol etmeyen bir InternalResourceViewResolver alt sınıfı oluşturun:

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

Ardından testinizi onunla ayarlayın:

MockMvc mockMvc;

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

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

Bu benim sorunumu çözdü. Testlerin aynı dizinine bir StandaloneMvcTestViewResolver sınıfı ekledim ve yukarıda açıklandığı gibi MockMvcBuilders'da kullandım. Teşekkürler
Matheus Araujo

Aynı sorunu yaşadım ve bu benim için de çözdü. Çok teşekkürler!
Johan

Bu, (1) denetleyicileri değiştirmeye ihtiyaç duymayan ve (2) sınıf başına bir basit içe aktarma ile tüm test sınıflarında yeniden kullanılabilen harika bir çözümdür. +1
Nander Speerstra

Eski ama iyi! Günümü kurtardım. Bu geçici çözüm için teşekkür ederiz +1
Raistlin

13

Spring Boot kullanıyorsanız, pom.xml dosyanıza thymeleaf bağımlılığını ekleyin:

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

1
Oyla. Eksik Thymeleaf bağımlılığı, projemde bu hataya neden olan şeydi. Ancak, Spring Boot kullanıyorsanız, bağımlılık bunun yerine şöyle görünecektir:<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-thymeleaf</artifactId> </dependency>
peterh

8

Benim için sorunu çözdükten /sonra eklemek /preference:

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

8

Benim durumumda Kotlin + Spring boot'u deniyordum ve Circular View Path konusuna girdim. Çevrimiçi olarak aldığım tüm öneriler, aşağıdakileri deneyene kadar yardımcı olamadı:

Başlangıçta denetleyicime şu şekilde açıklama eklemiştim: @Controller

import org.springframework.stereotype.Controller

Sonra yerini @Controllerile@RestController

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

Ve işe yaradı.


6

@RequestBody kullanmadıysanız ve yalnızca kullanıyorsanız @Controller, bunu düzeltmenin en basit yolu @RestControlleryerine kullanmaktır@Controller


bu düzeltilmedi, şimdi şablon yerine dosya adınızı gösterecek
Ashish Kamble

1
bu gerçek soruna bağlıdır. bu hata birçok nedenden dolayı ortaya çıkabilir
MozenRath

4

Ek açıklamayı @ResponseBodyyöntem dönüşünüze ekleyin.


Lütfen bunun sorunu nasıl ve neden çözdüğüne dair bir açıklama ekleyin, gönderinizin kalitesini artırmaya gerçekten yardımcı olur ve muhtemelen daha fazla oy almanıza neden olur.
Android

3

Spring Boot'u Thymeleaf ile kullanıyorum. Bu benim için çalıştı. JSP ile benzer cevaplar var ancak JSP yerine HTML kullandığımı ve bunların buradasrc/main/resources/templates açıklandığı gibi standart bir Spring Boot projesinde olduğu gibi klasörde bulunduğunu unutmayın . Bu sizin durumunuz da olabilir.

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

Bu yardımcı olur umarım.


3

Spring Boot + Freemarker çalıştırılırken sayfa görünürse:

Beyaz Etiket Hata Sayfası Bu uygulama / error için açık bir eşleme içermediğinden, bunu bir geri dönüş olarak görüyorsunuz.

Spring-boot-starter-parent 2.2.1.RELEASE sürümünde freemarker çalışmıyor:

  1. Freemarker dosyalarını .ftl'den .ftlh'ye yeniden adlandırın
  2. Application.properties'e ekleyin: spring.freemarker.expose-request-attributes = true

spring.freemarker.suffix = .ftl


1
Freemarker dosyalarını .ftl'den .ftlh'ye yeniden adlandırmak sorunu benim için çözdü.
jannnik

Dostum ... sana bir bira borçluyum. Bu yeniden adlandırma olayı yüzünden bütün günümü kaybettim.
julianobrasil

2

Thymeleaf için:

Spring 4 ve thymeleaf'i yeni kullanmaya başladım, bu hatayla karşılaştığımda ekleyerek çözüldü:

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

1

Kullanırken @Controllerek açıklama, ihtiyacınız @RequestMappingve @ResponseBodyek açıklamalar. Ek açıklama ekledikten sonra tekrar deneyin@ResponseBody


0

Yay web uygulamasını yapılandırmak için açıklamayı kullanıyorum, sorun InternalResourceViewResolveryapılandırmaya bir fasulye ekleyerek çözüldü . Umarım yardımcı olur.

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

Teşekkürler, bu benim için iyi çalışıyor. Uygulamam 1.2.7'den 1.3.1 bahar önyüklemesine yükselttikten sonra bozuldu ve yalnızca kayıtta başarısız olan bu satırdı. AddViewController ("/ login"). SetViewName ("login"); Bu fasulyeyi kaydederken, uygulama tekrar çalıştı ... en azından oturum açma işlemi başarısız oldu.
le0diaz

0

Bu, Spring'in "tercih" i kaldırması ve "tercih" i ekleyerek, istek Uri ile aynı yolu tekrar yapması nedeniyle oluyor.

Şöyle oluyor: Uri iste: "/ tercih"

"tercih" i kaldırın: "/"

yolu ekle: "/" + "tercih"

end string: "/ tercih"

Bu, Bahar'ın size istisna atarak bildirdiği bir döngüye giriyor.

"Tercih Görünümü" gibi farklı bir görünüm adı veya istediğiniz herhangi bir şey vermeniz ilginize en iyisidir.


0

gradle dosyanıza compile ("org.springframework.boot: spring-boot-starter-thymeleaf") bağımlılığı eklemeyi deneyin.Thymeleaf, görünümlerin eşlenmesine yardımcı olur.


0

Benim durumumda, Spring boot uygulamasını kullanarak JSP sayfalarına hizmet vermeye çalışırken bu sorunu yaşadım.

İşte benim için çalıştı:

application.properties

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

pom.xml

JSP'ler için desteği etkinleştirmek için tomcat-embed-jasper'a bir bağımlılık eklememiz gerekir.

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

-2

Başka bir basit yaklaşım:

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);
    }
}
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.