• 手写模拟SpringBoot核心流程


    1、创建工程

    创建一个工程,包含springboot和user两个module
    在这里插入图片描述

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

    1.1、pom依赖

    SpringBoot是基于的Spring,所以我们要依赖Spring,然后我希望我们模拟出来的SpringBoot也支持Spring MVC的那一套功能,所以也要依赖Spring MVC,包括Tomcat等,所以在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>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29

    在User模块只添加我们创建的SpringBoot的依赖:

    <dependencies>
    	<dependency>
    		<groupId>org.example</groupId>
    		<artifactId>springboot</artifactId>
    		<version>1.0-SNAPSHOT</version>
    	</dependency>
    </dependencies>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    定义相关Controller和Service
    在这里插入图片描述

    @RestController
    public class UserController {
    
        @Autowired
        private UserService userService;
    
        @GetMapping("test")
        public String test(){
            return userService.test();
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    @Service
    public class UserService {
    
        public String test() {
            return "tacy";
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    1.2、核心注解和核心类

    在真正使用SpringBoot时,核心会用到SpringBoot一个类和注解:

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

    现在也来模拟实现他们。
    @TacySpringBootApplication注解:

    @Target(ElementType.TYPE)
    @Retention(RetentionPolicy.RUNTIME)
    @Configuration
    @ComponentScan
    public @interface TacySpringBootApplication {
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    用来实现启动逻辑的TacySpringApplication类:

    public class TacySpringApplication {
    
        public static void run(Class clazz) {
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5

    **在MyApplication中来使用: **

    @TacySpringBootApplication
    public class UserApplication {
    
        public static void main(String[] args) {
            TacySpringApplication.run(UserApplication.class);
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    目前只是壳子出来了,还不能跑起来

    1.3、run方法

    目标: 希望run方法一旦执行完,就能在浏览器中访问到UserController,那势必在run方法中要启动Tomcat,通过Tomcat就能接收到请求了。

    在SpringMVC中有一个Servlet非常核心,那就是DispatcherServlet,这个DispatcherServlet需要绑定一个Spring容器,因为DispatcherServlet接收到请求后,就会从所绑定的Spring容器中找到所匹配的Controller,并执行所匹配的方法。

    所以,在run方法中,我们要实现的逻辑如下:

    • 创建一个Spring容器
    • 创建Tomcat对象
    • 生成DispatcherServlet对象,并且和前面创建出来的Spring容器进行绑定
    • 将DispatcherServlet添加到Tomcat中
    • 启动Tomcat

    1.4、创建Spring容器

    public class TacySpringApplication {
    
        public static void run(Class clazz) {
            AnnotationConfigWebApplicationContext applicationContext = new AnnotationConfigWebApplicationContext();
            applicationContext.register(clazz);
            applicationContext.refresh();
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 创建的是一个AnnotationConfigWebApplicationContext容器,
    • 把run方法传入进来的class作为容器的配置类,比如在UserApplication的run方法中,我们就是把UserApplication.class传入到了run方法中,最终UserApplication就是所创建出来的Spring容器的配置类
    • 并且由于UserApplication类上有@TacySpringBootApplication注解, @TacySpringBootApplication注解的定义上又存在@ComponentScan注解,所以AnnotationConfigWebApplicationContext容器在执行refresh时,就会解析UserApplication这个配置类,从而发现定义了@ComponentScan注解,也就知道了要进行扫描,只不过扫描路径为空,而AnnotationConfigWebApplicationContext容器会处理这种情况,如果扫描路径会空,则会将UserApplication所在的包路径做为扫描路径,从而就会扫描到UserService和UserController。
    • Spring容器创建完之后,容器内部就拥有了UserService和UserController这两个Bean。

    1.5、启动Tomcat

    使用内嵌的tomcat, Embed-Tomcat

    public 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(contextPath, "dispatcher", new DispatcherServlet(applicationContext));
    	context.addServletMappingDecoded("/*", "dispatcher");
    	
    	try {
    		tomcat.start();
    	} catch (LifecycleException e) {
    		e.printStackTrace();
    	}
    	
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 配置了Tomcat绑定的端口为8081,
    • 向当前Tomcat中添加了DispatcherServlet,并设置了一个Mapping关系,最后启动
    • 在构造DispatcherServlet对象时,传入了一个ApplicationContext对象,也就是一个Spring容器,前文说的,DispatcherServlet对象和一个Spring容器进行绑定。
    • 在run方法中,调用startTomcat:
        public static void run(Class clazz) {
            AnnotationConfigWebApplicationContext applicationContext = new AnnotationConfigWebApplicationContext();
            applicationContext.register(clazz);
            applicationContext.refresh();
    
            startTomcat(applicationContext);
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    现在运行Application,就能正常的启动项目,并能接收请求
    在这里插入图片描述
    在浏览器上访问:http://localhost:8081/test
    在这里插入图片描述

    1.6、实现Tomcat和Jetty的切换

    上面已经实现了一个简单的SpringBoot,接下来继续扩充,实现以下需求:

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

    需求目标: 希望SpringBoot自动帮我实现,对于程序员用户而言,只要在Pom文件中添加相关依赖就可以了,想用Tomcat就加Tomcat依赖,想用Jetty就加Jetty依赖

    定义一个接口,WebServer, 在这个接口中定义一个start方法

    public interface WebServer {
    
        public void start();
    }
    
    • 1
    • 2
    • 3
    • 4

    再针对Tomcat和Jetty提供2个实现类

    public class TomcatWebServer implements WebServer{
    
        @Override
        public void start() {
            System.out.println("启动Tomcat");
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    public class TomcatWebServer implements WebServer{
    
        @Override
        public void start() {
            System.out.println("启动Jetty");
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    在TacySpringApplication中的run方法中,去获取对应的WebServer,然后启动对应的webServer,代码为:

    public static void run(Class clazz){
    	AnnotationConfigWebApplicationContext applicationContext = new AnnotationConfigWebApplicationContext();
    	applicationContext.register(clazz);
    	applicationContext.refresh();
    	
    	WebServer webServer = getWebServer(applicationContext);
    	webServer.start();
    	
    }
    
    public static WebServer getWebServer(ApplicationContext applicationContext){
    	return null;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    1.7、模拟实现条件注解

    实现一个条件注解@TacyConditionalOnClass

    @Target({ElementType.TYPE, ElementType.METHOD})
    @Retention(RetentionPolicy.RUNTIME)
    @Conditional(TacyOnClassCondition.class)
    public @interface TacyConditionalOnClass {
        String value() default "";
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    真正实现条件逻辑的是@Conditional(TacyOnClassCondition.class)中的TacyOnClassCondition

    public class TacyOnClassCondition implements Condition {
    
        @Override
        public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
            Map<String, Object> annotationAttributes = metadata.getAnnotationAttributes(TacyConditionalOnClass.class.getName());
            String className = (String) annotationAttributes.get("value");
    
            try {
                context.getClassLoader().loadClass(className);
                return true;
            } catch (ClassNotFoundException e) {
                return false;
            }
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 拿到@TacyConditionalOnClass中的value属性
    • 用类加载器进行加载
    • 加载到了所指定的类,就符合条件
  • 相关阅读:
    【第十题详细解答】研一Python基础课程第五周课后习题(含源代码)
    【多线程】synchronized的特性
    用于多步乘客需求预测的深度时空序列建模
    CSS实现鼠标移至图片上显示遮罩层及文字效果
    【分布式】MIT 6.824 Lab 2B实现细节分析
    Java集合篇之逐渐被遗忘的Stack,手写一个栈你会吗?
    六、线程池的编写与解析 —— TinyWebServer
    计算机视觉 立体视觉极简一览
    HTTPS(超文本传输安全协议)工作过程
    【一月一本技术书】-【Go语言设计与实现】- 9月
  • 原文地址:https://blog.csdn.net/beautybug1126/article/details/134395685