手写springboot

前言

 首先确定springboot在spring基础上主要做了哪些改动:
  1. 内嵌tomcat
  2. spi技术动态加载

一、基本实现

1. 建一个工程目录结构如下:

springboot:  源码实现逻辑
user         :   业务系统

手写springboot_第1张图片

2.springboot工程项目构建

1. pom依赖如下

   <dependencies>
            <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. SpringBoot时,核心会用到SpringBoot一个类和注解:

  1. @SpringBootApplication,这个注解是加在应用启动类上的,也就是main方法所在的类
  2. SpringApplication,这个类中有个run()方法,用来启动SpringBoot应用的.

下面一一实现:

 @Retention(RetentionPolicy.RUNTIME)
 @Target(ElementType.TYPE)
 @Configuration
 @ComponentScan
 @Import(KcImportSelect.class)
public @interface KcSpringBootApplication {

}
public class KcSpringApplication {

    public static AnnotationConfigWebApplicationContext  run(Class cls){
        /**
         * spring启动步骤:
         * 1、构建上下文对象
         * 2、注册配置类
         * 3、刷新容器
         */
        AnnotationConfigWebApplicationContext  context=new AnnotationConfigWebApplicationContext();
        context.register(cls);
        context.refresh();
        /**
         * 内嵌tomcat\jetty  启动
         */
        WebServer  webServer=getWebServer(context);
        webServer.start();
        return context;
    }

    /**
     * 获取服务,有可能tomcat\jetty或者其他服务器
     * @param context
     * @return
     */
    public static WebServer getWebServer(AnnotationConfigWebApplicationContext  context){

        Map<String, WebServer> beansOfType = context.getBeansOfType(WebServer.class);

        if(beansOfType.isEmpty()||beansOfType.size()>1){
            throw new NullPointerException();
        }

        return beansOfType.values().stream().findFirst().get();
    }

}

3.内嵌tomcat、jetty服务器实现

项目根据pom配置动态实现tomcat\jetty内嵌要求如下:

  1. 如果项目中有Tomcat的依赖,那就启动Tomcat
  2. 如果项目中有Jetty的依赖就启动Jetty
  3. 如果两者都没有则报错
  4. 如果两者都有也报错

首先定义服务接口WebServer

public interface WebServer {

    void  start()  ;
}

tomcat服务实现:

public class TomcatWebServer implements WebServer {

    private Tomcat tomcat;

    public TomcatWebServer(WebApplicationContext webApplicationContext) {

        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");

    }

    @Override
    public void start() {
        try {
            System.out.println("tomcat start......");
            tomcat.start();
        }catch (Exception e){
            e.printStackTrace();
        }

    }
}

jetty服务实现(具体实现逻辑没写,具体实现逻辑类似tomcat实现)

public class JettyWebServer implements WebServer{
    @Override
    public void start() {
        System.out.println("jetty  start......");
    }
}

思考:jetty\tomcat都已实现,总不能用if/else这样决定用哪个服务扩展性太差,基于此,就联想到spring的Condition条件注解和参考springboot中的OnClassCondition这个注解。

具体实现如下:

public class KcOnClassCondition implements Condition {
    @Override
    public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {

        Map<String, Object> annotationAttributes = metadata.getAnnotationAttributes(KcConditionalOnClass.class.getName());
        String value = (String) annotationAttributes.get("value");

        try {
            context.getClassLoader().loadClass(value);
        } catch (ClassNotFoundException e) {
            return false;
        }
        return true;
    }
}
@Configuration
public class WebServerConfiguration{

    @Bean
    @KcConditionalOnClass("org.apache.catalina.startup.Tomcat")
    public TomcatWebServer tomcatWebServer( WebApplicationContext webApplicationContext){
        return new TomcatWebServer(webApplicationContext);
    }
    @Bean
    @KcConditionalOnClass("org.eclipse.jetty.server.Server")
    public JettyWebServer  jettyWebServer(){
        return new JettyWebServer();
    }
}

至此,springboot简化版已实现完成,首先启动看看
手写springboot_第2张图片

4.基于JDK的SPI实现扫描AutoConfiguration接口

  • AutoConfiguration接口
public interface AutoConfiguration {
}

  • 实现DeferredImportSelector接口实现类(具体为什么实现Import这个接口,请看以前的文章,主要这个接口具有延迟功能)
public class KcImportSelect implements DeferredImportSelector {
    @Override
    public String[] selectImports(AnnotationMetadata importingClassMetadata) {

        ServiceLoader<AutoConfiguration> load = ServiceLoader.load(AutoConfiguration.class);

        List<String> list = new ArrayList<>();

        for (AutoConfiguration autoConfiguration : load) {
            list.add(autoConfiguration.getClass().getName());
        }
        return list.toArray(new String[0]);
    }
}
  • 即KcSpringBootApplication注解导入该配置类
 @Retention(RetentionPolicy.RUNTIME)
 @Target(ElementType.TYPE)
 @Configuration
 @ComponentScan
 @Import(KcImportSelect.class)
public @interface KcSpringBootApplication {

}
  • WebServerConfiguration实现AutoConfiguration接口
@Configuration
public class WebServerConfiguration implements AutoConfiguration{

    @Bean
    @KcConditionalOnClass("org.apache.catalina.startup.Tomcat")
    public TomcatWebServer tomcatWebServer( WebApplicationContext webApplicationContext){
        return new TomcatWebServer(webApplicationContext);
    }
    @Bean
    @KcConditionalOnClass("org.eclipse.jetty.server.Server")
    public JettyWebServer  jettyWebServer(){
        return new JettyWebServer();
    }
}
  • 在springboot项目下创建META-INFO/service 接口全路径命名的文件,文件内容接口实现类全路径
    手写springboot_第3张图片

至此,springboot简易版已全部实现。

二、应用业务系统引入自己构建的springboot

1、user项目的pom依赖(引入自己构建的springboot项目)

    <dependencies>
        <dependency>
            <groupId>com.kc</groupId>
            <artifactId>springboot</artifactId>
            <version>1.0-SNAPSHOT</version>
        </dependency>
    </dependencies>

2、启动类

@ComponentScan(basePackages = {
        "kc.*"})
    @KcSpringBootApplication
public class UserApplication {


    public static void main(String[] args) {
        AnnotationConfigWebApplicationContext run = KcSpringApplication.run(UserApplication.class);
        System.out.println(run.getBeanFactory());

    }
}

3、实现具体业务类

@RestController
public class UserController {

    @Autowired
    private UserService userService;

    @GetMapping("test")
    public String test() {
        return userService.test();
    }
}
@Service
public class UserService {

    public String test() {
        return "hello  springboot";
    }
}

4、启动测试访问

在这里插入图片描述

三、项目地址

git地址

你可能感兴趣的:(spring,boot,后端,java,springboot)