Mengapa Spring MVC menanggapi dengan 404 dan melaporkan "Tidak ditemukan pemetaan untuk permintaan HTTP dengan URI […] di DispatcherServlet"?

91

Saya sedang menulis aplikasi Spring MVC yang digunakan di Tomcat. Lihat contoh minimal, lengkap, dan dapat diverifikasi berikut ini

public class Application extends AbstractAnnotationConfigDispatcherServletInitializer {
    protected Class<?>[] getRootConfigClasses() {
        return new Class<?>[] { };
    }
    protected Class<?>[] getServletConfigClasses() {
        return new Class<?>[] { SpringServletConfig.class };
    }
    protected String[] getServletMappings() {
        return new String[] { "/*" };
    }
}

dimana SpringServletConfigadalah

@Configuration
@ComponentScan("com.example.controllers")
@EnableWebMvc
public class SpringServletConfig {
    @Bean
    public InternalResourceViewResolver resolver() {
        InternalResourceViewResolver vr = new InternalResourceViewResolver();
        vr.setPrefix("/WEB-INF/jsps/");
        vr.setSuffix(".jsp");
        return vr;
    }
}

Akhirnya, saya memiliki @Controllerdalam paketcom.example.controllers

@Controller
public class ExampleController {
    @RequestMapping(path = "/home", method = RequestMethod.GET)
    public String example() {
        return "index";
    }
}

Nama konteks aplikasi saya adalah Example. Saat saya mengirim permintaan ke

http://localhost:8080/Example/home

aplikasi menanggapi dengan Status HTTP 404 dan mencatat berikut ini

WARN  o.s.web.servlet.PageNotFound - No mapping found for HTTP request with URI `[/Example/WEB-INF/jsps/index.jsp]` in `DispatcherServlet` with name 'dispatcher'

Saya memiliki sumber daya JSP pada /WEB-INF/jsps/index.jspsaya mengharapkan Spring MVC untuk menggunakan pengontrol saya untuk menangani permintaan dan meneruskan ke JSP, jadi mengapa itu menanggapi dengan 404?


Ini dimaksudkan sebagai postingan kanonis untuk pertanyaan tentang pesan peringatan ini.

Sotirios Delimanolis
sumber

Jawaban:

100

Aplikasi Spring MVC standar Anda akan melayani semua permintaan melalui DispatcherServletyang telah Anda daftarkan dengan container Servlet Anda.

The DispatcherServletterlihat di perusahaan ApplicationContextdan, jika tersedia, ApplicationContextterdaftar dengan ContextLoaderListenerbiji khusus perlu untuk setup permintaannya melayani logika. Kacang ini dijelaskan dalam dokumentasi .

Bisa dibilang yang paling penting, kacang HandlerMappingpeta tipe

permintaan masuk ke penangan dan daftar pemroses sebelum dan sesudah (penangan penangan) berdasarkan beberapa kriteria yang rinciannya berbeda-beda menurut HandlerMappingpenerapan. Implementasi yang paling populer mendukung pengontrol beranotasi, tetapi implementasi lain juga ada.

The javadoc dariHandlerMapping lebih lanjut menjelaskan bagaimana implementasi harus berperilaku.

The DispatcherServletmenemukan semua kacang jenis ini dan register mereka dalam beberapa urutan (dapat disesuaikan). Saat melayani permintaan, DispatcherServletloop melalui HandlerMappingobjek - objek ini dan menguji masing-masing objek dengan getHandleruntuk menemukan satu yang dapat menangani permintaan masuk, direpresentasikan sebagai standar HttpServletRequest. Pada 4.3.x, jika tidak menemukannya , ia mencatat peringatan yang Anda lihat

Tidak ada pemetaan ditemukan untuk permintaan HTTP dengan URI [/some/path]di DispatcherServletdengan nama somename

dan baik melemparkan NoHandlerFoundExceptionatau segera melakukan respon dengan kode status 404 Not Found.

Mengapa tidak DispatcherServletmenemukan HandlerMappingyang bisa menangani permintaan saya?

HandlerMappingImplementasi yang paling umum adalah RequestMappingHandlerMapping, yang menangani pendaftaran @Controllerkacang sebagai penangan (sebenarnya @RequestMappingmetode beranotasi mereka ). Anda dapat mendeklarasikan kacang jenis ini sendiri (dengan @Beanatau <bean>atau mekanisme lain) atau Anda dapat menggunakan opsi bawaan . Ini adalah:

  1. Beri anotasi pada @Configurationkelas Anda dengan @EnableWebMvc.
  2. Deklarasikan <mvc:annotation-driven />anggota dalam konfigurasi XML Anda.

