springboot零配置与内嵌tomcat原理(附基于spring项目实现零配置与内嵌tomcat代码)

传统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,请求成功

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值