传统spring项目所需xml配置:web.xml、application.xml、spring-mvc.xml
springboot怎么做到零配置与内嵌tomcat?
1、spring4开始就可以不用xml来配置了(注解/java config),用代码可完成上述三个xml的工作,替代xml。
最简单的spring web项目只需要两个依赖
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.2.1.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>5.1.1.RELEASE</version>
</dependency>
2、内嵌tomcat,tomcat是用java写的,tomcat加入到项目中,启动即可
引入jar包(第二个jar包避免报错,核心为第一个)
<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>
此方案基于servlet3,spi机制
spi,即service privider interface,是jdk为厂商和插件提供的一种解耦机制。
spi的具体规范为:当服务的提供者,提供了服务接口的一种实现之后,在jar包的META-INF/services/目录里同时创建一个以服务接口命名的文件。该文件里就是实现该服务接口的具体实现类。而当外部程序装配这个模块的时候,就能通过该jar包META-INF/services/里的配置文件找到具体的实现类名,并通过反射机制实例化,完成模块的注入。 基于这样一个约定就能很好的找到服务接口的实现类,而不需要再代码里制定。jdk提供服务实现查找的一个工具类:java.util.ServiceLoader
在spring-web中,定义了javax.servlet.ServletContainerInitializer的实现类org.springframework.web.SpringServletContainerInitializer,其注释对spi做了简单说明
在SpringServletContainerInitializer中会拿到所有WebApplicationInitializer的实现类(HandlesTypes注解),执行onStartup方法
@Override
public void onStartup(@Nullable Set<Class<?>> webAppInitializerClasses, ServletContext servletContext)
throws ServletException {
List<WebApplicationInitializer> initializers = new LinkedList<>();
if (webAppInitializerClasses != null) {
for (Class<?> waiClass : webAppInitializerClasses) {
// Be defensive: Some servlet containers provide us with invalid classes,
// no matter what @HandlesTypes says...
if (!waiClass.isInterface() && !Modifier.isAbstract(waiClass.getModifiers()) &&
WebApplicationInitializer.class.isAssignableFrom(waiClass)) {
try {
initializers.add((WebApplicationInitializer)
ReflectionUtils.accessibleConstructor(waiClass).newInstance());
}
catch (Throwable ex) {
throw new ServletException("Failed to instantiate WebApplicationInitializer class", ex);
}
}
}
}
if (initializers.isEmpty()) {
servletContext.log("No Spring WebApplicationInitializer types detected on classpath");
return;
}
servletContext.log(initializers.size() + " Spring WebApplicationInitializers detected on classpath");
AnnotationAwareOrderComparator.sort(initializers);
for (WebApplicationInitializer initializer : initializers) {
initializer.onStartup(servletContext);
}
}
附:tomcat与servlet对应关系
代码实践
spring对DispatcherServlet的注册与实例化例举了两种方式,一种为代码注册,一种为xml注册,所以使用代码注册即可替代web.xml。
https://docs.spring.io/spring/docs/5.2.1.RELEASE/spring-framework-reference/web.html#spring-web
拷贝官网代码,ac.refresh()可注释,因为在DispatcherServlet 的父类FrameworkServlet中initServletBean方法->initWebApplicationContext->configureAndRefreshWebApplicationContext方法最后会:wac.refresh(),会执行此方法,此方法为spring核心方法。
而不注释时,由于还没有初始化servlet,如果使用了某些servlet注解,ac.refresh()会报错。
public class MyWebApplicationInitializer implements WebApplicationInitializer {
@Override
public void onStartup(ServletContext servletCxt) {
System.out.println("init spring context");
// Load Spring web application configuration
AnnotationConfigWebApplicationContext ac = new AnnotationConfigWebApplicationContext();
ac.register(AppConfig.class);
// ac.refresh();
// Create and register the DispatcherServlet
DispatcherServlet servlet = new DispatcherServlet(ac);
// 将DispatcherServlet注册到tomcat
ServletRegistration.Dynamic registration = servletCxt.addServlet("app", servlet);
registration.setLoadOnStartup(1);
registration.addMapping("/app/*");
}
}
而application.xml及spring-mvc.xml可用注解来替代,如:
@Configuration
@ComponentScan({"com.demo"})
public class AppConfig {
}
至此,三个xml都不需要了,实现了0配置。
tomcat内嵌则如上文,利用spi机制。模拟springboot启动方式,写SpringApplication 类如下
public class SpringApplication {
public static void run(){
System.out.println("init tomcat");
Tomcat tomcat = new Tomcat();
tomcat.setPort(8080);
tomcat.addWebapp("/","d:\\spring\\");
try {
tomcat.start();
tomcat.getServer().await();
} catch (LifecycleException e) {
e.printStackTrace();
}
}
}
启动类
public class Application {
public static void main(String[] args) {
SpringApplication.run();
}
}
简单写一个controller
@Controller
public class DemoController {
@RequestMapping("/index")
@ResponseBody
public String index(){
System.out.println("hello");
return "hello";
}
}
执行main方法启动,首先初始化tomcat,然后根据spi机制,会执行MyWebApplicationInitializer 的onStartup初始化spring环境和servlet,至此模拟springboot成功
访问http://localhost:8080/app/index,请求成功