Seperti yang dijelaskan tautan di atas, keduanya akan mendaftarkan RequestMappingHandlerMappingkacang (dan banyak hal lainnya). Namun, a HandlerMappingtidak terlalu berguna tanpa pawang. RequestMappingHandlerMappingmengharapkan beberapa @Controllerbean sehingga Anda perlu mendeklarasikannya juga, melalui @Beanmetode dalam konfigurasi Java atau <bean>deklarasi dalam konfigurasi XML atau melalui pemindaian komponen @Controllerkelas beranotasi di keduanya. Pastikan kacang ini ada.

Jika Anda mendapatkan pesan peringatan dan 404 dan telah mengonfigurasi semua hal di atas dengan benar, maka Anda mengirimkan permintaan Anda ke URI yang salah , yang tidak ditangani oleh @RequestMappingmetode penangan beranotasi yang terdeteksi .

The spring-webmvcpenawaran perpustakaan lain built-in HandlerMappingimplementasi. Misalnya, BeanNameUrlHandlerMappingpeta

dari URL ke kacang dengan nama yang dimulai dengan garis miring ("/")

dan Anda selalu bisa menulis sendiri. Jelas, Anda harus memastikan permintaan yang Anda kirim cocok dengan setidaknya salah satu HandlerMappingpenangan objek terdaftar .

Jika Anda tidak secara implisit atau eksplisit mendaftarkan HandlerMappingkacang apa pun (atau jika detectAllHandlerMappingsada true), DispatcherServletregister beberapa default . Ini didefinisikan dalam DispatcherServlet.propertiespaket yang sama dengan DispatcherServletkelas. Mereka adalah BeanNameUrlHandlerMappingdan DefaultAnnotationHandlerMapping(yang serupa RequestMappingHandlerMappingtetapi tidak digunakan lagi).

Debugging

MVC Spring akan mencatat penangan yang terdaftar melalui RequestMappingHandlerMapping. Misalnya, @Controllersuka

@Controller
public class ExampleController {
    @RequestMapping(path = "/example", method = RequestMethod.GET, headers = "X-Custom")
    public String example() {
        return "example-view-name";
    }
}

akan mencatat berikut ini di tingkat INFO

Mapped "{[/example],methods=[GET],headers=[X-Custom]}" onto public java.lang.String com.spring.servlet.ExampleController.example()

Ini menggambarkan pemetaan yang terdaftar. Jika Anda melihat peringatan bahwa tidak ada penangan yang ditemukan, bandingkan URI dalam pesan dengan pemetaan yang tercantum di sini. Semua batasan yang ditentukan dalam @RequestMappingharus cocok untuk Spring MVC untuk memilih penangan.

HandlerMappingImplementasi lain mencatat pernyataan mereka sendiri yang seharusnya mengisyaratkan pemetaan mereka dan penangannya yang sesuai.

Demikian pula, aktifkan pencatatan Musim Semi pada tingkat DEBUG untuk melihat kacang mana yang didaftarkan Spring. Itu harus melaporkan kelas beranotasi mana yang ditemukannya, paket mana yang dipindai, dan kacang mana yang diinisialisasi. Jika yang Anda harapkan tidak ada, tinjau ApplicationContextkonfigurasi Anda .

Kesalahan umum lainnya

A DispatcherServlethanyalah Java EE tipikal Servlet. Anda mendaftar dengan khas <web.xml> <servlet-class>dan <servlet-mapping>deklarasi, atau langsung melalui ServletContext#addServletdalam WebApplicationInitializer, atau dengan mekanisme apapun yang menggunakan Musim Semi booting. Karena itu, Anda harus mengandalkan pemetaan url logika ditentukan dalam spesifikasi Servlet , lihat Bab 12. Lihat juga

