基于spring-context实现SpringBoot

内容大纲

1、手写模拟SpringBoot启动过程
2、手写模拟SpringBoot条件注解功能
3、手写模拟SpringBoot自动配置功能
4、SpringBoot整合Tomcat底层源码分析

创建springboot模仿工程

首先,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>

创建业务工程

依赖springboot模仿工程

<dependency>
  <groupId>org.example</groupId>
  <artifactId>springboot</artifactId>
  <version>1.0-SNAPSHOT</version>
  <exclusions>
    <exclusion>
      <groupId>org.apache.tomcat.embed</groupId>
      <artifactId>tomcat-embed-core</artifactId>
    </exclusion>
  </exclusions>
</dependency>

模仿springboot用法

@RestController
public class UserController {

    @Autowired
    private UserService userService;

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

模仿SpringBoot并希望MyApplication中的main方法,就直接启动了项 目,并能在浏览器中正常的访问到UserController中的某个方法。

模仿启动类

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

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

所以我们也来模拟实现他们:

启动注解@StartSpringBootApplication

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Configuration // 配置
@ComponentScan // 扫描
@Import(ZhouyuImportSeclet.class)
public @interface StartSpringBootApplication {
}

StartSpringBootApplication.class来实现启动逻辑

public class StartSpringBootApplication {
    public static void run(Class clazz) {
    }
}

启动类中使用@StartSpringBootApplication和StartSpringBootApplication.class

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

run方法中需要实现什么具体的逻辑呢?
分析一次请求过程:

  1. 首先,我们希望run方法一旦执行完,我们就能在浏览器中访问到UserController,那势必在run方法 中要启动Tomcat,通过Tomcat就能接收到请求了
  2. 在SpringMVC中有一个Servlet非常核心,那就是 DispatcherServlet,这个DispatcherServlet需要绑定一个Spring容器
  3. DispatcherServlet接收 到请求后,就会从所绑定的Spring容器中找到所匹配的Controller,并执行所匹配的方法

在run方法逻辑:

  1. 创建一个AnnotationConfigWebApplicationContext容器(Spring容器 )
  2. 解析主启动类(@StartSpringBootApplication),然后进行扫描
  3. 通过getWebServer方法从Spring容器中获取WebServer类型的Bean
  4. 调用WebServer对象的start方法,启动web容器
  5. 创建Tomcat对象
  6. 生成DispatcherServlet对象,并且和前面创建出来的Spring容器进行绑定
  7. 将DispatcherServlet添加到Tomcat中
  8. 启动Tomcat
public class StartSpringBootApplication {

    public static void run(Class clazz) {
        // Spring容器
        AnnotationConfigWebApplicationContext applicationContext = new AnnotationConfigWebApplicationContext();
        applicationContext.register(clazz);
        applicationContext.refresh();
        // 获取web容器
        WebServer webServer = getWebServer(applicationContext);
        webServer.start(); // 启动web容器
    }

    public static WebServer getWebServer(WebApplicationContext applicationContext) {
        // 从spring容器中获取WebServer
        Map<String, WebServer> beansOfType = applicationContext.getBeansOfType(WebServer.class);

        if (beansOfType.isEmpty()) {
            throw new NullPointerException();
        }

        if (beansOfType.size() > 1) {
            throw new IllegalStateException();
        }
        return beansOfType.values().stream().findFirst().get();
    }
}

Tomcat容器

public class TomcatWebServer implements WebServer {
    private final int port = 8081;
    private final String host = "localhost";
    private final String contextPath = "";

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

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

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

        Engine engine = new StandardEngine();
        engine.setDefaultHost(host);

        Host standardHost = new StandardHost();
        standardHost.setName(host);

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

        standardHost.addChild(context);
        engine.addChild(standardHost);

        service.setContainer(engine);
        service.addConnector(connector);
        // DispatcherServlet对象和一个Spring容器进行绑定
        DispatcherServlet dispatcherServlet = new DispatcherServlet(applicationContext);
        tomcat.addServlet(contextPath, "dispatcher", dispatcherServlet);
        context.addServletMappingDecoded("/*", "dispatcher");
        try {
            tomcat.start();
        } catch (LifecycleException e) {
            e.printStackTrace();
        }
    }
}

实现Tomcat和Jetty的切换

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

这个逻辑希望SpringBoot自动帮我实现,对于程序员用户而言,只要在Pom文件中添加相关依赖就可 以了,想用Tomcat就加Tomcat依赖,想用Jetty就加Jetty依赖
那SpringBoot该如何实现呢?
不管是Tomcat还是Jetty,它们都是应用服务器,或者是Servlet容器,所以我们可以定义
接口来表示它们,这个接口叫做WebServer(SpringBoot源码)

