返回

SpringBoot2源码1-嵌入式Tomcat启动和@SpringBootApplication注解原理

发布时间:2023-01-16 13:43:43 376
# webkit# java# apache# 容器# 服务器

1. 测试案例

1.1 引入依赖

<!-- https://mvnrepository.com/artifact/org.springframework/spring-webmvc -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>5.3.5</version>
</dependency>

<!-- https://mvnrepository.com/artifact/org.apache.tomcat.embed/tomcat-embed-core -->
<dependency>
<groupId>org.apache.tomcat.embed</groupId>
<artifactId>tomcat-embed-core</artifactId>
<version>8.5.64</version>
</dependency>

<!-- https://mvnrepository.com/artifact/org.apache.tomcat.embed/tomcat-embed-jasper -->
<dependency>
<groupId>org.apache.tomcat.embed</groupId>
<artifactId>tomcat-embed-jasper</artifactId>
<version>8.5.64</version>
</dependency>

1.2 Servlet处理类

public class HelloServlet extends HttpServlet {

@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
doPost(req, resp);
}

@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
resp.getWriter().write("hello tomcat");
}
}

1.3 启动类

public class Main {

public static void main(String[] args) throws LifecycleException {
//自己写Tomcat的启动源码
Tomcat tomcat = new Tomcat();

tomcat.setPort(8888);
tomcat.setHostname("localhost");
tomcat.setBaseDir(".");

Context context = tomcat.addWebapp("/boot", System.getProperty("user.dir") + "/src/main");

//给Tomcat里面添加一个Servlet
Wrapper hello = tomcat.addServlet("/boot", "hello", new HelloServlet());
hello.addMapping("/66"); //指定处理的请求

tomcat.start(); //启动tomcat 注解版MVC利用Tomcat SPI机制

tomcat.getServer().await(); //服务器等待
}
}

直接运行main方法,在浏览器中输入 ​​http://localhost:8888/boot/66​​ ,输出为:

hello tomcat

以上只是一个​​Tomcat​​的启动并使用​​HelloServlet​​处理一个请求的案例。下面我们结合SpringMVC,看如何优雅的启动。

2. 嵌入式Tomcat启动SpringMVC

2.1 最简单的方法

public class Main {

public static void main(String[] args) throws LifecycleException {
//自己写Tomcat的启动源码
Tomcat tomcat = new Tomcat();

tomcat.setPort(8888);
tomcat.setHostname("localhost");
tomcat.setBaseDir(".");

Context context = tomcat.addWebapp("/boot", System.getProperty("user.dir") + "/src/main");

//自己创建 DispatcherServlet 对象,并且创建ioc容器,DispatcherServlet里面有ioc容器
DispatcherServlet servlet = new DispatcherServlet();
Wrapper hello = tomcat.addServlet("/boot", "hello", servlet);

tomcat.start(); //启动tomcat 注解版MVC利用Tomcat SPI机制

tomcat.getServer().await(); //服务器等待
}
}

当然,如果使用这种方法启动,需要自己往容器中注入​​DispatcherServlet​​的各种初始化(九大组件等),所以这种方式比较麻烦,我们不采用这种方式。

2.2 Tomcat的SPI机制启动

利用tomcat的SPI启动机制,SPI机制下 ​​QuickAppStarter​​生效创建 ioc容器配置​​DispatcherServlet​​等各种组件。代码如下:

Main.java:

public class Main {

public static void main(String[] args) throws LifecycleException {
//自己写Tomcat的启动源码
Tomcat tomcat = new Tomcat();

tomcat.setPort(8888);
tomcat.setHostname("localhost");
tomcat.setBaseDir(".");

Context context = tomcat.addWebapp("/boot", System.getProperty("user.dir") + "/src/main");

tomcat.start(); //启动tomcat 注解版MVC利用Tomcat SPI机制

tomcat.getServer().await(); //服务器等待

}

}

QuickAppStarter类:

/**
* 最快速的整合注解版SpringMVC和Spring的
*/
public class QuickAppStarter extends AbstractAnnotationConfigDispatcherServletInitializer {
@Override //根容器的配置(Spring的配置文件===Spring的配置类)
protected Class<?>[] getRootConfigClasses() {
return new Class<?>[]{SpringConfig.class};
}

@Override //web容器的配置(SpringMVC的配置文件===SpringMVC的配置类)
protected Class<?>[] getServletConfigClasses() {
return new Class<?>[]{SpringMVCConfig.class};
}

@Override //Servlet的映射,DispatcherServlet的映射路径
protected String[] getServletMappings() {
return new String[]{"/"};
}

@Override
protected void customizeRegistration(ServletRegistration.Dynamic registration) {
// super.customizeRegistration(registration);

// registration.addMapping("");//
}
}