Dengan mengingat hal tersebut, kesalahan umum adalah mendaftarkan DispatcherServletdengan pemetaan url /*, mengembalikan nama tampilan dari a@RequestMapping metode penangan, dan mengharapkan JSP untuk dirender. Misalnya, pertimbangkan metode penangan seperti

@RequestMapping(path = "/example", method = RequestMethod.GET)
public String example() {
    return "example-view-name";
}

dengan sebuah InternalResourceViewResolver

@Bean
public InternalResourceViewResolver resolver() {
    InternalResourceViewResolver vr = new InternalResourceViewResolver();
    vr.setPrefix("/WEB-INF/jsps/");
    vr.setSuffix(".jsp");
    return vr;
}

Anda mungkin mengharapkan permintaan diteruskan ke sumber daya JSP di jalur /WEB-INF/jsps/example-view-name.jsp. Ini tidak akan terjadi. Sebaliknya, dengan asumsi nama konteksExample , DisaptcherServletakan melaporkan

Tidak ada pemetaan yang ditemukan untuk permintaan HTTP dengan URI [/Example/WEB-INF/jsps/example-view-name.jsp]diDispatcherServlet dengan nama 'operator'

Karena DispatcherServletdipetakan ke /*dan /*cocok dengan semuanya (kecuali kecocokan persis, yang memiliki prioritas lebih tinggi), DispatcherServletakan dipilih untuk menangani forwarddari JstlView(dikembalikan oleh InternalResourceViewResolver). Di hampir setiap kasus, DispatcherServlettidak akan dikonfigurasi untuk menangani permintaan seperti itu .

Sebaliknya, dalam kasus sederhana ini, Anda harus mendaftarkan DispatcherServletke /, menandainya sebagai servlet default. Servlet default adalah kecocokan terakhir untuk permintaan. Ini akan memungkinkan wadah servlet khas Anda untuk memilih implementasi Servlet internal, yang dipetakan ke*.jsp , untuk menangani sumber daya JSP (misalnya, Tomcat memiliki JspServlet), sebelum mencoba dengan servlet default.

Itulah yang Anda lihat dalam contoh Anda.

Sotirios Delimanolis
sumber
Dengan @EnableWebMvc dispatcherServlet itu sudah terdaftar ke /. "Anda mungkin mengharapkan permintaan diteruskan ke sumber daya JSP di jalur /WEB-INF/jsps/example-view-name.jsp. Ini tidak akan terjadi." Bagaimana Anda membuatnya berfungsi sehingga meneruskan ke sumber daya JSP di jalur itu? Itu pada dasarnya pertanyaan yang diajukan.
Tor
@Tor sendiri, @EnableWebMvcpada @Configurationkelas yang dianotasi tidak melakukan itu. Yang dilakukannya hanyalah menambahkan sejumlah kacang penangan / adaptor MVC Spring default ke konteks aplikasi. Mendaftarkan DispatcherServletuntuk melayani /adalah proses yang sepenuhnya terpisah yang dilakukan dengan beberapa cara yang saya jelaskan di bagian Kesalahan umum lainnya . Saya menjawab pertanyaan yang diajukan dua paragraf di bawah ini apa yang Anda kutip.
Sotirios Delimanolis
5

Saya menyelesaikan masalah saya saat selain dijelaskan sebelumnya: `

@Bean
public InternalResourceViewResolver resolver() {
    InternalResourceViewResolver vr = new InternalResourceViewResolver();
    vr.setPrefix("/WEB-INF/jsps/");
    vr.setSuffix(".jsp");
    return vr;
}

added tomcat-embed-jasper:

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

`from: File JSP tidak dirender dalam aplikasi web Spring Boot

RoutesMaps.com
sumber
2

Dalam kasus saya, saya mengikuti dokumentasi Interceptors Spring untuk versi 5.1.2 (saat menggunakan Spring Boot v2.0.4.RELEASE ) dan WebConfigkelas memiliki anotasi @EnableWebMvc, yang tampaknya bertentangan dengan hal lain dalam aplikasi saya yang mencegah statis saya. aset agar tidak diselesaikan dengan benar (yaitu tidak ada file CSS atau JS yang dikembalikan ke klien).

Setelah mencoba banyak hal yang berbeda, saya mencoba menghapus yang @EnableWebMvcdan bekerja!

Edit: Berikut dokumentasi referensi yang mengatakan Anda harus menghapus @EnableWebMvcanotasi

Rupanya dalam kasus saya setidaknya, saya sudah mengkonfigurasi aplikasi Spring saya (walaupun tidak dengan menggunakan web.xmlatau file statis lainnya, itu pasti secara terprogram), jadi ada konflik di sana.

Acapulco
sumber
1

Cobalah untuk mengubah kode Anda dengan perubahan berikut pada file konfigurasi Anda. Konfigurasi Java digunakan sebagai pengganti application.properties. Jangan lupa untuk mengaktifkan konfigurasi di configureDefaultServletHandlingmetode.

WebMvcConfigurerAdapterkelas tidak digunakan lagi, jadi kami menggunakan WebMvcConfigurerantarmuka.

@Configuration
@EnableWebMvc
@ComponentScan
public class WebConfig implements WebMvcConfigurer {

    @Override
    public void configureViewResolvers(ViewResolverRegistry registry) {
        registry.jsp("/WEB-INF/views/", ".jsp");
    }

    @Override
    public void configureDefaultServletHandling(DefaultServletHandlerConfigurer configurer) {
        configurer.enable();
    }
}

Saya menggunakan gradle, Anda harus memiliki dependensi berikut di pom.xml:

dependencies {

    compile group: 'org.springframework.boot', name: 'spring-boot-starter-web', version: '2.3.0.RELEASE'
    compile group: 'org.apache.tomcat.embed', name: 'tomcat-embed-jasper', version: '9.0.35'
}
Menandai
sumber
0

Saya menemukan alasan lain untuk kesalahan yang sama. Ini juga bisa terjadi karena file kelas tidak dibuat untuk file controller.java Anda. Akibatnya servlet operator yang disebutkan di web.xml tidak dapat memetakannya ke metode yang sesuai di kelas pengontrol.

@Controller
Class Controller{
@RequestMapping(value="/abc.html")//abc is the requesting page
public void method()
{.....}
}

Dalam gerhana di bawah Proyek-> pilih bersih -> Bangun Proyek. Lakukan centang apakah file kelas telah dibuat untuk file pengontrol di bawah membangun di ruang kerja Anda.

Anil NP
sumber
0

Bagi saya, saya menemukan bahwa kelas target saya dibuat dalam pola folder yang tidak sama dengan sumber. Ini mungkin di gerhana saya menambahkan folder untuk menampung pengontrol saya dan tidak menambahkannya sebagai paket. Jadi saya akhirnya menentukan jalur yang salah dalam konfigurasi musim semi.

Kelas target saya menghasilkan kelas di bawah aplikasi dan saya mengacu pada com.happy.app

<context:annotation-config />
<context:component-scan
    base-package="com.happy.app"></context:component-scan> 

Saya menambahkan paket (bukan folder) untuk com.happy.app dan memindahkan file dari folder ke paket di eclipse dan itu menyelesaikan masalah.

Roy
sumber
0

Bersihkan server Anda. Mungkin hapus server dan tambahkan proyek sekali lagi dan Jalankan.

  1. Hentikan server Tomcat

  2. Klik kanan server dan pilih "Bersihkan"

  3. Klik kanan server lagi dan pilih "Clean Tomcat Work Directory"

2rahulsk
sumber
0

Dalam kasus saya, saya bermain-main dengan mengimpor file konfigurasi java sekunder ke file konfigurasi java utama. Saat membuat file konfigurasi sekunder, saya telah mengubah nama kelas konfigurasi utama, tetapi saya gagal memperbarui nama di web.xml. Jadi, setiap kali saya me-restart server kucing jantan saya, saya tidak melihat penangan pemetaan dicatat di konsol Eclipse IDE, dan ketika saya mencoba menavigasi ke halaman beranda saya, saya melihat kesalahan ini:

1 Nov 2019 23:00:01 org.springframework.web.servlet.PageNotFound noHandlerFound PERINGATAN: Tidak ditemukan pemetaan untuk permintaan HTTP dengan URI [/ webapp / home / index] di DispatcherServlet dengan nama 'dispatcher'

Perbaikannya adalah memperbarui file web.xml sehingga nama lama "WebConfig" akan menjadi "MainConfig", cukup ganti namanya untuk mencerminkan nama terbaru dari file konfigurasi java utama (di mana "MainConfig" berubah-ubah dan kata-kata " Web "dan" Main "yang digunakan di sini bukan merupakan persyaratan sintaks). MainConfig penting, karena itu adalah file yang melakukan pemindaian komponen untuk "WebController", kelas pengontrol mvc pegas saya yang menangani permintaan web saya.

@ComponentScan(basePackageClasses={WebController.class})

web.xml memiliki ini:

<init-param>
    <param-name>contextConfigLocation</param-name>
    <param-value>
        com.lionheart.fourthed.config.WebConfig
    </param-value>
</init-param>

file web.xml sekarang memiliki:

<init-param>
    <param-name>contextConfigLocation</param-name>
    <param-value>
        com.lionheart.fourthed.config.MainConfig
    </param-value>
</init-param>

Sekarang saya melihat pemetaan di jendela konsol:

INFO: Dipetakan "{[/ home / index], metode = [GET]}" ke publik org.springframework.web.servlet.ModelAndView com.lionheart.fourthed.controller.WebController.gotoIndex ()

Dan halaman web saya dimuat lagi.

Steve T
sumber
-1

Saya memiliki masalah yang sama seperti **No mapping found for HTTP request with URI [/some/path] in DispatcherServlet with name SomeName**

Setelah saya menganalisa selama 2 sampai 4 hari saya menemukan akar penyebabnya. File kelas tidak dibuat setelah saya menjalankan proyek. Saya mengklik tab proyek.

Proyek -> CloseProject -> OpenProject -> Clean -> Bangun proyek

File kelas untuk kode sumber telah dibuat. Itu memecahkan masalah saya. Untuk memeriksa apakah file kelas telah dibuat atau tidak, Silakan periksa folder Build di folder proyek Anda.

Thanis Albert
sumber