Bootstrap

01.手写SpringBoot源码

1、SpringBoot依赖配置

SpringBoot是基于Spring框架开发的,所以模拟写SpringBoot需导入以下Spring、SpringMvc相关的包;
SpringBoot默认启动服务是tomcat,所以也需要导入tomcat 相关的jar包;
具体导入包如下:

<dependencies>
    <!-- 依赖Spring、SpringMvc、Tomcat包 -->
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-context</artifactId>
        <version>5.3.18</version>
    </dependency>
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-web</artifactId>
        <version>5.3.18</version>
    </dependency>
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-webmvc</artifactId>
        <version>5.3.18</version>
    </dependency>

    <dependency>
        <groupId>javax.servlet</groupId>
        <artifactId>javax.servlet-api</artifactId>
        <version>4.0.1</version>
    </dependency>

    <dependency>
        <groupId>org.apache.tomcat.embed</groupId>
        <artifactId>tomcat-embed-core</artifactId>
        <version>9.0.60</version>
    </dependency>
</dependencies>

2、模拟手写自动配置

2.1 模拟写条件注解

当条件注解匹配为true时,被标注的bean才会生效注入到容器当中,模拟某个类是否存在为例,对应的注解如下:

@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Conditional({MyOnClassCondition.class})
public @interface MyConditionalOnClass {

    String value();
}

当某个类标准了该注解,Spring解析该类时会走MyOnClassCondition中matches方法,进行匹配判断,对应的注解匹配解析逻辑如下:

public class MyOnClassCondition implements Condition {

    @Override
    public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {

        /**
         *  装载类是否存在  存在则返回true
         */
        Map<String, Object> annotationAttributes = metadata.getAnnotationAttributes(MyConditionalOnClass.class.getName());
        String value = (String) annotationAttributes.get("value");
        try {
            context.getClassLoader().loadClass(value);
            return true;
        } catch (Throwable throwable) {
            return false;
        }
    }
}

2.2 模拟写自动配置类

自动配置是基于条件注解实现自动配置的,这里模拟写tomcat与jetty根据导入不同的包实现容器的切换,根据环境中存在某个class文件实行注入不通的容器,
实现方法如下:

@Configuration
public class WebServerAutoConfiguration implements AutoConfiguration {

    @Bean
    @MyConditionalOnClass("org.apache.catalina.startup.Tomcat")
    public TomcatWebServer tomcatWebServer() {
        return new TomcatWebServer();
    }


    @Bean
    @MyConditionalOnClass("org.eclipse.jetty.server.Server")
    public JettyWebServer jettyWebServer() {
        return new JettyWebServer();
    }
}

实现AutoConfiguration接口是为了模拟SpringBoot扫描自动配置类,使用Java的SPL机制进行读取

2.3 模拟发现自动配置类

SpringBoot是通过@Import将自动配置类导入,其中Spring是通过实现DeferredImportSelector接口,完成自动配置bean的导入;
其中DeferredImportSelector与ImportSelector的区别在与实现DeferredImportSelector会在自身的bean注入完成后才注入自配置的bean;
SpringBoot是通过读取spring.factories文件查找需要导入的配置类,这里模拟使用Java的SPL机制进行读取,实现如下:

/**
 *  DeferredImportSelector 与 ImportSelector的区别:
 *      DeferredImportSelector等用户的bean注入后进行注入,当自动配置类使用ConditionalOnMissingBean注解,用户配置了对应的bean可使
 *      SpringBoot自动配置类不生效
 */
public class MyImportSelect implements DeferredImportSelector {


    @Override
    public String[] selectImports(AnnotationMetadata importingClassMetadata) {

        /**
         *  使用JAVA的SPI机制模拟SpringBoot的spring.factories机制
         */
        ServiceLoader<AutoConfiguration> serviceLoader = ServiceLoader.load(AutoConfiguration.class);
        List<String> list = new ArrayList<>();
        for (AutoConfiguration autoConfiguration : serviceLoader) {
            list.add(autoConfiguration.getClass().getName());
        }
        return list.toArray(new String[0]);
    }
}

3、模拟@SpringBootApplication注解

@SpringBootApplication注解主要是完成自定义的bean的扫描以及SpringBoot自动配置类的导入,实现如下:

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Configuration
@ComponentScan
@Import(MyImportSelect.class)
public @interface MySpringBootApplication {
}

4、模拟SpringBoot实现启动

SpringBoot主程序启动,主要实现容器创建、主启动类装载使@SpringBootApplication生效、启动web容器,模拟如下:

public class MySpringApplication {

    /**
     *
     * @param clazz
     */
    public static void run(Class clazz) {

        /**
         *  1、创建一个Spring Web容器
         */
        AnnotationConfigWebApplicationContext webApplicationContext = new AnnotationConfigWebApplicationContext();
        /**
         *  2、装载主启动类,使启动类上的注解生效
         */
        webApplicationContext.register(clazz);
        webApplicationContext.refresh();
        /**
         *  3、启动容器
         */
        startWebServer(webApplicationContext);
    }