启动后,在浏览器访问​​http://localhost:8888/boot/hello66​​ ,输出为:

66666666~~~~~

SpringBoot就是采用上面的这种方式来启动Tomcat的。SpringBoot封装了功能的自动配置,导入各种starter依赖,SpringBoot封装了很多的自动配置,帮我们给容器中放了很多组件。

3. @SpringBootApplication 注解原理

1.@SpringBootApplication注解

对于Springboot项目,它有一个启动类。一般如下(此处以Springboot演示案例为例):

@SpringBootApplication
public class DemoSpringbootApplication{
public static void main(String[] args) {
SpringApplication.run(DemoSpringbootApplication.class, args);
}
}

从上述代码里看出,启动类引用了一个注解 @SpringBootApplication,而
@SpringBootApplication实际上是一个复合注解,它的类定义如下所示:

@Target(ElementType.TYPE)         
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@SpringBootConfiguration // 继承了Configuration,表示当前是注解类
@EnableAutoConfiguration // 开启自动配置
@ComponentScan(excludeFilters = { // 扫描组件
@Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),
@Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) })
public @interface SpringBootApplication {
...
}

实际上 @SpringBootApplication内的核心注解有三个: @SpringBootConfiguration@EnableAutoConfiguration@ComponentScan

SpringBoot2源码1-嵌入式Tomcat启动和@SpringBootApplication注解原理_tomcat

3.1 @SpringBootConfiguration

​@SpringBootConfiguration​​实际上是引入的​​@Configuration​​,而​​@Configuration​​是Spring的注解类,用于定义配置类,并会将当前类内声明的一个或多个以​​@Bean​​注解标记的方法的实例纳入到srping容器中,并且实例名就是方法名,等同于spring的XML配置文件。

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Configuration
public @interface SpringBootConfiguration {
}

@Configuration与XML配置的简单对比如下:

@Configuration
public class SpringDemo{

}

等价于XML形式的​​​​:


xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.0.xsd">

3.2 @EnableAutoConfiguration

​@EnableAutoConfiguration​​是借助​​@Import​​的帮助,将所有符合自动配置条件的bean定义加载到IoC容器,它也是复合注解,定义如下所示:

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage //自动配置包
@Import({AutoConfigurationImportSelector.class}) //借助AutoConfigurationImportSelector自动配置类
public @interface EnableAutoConfiguration {
String ENABLED_OVERRIDE_PROPERTY = "spring.boot.enableautoconfiguration";
Class<?>[] exclude() default {};
String[] excludeName() default {};
}

​@EnableAutoConfiguration​​里重要的注解分别是​​@AutoConfigurationPackage​​和​​@Import(AutoConfigurationImportSelector.class)​​,看名知意,​​@AutoConfigurationPackage​​:自动配置包,​​AutoConfigurationImportSelector​​:自动配置组件的导入。

3.2.1 @AutoConfigurationPackage

​@AutoConfigurationPackage​​具体定义如下所示:

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@Import({Registrar.class})
public @interface AutoConfigurationPackage {
}

由​​@AutoConfigurationPackage​​定义可以看出,实际是借助​​@Import​​导入了​​Registrar​​,而​​Registrar​​中主要调用了​​registerBeanDefinition​​方法来进行bean定义的注册。Registrar类定义如下:

static class Registrar implements ImportBeanDefinitionRegistrar, DeterminableImports {
Registrar() {
}
public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
//此处是注册了一个Bean的定义。
//getPackageName()其实返回了当前主程序类的 同级以及子级的包组件。
AutoConfigurationPackages.register(registry, (new AutoConfigurationPackages.PackageImport(metadata)).getPackageName());

}
public Set<Object> determineImports(AnnotationMetadata metadata) {
return Collections.singleton(new AutoConfigurationPackages.PackageImport(metadata));
}
}
特别声明:以上内容(图片及文字)均为互联网收集或者用户上传发布,本站仅提供信息存储服务!如有侵权或有涉及法律问题请联系我们。
举报
评论区(0)
按点赞数排序
用户头像
精选文章
thumb 中国研究员首次曝光美国国安局顶级后门—“方程式组织”
thumb 俄乌线上战争,网络攻击弥漫着数字硝烟
thumb 从网络安全角度了解俄罗斯入侵乌克兰的相关事件时间线