Spring Boot是零配置无xml配置文件,其中对于MVC,可以实现无web.xml文件。
那么是如何做到的呢? 本文先通过一个例子演示无web.xml的效果,然后讲解JDK中的SPI机制,以及servlet如何利用SPI机制。最后分析tomcat调用加载的过程。
一、Spring MVC 无web.xml配置
传统式对于Java Web服务,一般都需要web.xml作为Web服务的顶级配置文件。
然而对于Spring来说,且实现了Servlet 3.0既以上的容器(如Tomcat),可以无需web.xml文件,即可正常运行Web服务,达到同等效果。
1、演示如下
1、在Eclipse中新建一个动态Web工程。工程名任意,这里工程名暂定为NoXML
2、将Spring 5.X Framework 的二十几个jar和commons-logging相关的jar文件,放入NoXML\WebContent\WEB-INF\lib目录下。 同时在Eclipse设置工程引用这些jar文件。具体文件为:
commons-fileupload-1.4.jar
commons-io-2.6.jar
commons-logging-1.2.jar
spring-aop-5.2.2.RELEASE.jar
spring-aspects-5.2.2.RELEASE.jar
spring-beans-5.2.2.RELEASE.jar
spring-context-5.2.2.RELEASE.jar
spring-context-indexer-5.2.2.RELEASE.jar
spring-context-support-5.2.2.RELEASE.jar
spring-core-5.2.2.RELEASE.jar
spring-expression-5.2.2.RELEASE.jar
spring-instrument-5.2.2.RELEASE.jar
spring-jcl-5.2.2.RELEASE.jar
spring-jdbc-5.2.2.RELEASE.jar
spring-jms-5.2.2.RELEASE.jar
spring-messaging-5.2.2.RELEASE.jar
spring-orm-5.2.2.RELEASE.jar
spring-oxm-5.2.2.RELEASE.jar
spring-test-5.2.2.RELEASE.jar
spring-tx-5.2.2.RELEASE.jar
spring-web-5.2.2.RELEASE.jar
spring-webflux-5.2.2.RELEASE.jar
spring-webmvc-5.2.2.RELEASE.jar
spring-websocket-5.2.2.RELEASE.jar
3、增加一个Java类com.zyp.MyWebApplicationInitializer,代码拷贝自Spring官方文档(https://docs.spring.io/spring/docs/current/spring-framework-reference/web.html)如下:
package com.zyp;
import org.springframework.web.WebApplicationInitializer;
import org.springframework.web.context.support.AnnotationConfigWebApplicationContext;
import org.springframework.web.servlet.DispatcherServlet;
import javax.servlet.ServletContext;
import javax.servlet.ServletRegistration;
public class MyWebApplicationInitializer implements WebApplicationInitializer {
@Override
public void onStartup(ServletContext servletCxt) {
System.err.println("------------Load Spring web application configuration");
AnnotationConfigWebApplicationContext ac = new AnnotationConfigWebApplicationContext();
ac.register(AppConfig.class);
ac.refresh();
System.err.println("------------Create and register the DispatcherServlet");
DispatcherServlet servlet = new DispatcherServlet(ac);
ServletRegistration.Dynamic registration = servletCxt.addServlet("app", servlet); // 第一个参数为servlet名
registration.setLoadOnStartup(1);
registration.addMapping("/app/*");
}
}
4、增加一个Java配置类(Java Configuration Class)。com.zyp.AppConfig,代码如下:
package com.zyp;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
@Configuration
@ComponentScan(basePackages = {
"com.zyp.controller"})
public class AppConfig {
}
4、增加一个Java Controller用于测试。com.zyp.AppConfig,为简单没有返回网页,而是输出日志查看是否被正常访问。代码如下:
package com.zyp.controller;
import java.util.ArrayList;
import java.util.List;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.CookieValue;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.servlet.ModelAndView;
import com.zyp.domain.User;
@Controller
public class UserController {
@RequestMapping(value= "/index.do")
@ResponseBody
public String index() {
System.out.println("index.do has been called sucessfully");
return "index.do has been called sucessfully";
}
}
5、对此。无web.xml的Web工程配置完毕。 启动一个tomcat server,在tomcat server纳入本工程(本NoXML工程) 。在浏览器中访问:http://localhost:8080/NoXML/app/index.do 通过日志可以看到定义的Controller index.do成功访问,且浏览器中显示的内容为:
2、分析说明
替代了什么
通过上文的MyWebApplicationInitializer
类,实现的效果类似于以下的web.xml。通过以上AppConfig
类,实现了常用的app-context.xml的配置。
<web-app>
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>/WEB-INF/app-context.xml</param-value>
</context-param>
<servlet>
<servlet-name>app</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value></param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>app</servlet-name>
<url-pattern>/app/*</url-pattern>
</servlet-mapping>
</web-app>
以上Java代码分析
**以上无web.xml能正常运行Web,并不是因为Spring的功能,而是Servlet规范的定义。**从servlet3.0后springMVC提供了WebApplicationInitializer接口替代了Web.xml。而JavaConfig的方式替代了springmvc-config.xml。
具体说明如下:
1、AnnotationConfigWebApplicationContext ac = new AnnotationConfigWebApplicationContext()
这句Java代码,实现的效果类似于web.xml中的<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
这句。 他们的效果是,加载初始化Spring的Context上下文文件。
2、在上面初始化Spring Context上下文传入参数,在web.xml文件中参数时/WEB-INF/app-context.xml
, 该文件定义了具体信息,包括需要扫描的packages。而在Java代码中,使用AppConfig
类(该类是Java configuration class)来定义相关信息。
3、在Java中后面的代码实现的效果,既等价于web.xml全局的DispatcherServlet定义,包括servlet-mapping。 而且在这里的Java代码中,DispatcherServlet是我们人工主动new的对象。而传统的web.xml是有容器自动new创建的。
4、通过借用以上Servlet 3.0及以上的规范,即可完全抛弃web.xml文件。 这也为Spring boot零配置打下了基础。
tomcat如何加载的
以上MyWebApplicationInitializer
类,tomcat如何找到这个类,如何调用它的?。
**可能1:**由于MyWebApplicationInitializer
实现了WebApplicationInitializer
接口,tomcat可能通过该接口找到实现类。 但是这里的问题是WebApplicationInitializer
接口是Spring的,它不是tomcat的,也不是容器规范。所以该接口tomcat是无法知道的,无法认识,也无法调用。
**可能2:**一种特殊规范,既SPI=service privider interface。这时Java本身的一个规范。 在Servlet 3.0规范后,利用了该SPI规范,实现无web.xml。既在jar包的META-INF/services/目录里同时创建一个以服务接口命名的文件(本以上例子中在spring-web-5.2.2.RELEASE.jar中),当容器装载这个jar模块时,就能通过这个规定的目录和文件,找到对应的类(这里是接口)。这时tomcat才识别到了Spring的WebApplicationInitializer
接口,然后再通过反射机制找到所有实现类,最后for循环实现类,逐个进行new实例化并调用接口中的onStartup()
方法,同时容器将自身Servlet的Context作为入参传入。 这就是tomcat能自动找到这个类的机制。
二、SPI机制
关于SPI,先看一个之前使用数据库,需要DriverManager获取DB连接之前,我们总是需要显示