    /**
     * 启动web容器
     * @param webApplicationContext
     */
    private static void startWebServer(AnnotationConfigWebApplicationContext webApplicationContext) {

        Map<String, WebServer> webServers = webApplicationContext.getBeansOfType(WebServer.class);
        if(webServers.isEmpty()) {
            throw new NullPointerException("未注入相关web容器包");
        }
        if(webServers.size() > 1) {
            throw new IllegalArgumentException("存在多个web容器");
        }
        webServers.values().stream().findFirst().get().start();
    }
}

5、tomcat容器启动示例

当导入的事tomcat容器,Spring容器中将能获取到TomcatWebServer,其中实现如下:

/**
 * @Author:qww
 * @name:TomcatWebServer
 * @Date:2023/6/6 14:22
 * @Description tomcat容器启动
 * ApplicationContextAware,可将IOC容器注入到类
 */
public class TomcatWebServer implements WebServer, ApplicationContextAware {

    private WebApplicationContext webApplicationContext;

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        this.webApplicationContext = (WebApplicationContext) applicationContext;
    }

    @Override
    public void start() {
        Tomcat tomcat = new Tomcat();

        Server server = tomcat.getServer();
        Service service = server.findService("Tomcat");

        Connector connector = new Connector();
        connector.setPort(8081);

        Engine engine = new StandardEngine();
        engine.setDefaultHost("localhost");

        Host host = new StandardHost();
        host.setName("localhost");

        String contextPath = "";
        Context context = new StandardContext();
        context.setPath(contextPath);
        context.addLifecycleListener(new Tomcat.FixContextListener());

        host.addChild(context);
        engine.addChild(host);

        service.setContainer(engine);
        service.addConnector(connector);

        tomcat.addServlet(contextPath, "dispatcher", new DispatcherServlet(webApplicationContext));
        context.addServletMappingDecoded("/*", "dispatcher");

        try {
            tomcat.start();
            System.out.println("Tomcat启动了");
        } catch (LifecycleException e) {
            e.printStackTrace();
        }
    }

}

6、测试

导入自定义的SpringBoot模块,使用模拟启动SpringBoot相关注解及启动类进行启动即可测试,如下:

<dependency>
    <groupId>com.qww</groupId>
    <artifactId>my-springboot</artifactId>
    <version>1.0</version>
</dependency>
@MySpringBootApplication
public class MainApplication {

    public static void main(String[] args) {
        MySpringApplication.run(MainApplication.class);
    }
}

7、源码地址

https://gitee.com/qianweiwei1994/spring-cloud-study/tree/master/01-write-springboot# 1、SpringBoot依赖配置

SpringBoot是基于Spring框架开发的,所以模拟写SpringBoot需导入以下Spring、SpringMvc相关的包;
SpringBoot默认启动服务是tomcat,所以也需要导入tomcat 相关的jar包;
具体导入包如下:

<dependencies>
    <!-- 依赖Spring、SpringMvc、Tomcat包 -->
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-context</artifactId>
        <version>5.3.18</version>
    </dependency>
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-web</artifactId>
        <version>5.3.18</version>
    </dependency>
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-webmvc</artifactId>
        <version>5.3.18</version>
    </dependency>

    <dependency>
        <groupId>javax.servlet</groupId>
        <artifactId>javax.servlet-api</artifactId>
        <version>4.0.1</version>
    </dependency>

    <dependency>
        <groupId>org.apache.tomcat.embed</groupId>
        <artifactId>tomcat-embed-core</artifactId>
        <version>9.0.60</version>
    </dependency>
</dependencies>

2、模拟手写自动配置

2.1 模拟写条件注解

当条件注解匹配为true时,被标注的bean才会生效注入到容器当中,模拟某个类是否存在为例,对应的注解如下:

@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Conditional({MyOnClassCondition.class})
public @interface MyConditionalOnClass {

    String value();
}

当某个类标准了该注解,Spring解析该类时会走MyOnClassCondition中matches方法,进行匹配判断,对应的注解匹配解析逻辑如下:

public class MyOnClassCondition implements Condition {

    @Override
    public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {

        /**
         *  装载类是否存在  存在则返回true
         */
        Map<String, Object> annotationAttributes = metadata.getAnnotationAttributes(MyConditionalOnClass.class.getName());
        String value = (String) annotationAttributes.get("value");
        try {
            context.getClassLoader().loadClass(value);
            return true;
        } catch (Throwable throwable) {
            return false;
        }
    }
}

2.2 模拟写自动配置类

自动配置是基于条件注解实现自动配置的,这里模拟写tomcat与jetty根据导入不同的包实现容器的切换,根据环境中存在某个class文件实行注入不通的容器,
实现方法如下:

@Configuration
public class WebServerAutoConfiguration implements AutoConfiguration {

    @Bean
    @MyConditionalOnClass("org.apache.catalina.startup.Tomcat")
    public TomcatWebServer tomcatWebServer() {
        return new TomcatWebServer();
    }


    @Bean
    @MyConditionalOnClass("org.eclipse.jetty.server.Server")
    public JettyWebServer jettyWebServer() {
        return new JettyWebServer();
    }
}