web服务接口:

public interface WebServer {
    void start(WebApplicationContext applicationContext);
}

Tomcat

public class TomcatWebServer implements WebServer {
    @Override
    public void start(WebApplicationContext applicationContext) {
    }
}

Jetty

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

WebServer注入Spring容器

根据pom依赖注入不同的WebServer

模拟实现条件注解

@Target({ ElementType.TYPE, ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
@Conditional(ClassCondition.class) // 条件注解
public @interface ConditionalOnClass {
    String value();
}
public class ClassCondition implements Condition {
    @Override
    public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
        Map<String, Object> annotationAttributes = metadata.getAnnotationAttributes(ConditionalOnClass.class.getName());
        String className = (String) annotationAttributes.get("value");
        try {
            context.getClassLoader().loadClass(className);
            return true;
        } catch (ClassNotFoundException e) {
            return false;
        }

    }
}

具体逻辑为,拿到@ConditionalOnClass中的value属性,然后用类加载器进行加载,如果加 载到了所指定的这个类,那就表示符合条件,如果加载不到,则表示不符合条件

模拟实现自动配置类

package com.framework.springboot
@Configuration
public class WebServerAutoConfiguration implements AutoConfiguration {

    // 只有存在"org.apache.catalina.startup.Tomcat"类,那么才有TomcatWebServer这个Bean
    @Bean
    @ConditionalOnClass("org.apache.catalina.startup.Tomcat")
    public TomcatWebServer tomcatWebServer(){
        return new TomcatWebServer();
    }

    // 只有存在"org.eclipse.jetty.server.Server"类,那么才有TomcatWebServer这个Bean
    @Bean
    @ConditionalOnClass("org.eclipse.jetty.server.Server")
    public JettyWebServer jettyWebServer(){
        return new JettyWebServer();
    }
}

获取WebServer,StartSpringApplication.getWebServer

public static WebServer getWebServer(WebApplicationContext applicationContext) {
    Map<String, WebServer> beansOfType = applicationContext.getBeansOfType(WebServer.class);
    if (beansOfType.isEmpty()) { throw new NullPointerException(); }
    if (beansOfType.size() > 1) {  throw new IllegalStateException();  }
    return beansOfType.values().stream().findFirst().get();
}

WebServiceAutoConfiguration解析

Spring要能解析到WebServiceAutoConfiguration这 个自动配置类?在SpringBoot在run方法中,能找到WebServiceAutoConfiguration这个配置类并添加到Spring容器中

MyApplication启动类是Spring的一个配置类,但是MyApplication是我们传递给SpringBoot,从而添加到 Spring容器中去的,而WebServiceAutoConfiguration就需要SpringBoot去自动发现,而不需要程 序员做任何配置才能把它添加到Spring容器中去,那SpringBoot中是如何实现的呢?SPI
注意:WebServiceAutoConfiguration包路径为"com.framework.springboot",只能扫描到主配置类MyApplication下的包

发现自动配置类

SpringBoot中自己实现了一套SPI机制(spring.factories文件),这儿不搞复杂了,直接用JDK自带的SPI机制

要在springboot项目中的resources目录下添加如下目录(META-INF/services)和文件:
image.png
文件内容:com.framework.springboot.WebServerAutoConfiguration

package com.framework.springboot;
public interface AutoConfiguration {}

相当于通过com.framework.springboot.AutoConfiguration文件配置了 springboot中所提供的配置类

加载自动配置类

利用spring中的@Import技术来导入这些配置类,我们在 @StartSpringBootApplication的定义上增加如下代码:

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Configuration
@ComponentScan
@Import(JdkSpiImportSeclet.class) // 导入配置类
public @interface ZhouyuSpringBootApplication {
}

通过jdk的spi机制加载AutoConfiguration配置类到spring容器中

public class JdkSpiImportSeclet implements DeferredImportSelector {
    @Override
    public String[] selectImports(AnnotationMetadata importingClassMetadata) {
        // 自动配置
        ServiceLoader<AutoConfiguration> loader = ServiceLoader.load(AutoConfiguration.class);
        List<String> list = new ArrayList<>();
        for (AutoConfiguration configuration : loader) {
            list.add(configuration.getClass().getName());
        }
        return list.toArray(new String[0]);
    }
}

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
A:实现定时器任务管理可以使用Spring Boot提供的定时任务(@Scheduled)和Mybatis-Plus提供的定时任务表(定时器任务表在Mybatis-Plus 3.1.1版本及以上已支持,使用Mybatis-Plus官方提供的SQL脚本创建定时器任务表)。 1. 首先,在项目中引入Mybatis-Plus和定时任务依赖。 ```xml <!-- Mybatis-Plus --> <dependency> <groupId>com.baomidou</groupId> <artifactId>mybatis-plus-boot-starter</artifactId> <version>{mybatis-plus.version}</version> </dependency> <!-- 定时任务 --> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-context-support</artifactId> <version>{spring.version}</version> </dependency> ``` 2. 创建定时器任务表。 可以直接在数据库中执行Mybatis-Plus官方提供的SQL脚本,创建定时器任务表(或者在Mybatis-Plus 3.1.1版本之后,通过注解方式自动创建)。 ```sql CREATE TABLE `tb_task` ( `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '主键ID', `task_group` varchar(50) NOT NULL COMMENT '任务组', `task_name` varchar(50) NOT NULL COMMENT '任务名称', `task_cron` varchar(50) NOT NULL COMMENT '任务执行cron表达式', `task_class` varchar(500) NOT NULL COMMENT '任务具体执行类', `task_method` varchar(50) NOT NULL COMMENT '任务具体执行方法', `task_data` varchar(500) DEFAULT NULL COMMENT '任务执行参数', `task_status` tinyint(1) NOT NULL DEFAULT '0' COMMENT '任务状态(0:停止,1:运行)', `create_time` datetime NOT NULL COMMENT '创建时间', `update_time` datetime NOT NULL COMMENT '更新时间', `remark` varchar(500) DEFAULT NULL COMMENT '备注', PRIMARY KEY (`id`) ) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb4 COMMENT='定时任务表'; ``` 3. 创建定时器任务实体类。 ```java @Data @TableName("tb_task") public class Task extends BaseEntity { /** * 任务组(用于区分不同业务) */ private String taskGroup; /** * 任务名称 */ private String taskName; /** * 任务执行cron表达式 */ private String taskCron; /** * 任务具体执行类 */ private String taskClass; /** * 任务具体执行方法 */ private String taskMethod; /** * 任务执行参数 */ private String taskData; /** * 任务状态(0:停止,1:运行) */ private Integer taskStatus; /** * 备注 */ private String remark; } ``` 4. 创建定时器任务管理接口和实现类。 ```java public interface TaskService extends IService<Task> { /** * 执行定时任务 */ void runTask(Task task); /** * 根据任务组和任务名称获取任务 */ Task getTaskByGroupAndName(String taskGroup, String taskName); /** * 更新任务状态 */ boolean updateTaskStatus(Long id, Integer taskStatus); } @Service public class TaskServiceImpl extends ServiceImpl<TaskMapper, Task> implements TaskService { @Autowired private ApplicationContext applicationContext; @Override public void runTask(Task task) { try { // 获取任务类 Object taskObj = applicationContext.getBean(task.getTaskClass()); if (null == taskObj) { throw new RuntimeException("No such task class found!"); } // 获取任务方法 Method method = taskObj.getClass().getMethod(task.getTaskMethod(), String.class); // 执行任务方法 method.invoke(taskObj, task.getTaskData()); } catch (Exception e) { throw new RuntimeException(e.getMessage(), e); } } @Override public Task getTaskByGroupAndName(String taskGroup, String taskName) { QueryWrapper<Task> wrapper = new QueryWrapper<>(); wrapper.eq("task_group", taskGroup) .eq("task_name", taskName); return baseMapper.selectOne(wrapper); } @Override public boolean updateTaskStatus(Long id, Integer taskStatus) { Task task = new Task(); task.setId(id); task.setTaskStatus(taskStatus); return updateById(task); } } ``` 5. 配置定时任务。 ```java @Configuration @EnableScheduling public class TaskConfig { @Autowired private TaskService taskService; @Autowired private ThreadPoolTaskScheduler taskScheduler; @Scheduled(fixedRate = 5000) // 每隔5000ms执行一次 public void runTask() { // 获取需要执行的任务列表 List<Task> taskList = taskService.list(new QueryWrapper<Task>().eq("task_status", 1)); if (CollectionUtils.isEmpty(taskList)) { return; } // 执行任务 for (Task task : taskList) { taskScheduler.schedule(new TaskRunnable(taskService, task), new CronTrigger(task.getTaskCron())); } } /** * 定时器任务运行线程 */ public static class TaskRunnable implements Runnable { private TaskService taskService; private Task task; public TaskRunnable(TaskService taskService, Task task) { this.taskService = taskService; this.task = task; } @Override public void run() { // 执行任务 taskService.runTask(task); // 更新任务状态 taskService.updateTaskStatus(task.getId(), 1); } } } ``` 6. 启动项目,添加定时器任务,等待定时任务执行。 以上就是基于Mybatis-Plus实现定时器任务管理的过程,希望对你有所帮助。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值