Tio Boot 整合 Spring Boot Starter

简介

传统上,我们通过引入 spring-boot-starter-web,将 Spring Boot 的 Controller 与 Tio Boot 的 Service 进行整合。然而,这种方法虽然可行,但却丢失了 Tio Boot 在处理请求时的异步和非阻塞特性。

为了解决这一问题,我们提出了一种新的整合方法:仅集成 spring-boot-starter,并使用 Tio Boot 的 Controller 来处理请求。这样既能保持 Tio Boot 的性能优势,又能利用 Spring Boot 的生态系统。

整合步骤

1. Maven 配置(pom.xml)

首先,配置 Maven 项目的 pom.xml 文件,关键点包括:

  • Java 和 Spring Boot 版本:确保使用兼容的版本。
  • 依赖项:除了 spring-boot-starter,还需要引入 tio-boot、Lombok、Druid 数据库连接池、MySQL 驱动等。
  • 构建插件:使用 spring-boot-maven-plugin 支持热部署和其他特性。
```xml
  <properties>
    <java.version>1.8</java.version>
    <maven.compiler.source>${java.version}</maven.compiler.source>
    <maven.compiler.target>${java.version}</maven.compiler.target>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
    <spring-boot.version>2.5.6</spring-boot.version>
    <hotswap-classloader.version>1.2.5</hotswap-classloader.version>
    <main.class>com.litongjava.spring.boot.tio.boot.demo01.Applicaton</main.class>
  </properties>

  <dependencies>
    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter</artifactId>
    </dependency>

    <dependency>
      <groupId>org.projectlombok</groupId>
      <artifactId>lombok</artifactId>
      <scope>provided</scope>
    </dependency>


    <dependency>
      <groupId>junit</groupId>
      <artifactId>junit</artifactId>
      <scope>test</scope>
    </dependency>

    <dependency>
      <groupId>com.litongjava</groupId>
      <artifactId>hotswap-classloader</artifactId>
      <version>${hotswap-classloader.version}</version>
    </dependency>

    <dependency>
      <groupId>com.litongjava</groupId>
      <artifactId>tio-boot</artifactId>
      <version>1.6.2</version>
    </dependency>

    <dependency>
      <groupId>com.litongjava</groupId>
      <artifactId>java-db</artifactId>
      <version>1.2.5</version>
    </dependency>
    <!-- 连接池 -->
    <dependency>
      <groupId>com.alibaba</groupId>
      <artifactId>druid</artifactId>
      <version>1.1.10</version>
    </dependency>
    <!-- 数据库驱动 -->
    <dependency>
      <groupId>mysql</groupId>
      <artifactId>mysql-connector-java</artifactId>
    </dependency>

  </dependencies>

  <build>
    <plugins>
      <!-- Spring Boot -->
      <plugin>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-maven-plugin</artifactId>
        <version>${spring-boot.version}</version>
        <configuration>
          <includeSystemScope>true</includeSystemScope>
          <!--使该插件支持热启动 -->
          <fork>true</fork>
          <mainClass>${main.class}</mainClass>
          <excludeGroupIds>org.projectlombok</excludeGroupIds>
        </configuration>
        <!-- 设置执行目标 -->
        <executions>
          <execution>
            <goals>
              <goal>repackage</goal>
            </goals>
          </execution>
        </executions>
      </plugin>
    </plugins>
  </build>

  <dependencyManagement>
    <dependencies>
      <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-dependencies</artifactId>
        <version>${spring-boot.version}</version>
        <type>pom</type>
        <scope>import</scope>
      </dependency>
    </dependencies>
  </dependencyManagement>

2. 启动类(Application)

定义应用的主启动类,使用 Tio Boot 启动应用程序。

package com.litongjava.spring.boot.tio.boot.demo01;

import com.litongjava.hotswap.wrapper.tio.boot.TioApplicationWrapper;
import com.litongjava.jfinal.aop.annotation.AComponentScan;

@AComponentScan
public class Application {
  public static void main(String[] args) {
    long start = System.currentTimeMillis();
    // 启动 Tio Boot
    TioApplicationWrapper.run(Application.class, args);
    long end = System.currentTimeMillis();
    System.out.println("启动耗时:" + (end - start) + "ms");
  }
}

3. 配置类(Configuration)

3.1 整合 Spring Boot

创建配置类,整合 Spring Boot 和 Tio Boot。

package com.litongjava.spring.boot.tio.boot.demo01.config;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.SpringApplication;
import org.springframework.context.ConfigurableApplicationContext;

import com.litongjava.jfinal.aop.Aop;
import com.litongjava.jfinal.aop.AopManager;
import com.litongjava.jfinal.aop.annotation.AConfiguration;
import com.litongjava.jfinal.aop.annotation.AInitialization;
import com.litongjava.jfinal.spring.SpringBeanContextUtils;
import com.litongjava.spring.boot.tio.boot.demo01.Application;
import com.litongjava.tio.boot.server.TioBootServer;
import com.litongjava.tio.utils.environment.EnvUtils;

@AConfiguration
public class SpringConfig {

  @AInitialization
  public void config() {
    // 启动 Spring Boot
    ConfigurableApplicationContext context = SpringApplication.run(Application.class, EnvUtils.getArgs());

    // 开启与 Spring 的整合
    AopManager.me().getAopFactory().setEnableWithSpring(true);
    SpringBeanContextUtils.setContext(context);

    // 支持 @Autowired 注解
    Aop.addFetchBeanAnnotation(Autowired.class);

    // 注册关闭钩子
    TioBootServer.me().addDestroyMethod(context::close);
  }
}

3.2 整合 ActiveRecordPlugin

配置数据库连接池和 ActiveRecordPlugin。

package com.litongjava.spring.boot.tio.boot.demo01.config;

import com.litongjava.db.activerecord.ActiveRecordPlugin;
import com.litongjava.db.activerecord.OrderedFieldContainerFactory;
import com.litongjava.db.druid.DruidPlugin;
import com.litongjava.jfinal.aop.annotation.AConfiguration;
import com.litongjava.tio.boot.server.TioBootServer;

@AConfiguration
public class ActiveRecordPluginConfig {

  public void activeRecordPlugin() {
    String jdbcUrl = "jdbc:mysql://localhost:3306/your_database";
    String jdbcUser = "your_username";
    String jdbcPswd = "your_password";

    // 配置 Druid 数据库连接池
    DruidPlugin druidPlugin = new DruidPlugin(jdbcUrl, jdbcUser, jdbcPswd);
    druidPlugin.start();

    // 配置 ActiveRecordPlugin
    ActiveRecordPlugin arp = new ActiveRecordPlugin(druidPlugin);
    arp.setContainerFactory(new OrderedFieldContainerFactory());
    arp.start();

    // 注册关闭钩子
    TioBootServer.me().addDestroyMethod(() -> {
      druidPlugin.stop();
      arp.stop();
    });
  }
}

4. 编写 Service 和 Controller

4.1 定义 Service 接口和实现

Service 接口:

package com.litongjava.spring.boot.tio.boot.demo01.service;

import java.util.List;
import com.litongjava.jfinal.plugin.activerecord.Record;

public interface UserService {
  List<Record> listAll();
}

Service 实现类:

package com.litongjava.spring.boot.tio.boot.demo01.service;

import java.util.List;
import com.litongjava.jfinal.aop.annotation.AService;
import com.litongjava.jfinal.plugin.activerecord.Db;
import com.litongjava.jfinal.plugin.activerecord.Record;

@AService
public class UserServiceImpl implements UserService {

  @Override
  public List<Record> listAll() {
    return Db.findAll("user");
  }
}

4.2 定义 Controller

使用 Tio Boot 的 @RequestPath 注解定义路由。

package com.litongjava.spring.boot.tio.boot.demo01.controller;

import java.util.List;
import com.litongjava.jfinal.aop.Aop;
import com.litongjava.jfinal.plugin.activerecord.Record;
import com.litongjava.spring.boot.tio.boot.demo01.service.UserService;
import com.litongjava.tio.http.server.annotation.RequestPath;

@RequestPath("/user")
public class UserController {

  public List<Record> listAll() {
    return Aop.get(UserService.class).listAll();
  }
}

5. 启动和测试

启动应用后,访问 http://localhost/user/listAll 以测试接口。

6. 单元测试

6.1 测试 Service 层

package com.litongjava.spring.boot.tio.boot.demo01.services;

import java.util.List;
import org.junit.Before;
import org.junit.Test;
import com.litongjava.jfinal.aop.Aop;
import com.litongjava.spring.boot.tio.boot.demo01.Application;
import com.litongjava.spring.boot.tio.boot.demo01.service.UserService;
import com.litongjava.tio.boot.testing.TioBootTest;
import com.litongjava.jfinal.plugin.activerecord.Record;

public class UserServiceTest {

  @Before
  public void setUp() {
    TioBootTest.before(Application.class);
  }

  @Test
  public void testListAll() {
    UserService userService = Aop.get(UserService.class);
    List<Record> users = userService.listAll();
    System.out.println(users);
  }
}

6.2 测试 Spring 上下文

package com.litongjava.spring.boot.tio.boot.demo01.services;

import org.junit.Before;
import org.junit.Test;
import com.litongjava.jfinal.spring.SpringBeanContextUtils;
import com.litongjava.spring.boot.tio.boot.demo01.Application;
import com.litongjava.tio.boot.TioApplication;
import com.litongjava.tio.utils.json.JsonUtils;

public class SpringContextTest {

  @Before
  public void setUp() {
    TioApplication.run(Application.class, new String[] {});
  }

  @Test
  public void testBeanDefinitions() {
    String[] beanNames = SpringBeanContextUtils.getContext().getBeanDefinitionNames();
    System.out.println(JsonUtils.toJson(beanNames));
  }
}
[
  "org.springframework.context.annotation.internalConfigurationAnnotationProcessor",
  "org.springframework.context.annotation.internalAutowiredAnnotationProcessor",
  "org.springframework.context.annotation.internalCommonAnnotationProcessor",
  "org.springframework.context.event.internalEventListenerProcessor",
  "org.springframework.context.event.internalEventListenerFactory",
  "applicaton",
  "org.springframework.boot.autoconfigure.internalCachingMetadataReaderFactory",
  "org.springframework.boot.autoconfigure.AutoConfigurationPackages",
  "org.springframework.boot.autoconfigure.context.PropertyPlaceholderAutoConfiguration",
  "propertySourcesPlaceholderConfigurer",
  "org.springframework.boot.autoconfigure.aop.AopAutoConfiguration$ClassProxyingConfiguration",
  "forceAutoProxyCreatorToUseClassProxying",
  "org.springframework.boot.autoconfigure.aop.AopAutoConfiguration",
  "org.springframework.boot.autoconfigure.availability.ApplicationAvailabilityAutoConfiguration",
  "applicationAvailability",
  "org.springframework.boot.autoconfigure.context.ConfigurationPropertiesAutoConfiguration",
  "org.springframework.boot.context.properties.ConfigurationPropertiesBindingPostProcessor",
  "org.springframework.boot.context.internalConfigurationPropertiesBinderFactory",
  "org.springframework.boot.context.internalConfigurationPropertiesBinder",
  "org.springframework.boot.context.properties.BoundConfigurationProperties",
  "org.springframework.boot.context.properties.EnableConfigurationPropertiesRegistrar.methodValidationExcludeFilter",
  "org.springframework.boot.autoconfigure.context.LifecycleAutoConfiguration",
  "lifecycleProcessor",
  "spring.lifecycle-org.springframework.boot.autoconfigure.context.LifecycleProperties",
  "org.springframework.boot.autoconfigure.info.ProjectInfoAutoConfiguration",
  "spring.info-org.springframework.boot.autoconfigure.info.ProjectInfoProperties",
  "org.springframework.boot.autoconfigure.sql.init.SqlInitializationAutoConfiguration",
  "spring.sql.init-org.springframework.boot.autoconfigure.sql.init.SqlInitializationProperties",
  "org.springframework.boot.sql.init.dependency.DatabaseInitializationDependencyConfigurer$DependsOnDatabaseInitializationPostProcessor",
  "org.springframework.boot.autoconfigure.task.TaskExecutionAutoConfiguration",
  "taskExecutorBuilder",
  "applicationTaskExecutor",
  "spring.task.execution-org.springframework.boot.autoconfigure.task.TaskExecutionProperties",
  "org.springframework.boot.autoconfigure.task.TaskSchedulingAutoConfiguration",
  "scheduledBeanLazyInitializationExcludeFilter",
  "taskSchedulerBuilder",
  "spring.task.scheduling-org.springframework.boot.autoconfigure.task.TaskSchedulingProperties",
  "org.springframework.aop.config.internalAutoProxyCreator"
]

通过以上步骤,我们成功实现了 Tio Boot 与 Spring Boot 的整合,既保留了 Tio Boot 的异步非阻塞特性,又充分利用了 Spring Boot 的生态优势。