spring mvc的零xml配置原理
在tomcat啟動(dòng)的時(shí)候咆爽,會(huì)調(diào)用到一個(gè)類脆丁,叫StandardContext音榜,這里會(huì)讀取我們配置的web.xml文件蔚鸥,所以可以簡(jiǎn)單的理解為“tomcat啟動(dòng)的時(shí)候默認(rèn)會(huì)調(diào)用web.xml”惜论。
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:applicationContext.xml</param-value>
</context-param>
在web.xml中,最開(kāi)始我們?cè)谂渲胢vc的時(shí)候都會(huì)配置這兩個(gè)東西止喷。ContextLoaderListener(spring中的類)實(shí)現(xiàn)了ServletContextListener(servlet中的類)馆类,ServletContextListener能夠監(jiān)聽(tīng)到servlet的生命周期,而一個(gè)web應(yīng)用啟動(dòng)的時(shí)候就只有1個(gè)servlet弹谁,因此相當(dāng)于監(jiān)聽(tīng)了整個(gè)web應(yīng)用乾巧。web啟動(dòng)后,會(huì)觸發(fā)ServletContextEvent事件预愤,此時(shí)就會(huì)調(diào)用ContextLoaderListener中的contextInitialized方法沟于,這個(gè)方法會(huì)調(diào)用initWebApplicationContext,返回一個(gè)WebApplicationContext鳖粟,在這個(gè)方法中社裆,會(huì)加載在web.xml中配置的classpath。因此在web.xml中配置的這段實(shí)際上的作用是“tomcat來(lái)初始化一個(gè)spring環(huán)境”向图。
public void contextInitialized(ServletContextEvent event) {
this.initWebApplicationContext(event.getServletContext());
}
public WebApplicationContext initWebApplicationContext(ServletContext servletContext) {
...
String configLocationParam = sc.getInitParameter(CONFIG_LOCATION_PARAM);//"contextConfigLocation"(設(shè)置配置文件的路徑名)
}
那么如果我們想不用xml配置泳秀,需要我們自己來(lái)初始化spring環(huán)境标沪,在一個(gè)spring項(xiàng)目中,我們初始化spring的方式是嗜傅。
@ComponentScan("com.bafan.spring.dependencies")
public class AppConfig {
}
AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext(AppConfig.class);
所以思路就是需要我們自己new一個(gè)ApplicationContext金句,然后需要tomcat在啟動(dòng)的時(shí)候能調(diào)用到它。
web.xml中另一個(gè)很重要的需要配置的點(diǎn)就是攔截器吕嘀,在使用web.xml的時(shí)候违寞,配置姿勢(shì)如下面所示。
<servlet>
<servlet-name>SpringMVC</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<load-on-startup>1</load-on-startup>
<async-supported>true</async-supported>
</servlet>
<servlet-mapping>
<servlet-name>SpringMVC</servlet-name>
<!-- 此處可以可以配置成*.do偶房,對(duì)應(yīng)struts的后綴習(xí)慣 -->
<url-pattern>/</url-pattern>
</servlet-mapping>
如果不通過(guò)web.xml的方式能夠讓上面的代碼別調(diào)用趁曼,mvc官方文檔中寫,可以實(shí)現(xiàn)WebApplicationInitializer棕洋,然后重寫onStartup方法挡闰。
/**
* mvc如何實(shí)現(xiàn)0xml配置
* onStartup方法,tomcat是如何能調(diào)用到的
*/
public class BafanWebApplication implements WebApplicationInitializer {
@Override
public void onStartup(ServletContext servletContext) throws ServletException {
System.out.println("tomcat------init---------");
//spring環(huán)境初始化
AnnotationConfigWebApplicationContext applicationContext = new AnnotationConfigWebApplicationContext();
applicationContext.register(AppConfig.class);
applicationContext.setServletContext(servletContext);
applicationContext.refresh();
//web環(huán)境初始化(servlet的配置)
DispatcherServlet servlet = new DispatcherServlet(applicationContext);
ServletRegistration.Dynamic registration = servletContext.addServlet("app", servlet);
registration.addMapping("*.do");
registration.setLoadOnStartup(1);
}
}
在tomcat啟動(dòng)的時(shí)候掰盘,會(huì)調(diào)用到我們重寫的onStartup方法摄悯,在這個(gè)方法中,我們初始化了spring的環(huán)境愧捕,因此就不用xml的方式初始spring的環(huán)境了奢驯,同時(shí)也初始化了web的環(huán)境。寫了這幾行代碼就可以不用配置web.xml了次绘。
為什么我們實(shí)現(xiàn)了WebApplicationInitializer tomcat就可以調(diào)用到我們重寫的方法了呢瘪阁?
- WebApplicationInitializer是在org.springframework.web這個(gè)包下面的,也就是說(shuō)這個(gè)接口是spring寫的断盛,點(diǎn)到這個(gè)jar包下罗洗。
- 它的根目錄中的/META-INF/services有一個(gè)文件,文件名是javax.servlet.ServletContainerInitializer钢猛,文件里面寫著org.springframework.web.SpringServletContainerInitializer。根據(jù)servlet的規(guī)范轩缤,tomcat和spring都遵守這個(gè)規(guī)范命迈,tomcat啟動(dòng)的時(shí)候會(huì)調(diào)用這個(gè)方法(servlet的規(guī)定)。
- 這個(gè)方法的上面火的,加了一個(gè)@HandlesTypes(WebApplicationInitializer.class)的注解壶愤,@Nullable Set<Class<?>> webAppInitializerClasses的入?yún)⒅校绻覀冏约簩?shí)現(xiàn)了WebApplicationInitializer這個(gè)接口的話馏鹤,這個(gè)入?yún)⒅袝?huì)拿到我們所有實(shí)現(xiàn)了這個(gè)接口類征椒,然后循環(huán)調(diào)用里面的onStartup方法。
for (WebApplicationInitializer initializer : initializers) {
initializer.onStartup(servletContext);
}
我們可以自己模擬一下這個(gè)過(guò)程
1. 在根目錄下創(chuàng)建一個(gè)/META-INF/services/javax.servlet.ServletContainerInitializer這個(gè)文件湃累,里面寫com.bafan.spring.web.test.BafanServletContainerInitializer
2.
@HandlesTypes(A.class)
public class BafanServletContainerInitializer implements ServletContainerInitializer {
@Override
public void onStartup(Set<Class<?>> set, ServletContext servletContext) throws ServletException {
System.out.println("執(zhí)行了");
}
}
3. 啟動(dòng)tomcat勃救,這時(shí)候set會(huì)傳進(jìn)來(lái)B和C(都實(shí)現(xiàn)了A這個(gè)接口)碍讨。
PS:
在代碼中引入
<dependency>
<groupId>org.apache.tomcat</groupId>
<artifactId>tomcat-catalina</artifactId>
<version>8.5.43</version>
</dependency>
<dependency>
<groupId>org.apache.tomcat.embed</groupId>
<artifactId>tomcat-embed-jasper</artifactId>
<version>8.5.45</version>
</dependency>
/**
* 如何使用一個(gè)jar包的方式用main方法來(lái)啟動(dòng)tomcat
*/
public class TomcatServer {
public static void main(String[] args) {
Tomcat tomcat = new Tomcat();
tomcat.setPort(8080);
tomcat.addWebapp("/", "/Users/maxiaojun/bafan/tomcat");
try {
tomcat.start();
tomcat.getServer().await();
} catch (LifecycleException e) {
e.printStackTrace();
}
}
}
這樣就可以在代碼中使用一個(gè)方法來(lái)啟動(dòng)tomcat,感覺(jué)很高大尚蒙秒。
一個(gè)mvc項(xiàng)目勃黍,如果什么都不配置的話
@RequestMapping("/query.do")
@ResponseBody
public Map<String, String> query() {
System.out.println("query controller");
Map<String, String> map = new HashMap<String, String>();
map.put("xxx", "xxxx");
return map;
}
這行代碼會(huì)報(bào)500的錯(cuò)誤,因?yàn)閙vc默認(rèn)不知道咋對(duì)Map(對(duì)象也一樣)進(jìn)行解析晕讲,所以需要我們配置一下覆获,告訴mvc要用fastjson來(lái)解析
@Configuration
@ComponentScan("com.bafan.spring.web")
@EnableWebMvc
public class AppConfig implements WebMvcConfigurer {
@Override
public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
converters.add(new FastJsonHttpMessageConverter());
}
}
實(shí)現(xiàn)WebMvcConfigurer,重寫configureMessageConverters方法瓢省,然后加一下fastjson解析器就ok了弄息。
思考方式:
- tomcat啟動(dòng)的時(shí)候是怎么初始化的spring環(huán)境?
- tomcat初始化web環(huán)境的時(shí)候有哪些比較重要的配置勤婚?
- 為什么實(shí)現(xiàn)了WebApplicationInitializer之后疑枯,里面的onStartUp方法就能別調(diào)用到?