实现AutoConfiguration接口是为了模拟SpringBoot扫描自动配置类,使用Java的SPL机制进行读取

2.3 模拟发现自动配置类

SpringBoot是通过@Import将自动配置类导入,其中Spring是通过实现DeferredImportSelector接口,完成自动配置bean的导入;
其中DeferredImportSelector与ImportSelector的区别在与实现DeferredImportSelector会在自身的bean注入完成后才注入自配置的bean;
SpringBoot是通过读取spring.factories文件查找需要导入的配置类,这里模拟使用Java的SPL机制进行读取,实现如下:

/**
 *  DeferredImportSelector 与 ImportSelector的区别:
 *      DeferredImportSelector等用户的bean注入后进行注入,当自动配置类使用ConditionalOnMissingBean注解,用户配置了对应的bean可使
 *      SpringBoot自动配置类不生效
 */
public class MyImportSelect implements DeferredImportSelector {


    @Override
    public String[] selectImports(AnnotationMetadata importingClassMetadata) {

        /**
         *  使用JAVA的SPI机制模拟SpringBoot的spring.factories机制
         */
        ServiceLoader<AutoConfiguration> serviceLoader = ServiceLoader.load(AutoConfiguration.class);
        List<String> list = new ArrayList<>();
        for (AutoConfiguration autoConfiguration : serviceLoader) {
            list.add(autoConfiguration.getClass().getName());
        }
        return list.toArray(new String[0]);
    }
}

3、模拟@SpringBootApplication注解

@SpringBootApplication注解主要是完成自定义的bean的扫描以及SpringBoot自动配置类的导入,实现如下:

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Configuration
@ComponentScan
@Import(MyImportSelect.class)
public @interface MySpringBootApplication {
}

4、模拟SpringBoot实现启动

SpringBoot主程序启动,主要实现容器创建、主启动类装载使@SpringBootApplication生效、启动web容器,模拟如下:

public class MySpringApplication {

    /**
     *
     * @param clazz
     */
    public static void run(Class clazz) {

        /**
         *  1、创建一个Spring Web容器
         */
        AnnotationConfigWebApplicationContext webApplicationContext = new AnnotationConfigWebApplicationContext();
        /**
         *  2、装载主启动类,使启动类上的注解生效
         */
        webApplicationContext.register(clazz);
        webApplicationContext.refresh();
        /**
         *  3、启动容器
         */
        startWebServer(webApplicationContext);
    }

    /**
     * 启动web容器
     * @param webApplicationContext
     */
    private static void startWebServer(AnnotationConfigWebApplicationContext webApplicationContext) {

        Map<String, WebServer> webServers = webApplicationContext.getBeansOfType(WebServer.class);
        if(webServers.isEmpty()) {
            throw new NullPointerException("未注入相关web容器包");
        }
        if(webServers.size() > 1) {
            throw new IllegalArgumentException("存在多个web容器");
        }
        webServers.values().stream().findFirst().get().start();
    }
}

5、tomcat容器启动示例

当导入的事tomcat容器,Spring容器中将能获取到TomcatWebServer,其中实现如下:

/**
 * @Author:qww
 * @name:TomcatWebServer
 * @Date:2023/6/6 14:22
 * @Description tomcat容器启动
 * ApplicationContextAware,可将IOC容器注入到类
 */
public class TomcatWebServer implements WebServer, ApplicationContextAware {

    private WebApplicationContext webApplicationContext;

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        this.webApplicationContext = (WebApplicationContext) applicationContext;
    }

    @Override
    public void start() {
        Tomcat tomcat = new Tomcat();

        Server server = tomcat.getServer();
        Service service = server.findService("Tomcat");

        Connector connector = new Connector();
        connector.setPort(8081);

        Engine engine = new StandardEngine();
        engine.setDefaultHost("localhost");

        Host host = new StandardHost();
        host.setName("localhost");

        String contextPath = "";
        Context context = new StandardContext();
        context.setPath(contextPath);
        context.addLifecycleListener(new Tomcat.FixContextListener());

        host.addChild(context);
        engine.addChild(host);

        service.setContainer(engine);
        service.addConnector(connector);

        tomcat.addServlet(contextPath, "dispatcher", new DispatcherServlet(webApplicationContext));
        context.addServletMappingDecoded("/*", "dispatcher");

        try {
            tomcat.start();
            System.out.println("Tomcat启动了");
        } catch (LifecycleException e) {
            e.printStackTrace();
        }
    }

}

6、测试

导入自定义的SpringBoot模块,使用模拟启动SpringBoot相关注解及启动类进行启动即可测试,如下:

<dependency>
    <groupId>com.qww</groupId>
    <artifactId>my-springboot</artifactId>
    <version>1.0</version>
</dependency>
@MySpringBootApplication
public class MainApplication {

    public static void main(String[] args) {
        MySpringApplication.run(MainApplication.class);
    }
}

7、源码地址

https://gitee.com/qianweiwei1994/spring-cloud-study/tree/master/01-write-springboot

;