如何手写一个SpringBoot框架

你好,我是柳岸花开。

在这篇文章中,我们将手写模拟SpringBoot的核心流程,让大家能够以一种简单的方式了解SpringBoot的大概工作原理。

项目结构

我们创建一个工程,包含两个模块:

  1. springboot模块,表示SpringBoot框架的源码实现。
  2. user包,表示用户业务系统,用来写业务代码来测试我们所模拟出来的SpringBoot。

首先,在springboot模块中添加以下依赖:

<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>

user模块下,我们进行正常的开发,比如先添加SpringBoot依赖,然后定义相关的ControllerService

<dependencies>
    <dependency>
        <groupId>org.example</groupId>
        <artifactId>springboot</artifactId>
        <version>1.0-SNAPSHOT</version>
    </dependency>
</dependencies>

定义UserController和UserService

user包下定义UserControllerUserService,希望能通过运行MyApplication中的main方法,直接启动项目,并能在浏览器中正常访问到UserController中的某个方法。

@RestController
public class UserController {

    @Autowired
    private UserService userService;

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

核心注解和核心类:

  • @SpringBootApplication:这个注解加在应用启动类上,即 main方法所在的类。
  • SpringApplication:这个类中有个 run()方法,用来启动SpringBoot应用。
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Configuration
@ComponentScan
public @interface BobSpringBootApplication {
}

public class BobSpringApplication {

    public static void run(Class clazz){
        AnnotationConfigWebApplicationContext applicationContext = new AnnotationConfigWebApplicationContext();
        applicationContext.register(clazz);
        applicationContext.refresh();

        startTomcat(applicationContext);
    }

    private static void startTomcat(WebApplicationContext applicationContext) {
        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(context, "dispatcher"new DispatcherServlet(applicationContext));
        context.addServletMappingDecoded("/""dispatcher");

        try {
            tomcat.start();
        } catch (LifecycleException e) {
            e.printStackTrace();
        }

        tomcat.getServer().await();
    }
}

@BobSpringBootApplication
public class MyApplication {

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

实现Tomcat和Jetty的切换

如果项目中有Tomcat的依赖,那就启动Tomcat;如果有Jetty的依赖就启动Jetty;如果两者都没有则报错;如果两者都有也报错。

public interface WebServer {
    void start();
}

public class TomcatWebServer implements WebServer {
    @Override
    public void start() {
        System.out.println("启动Tomcat");
    }
}

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

模拟实现条件注解:

@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Conditional(BobOnClassCondition.class)
public @interface BobConditionalOnClass 
{
    String value() default "";
}

public class BobOnClassCondition implements Condition {
    @Override
    public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
        Map<String, Object> annotationAttributes = metadata.getAnnotationAttributes(BobConditionalOnClass.class.getName());
        String className = (String) annotationAttributes.get("value");

        try {
            context.getClassLoader().loadClass(className);
            return true;
        } catch (ClassNotFoundException e) {
            return false;
        }
    }
}

模拟实现自动配置类:

@Configuration
public class WebServiceAutoConfiguration {
    @Bean
    @BobConditionalOnClass("org.apache.catalina.startup.Tomcat")
    public TomcatWebServer tomcatWebServer() {
        return new TomcatWebServer();
    }

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

BobSpringApplication中添加获取WebServer的方法:

public static WebServer getWebServer(ApplicationContext applicationContext) {
    Map<String, WebServer> webServers = applicationContext.getBeansOfType(WebServer.class);

    if (webServers.isEmpty()) {
        throw new NullPointerException();
    }
    if (webServers.size() > 1) {
        throw new IllegalStateException();
    }

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

通过以上步骤,我们实现了一个简单的SpringBoot,并且可以根据依赖切换Tomcat和Jetty。

👇关注我,下期了解👇 ​ SpringBoot自动配置 ​ ​ alt ​ ​ 回复 222,获取Java面试题合集 ​ 关于我 ​ 一枚爱折腾的Java程序猿,专注Spring干货。把路上的问题记录下来,帮助那些和我一样的人。 ​ 好奇心强,喜欢并深入研究古天文。 ​ 崇尚 个人系统创建,做一些时间越长越有价值的事情。思考 把时间留下来 又 每刻都是新的。

本文由 mdnice 多平台发布

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值