说明
公司里面原来的项目都是 Spring Boot 和 Spring Cloud 框架的,自己手动迁移完一个项目后,发现迁移的过程有些还是能代码化的东西,于是整理了 SpringConverter 这个工具。
这个工具不是说你转换完就能无痛的启动,你还是需要手动处理一些错误。虽然工具内置了一部分的 Spring 与 Solon 的对照关系,但你仍然可能需要修改这个工具的代码,配置遗漏的对照关系,以便能将自己的项目进行迁移。
思路
在手动替换的过程中,会首先创建新项目,然后把旧代码复制到新项目中,之后对相关的包和类进行替换,然后逐步修复其中的错误。
这个工具做的事情就是从实现了复制代码和替换包名、类名的过程。这个过程当中,我发现通常是一个包对另一个包,一个类对一个类,当然也会有多对一的情况,和无法对应的情况,于是注意的工作就是配置类名、包名的映射关系,然后就可以通过代码实现自动替换,从而实现减少工作量的目的。
技巧
- 自己手动转换 pom 或者 gradle 文件,调整好依赖,不要交给转换工具。
- 替换原项目地址,原项目包名,新项目地址,新项目包名。
- 转化工具放置在另一个项目(或者模块中),这样方便多次转换新项目(边调整映射配置边替换)。必要时也可以方便的整个删除新项目。
- 如果类名从短变长(通常是后缀相同),需要先替换类名,再通过旧包名+新类名的方式,替换新包名。
- 如果无法对应的包,可以直接替换为空串或者回车(\n)
- 如果是类上无法使用注解,可以增加回车(\n),替换从空串或者回车(\n)。
- 注意字符串匹配是通过正则匹配的,替换的原字符串中包含中括弧(()),大括号({}),星号(*)等时需要进行转移。
- 根据不同的类库进行替换,方便维护。必要时可以自己修改成配置文件从配置文件中读取对照关系。
项目地址
https://gitee.com/CrazyAirhead/porpoise-demo/tree/ray-admin/
ray-tools/ray-spring-converter
代码
代码本身无特别之处,就是根据配置的信息不断地替换源代码。
package com.goldsyear.ray.converter;import static java.io.File.separator;import com.jfinal.kit.Kv;
import java.io.File;
import java.nio.charset.StandardCharsets;
import java.util.*;
import java.util.regex.Matcher;
import java.util.stream.Collectors;
import lombok.extern.slf4j.Slf4j;
import org.dromara.hutool.core.collection.set.SetUtil;
import org.dromara.hutool.core.io.file.FileTypeUtil;
import org.dromara.hutool.core.io.file.FileUtil;/*** SpringBoot 项目迁移 Solon 项目工具,并不能迁移完就直接启动,只是减少迁移工作量** @author Airhead*/
@Slf4j
public class RayConverterApp {/** 白名单文件,不进行重写,避免出问题 */private static final Set<String> WHITE_FILE_TYPES =SetUtil.of("gif", "jpg", "svg", "png", "eot", "woff2", "ttf", "woff");public static void main(String[] args) {long start = System.currentTimeMillis();// 修改路径String oldProjectDir = "旧项目路";String oldPackageName = "旧包名";String newProjectDir = "新项目路";String newPackageName = "新包名";log.info("原项目目录:{}, 原基础包名:{}", oldProjectDir, oldPackageName);log.info("新项目目录:{}, 新基础包名:{}", newProjectDir, newPackageName);// ========== 配置,需要你手动修改 ==========List<Kv> mappingList = new ArrayList<>();mappingList.add(Kv.by("old", oldPackageName).set("new", newPackageName));// Spring 包调整mappingSpringBoot(mappingList);// Hutool 包调整mappingHutool(mappingList);// FastJsonmappingFastJson(mappingList);// Mybatis 包调整mappingMybatis(mappingList);// TODO: 根据自己依赖的项目模块进行替换// 业务包调整mappingBusiness(mappingList);// 获得需要复制的文件log.info("开始获得需要重写的文件,预计需要 10-20 秒");Collection<File> files = listFiles(oldProjectDir);log.info("需要重写的文件数量:{},预计需要 15-30 秒", files.size());// 写入文件files.forEach(file -> {// 如果是白名单的文件类型,不进行重写,直接拷贝String fileType = FileTypeUtil.getType(file);if (WHITE_FILE_TYPES.contains(fileType)) {copyFile(file, oldProjectDir, oldPackageName, newProjectDir, newPackageName);return;}// 如果非白名单的文件类型,重写内容,在生成文件String content = replaceFileContent(file, mappingList);writeFile(file, content, oldProjectDir, oldPackageName, newProjectDir, newPackageName);});log.info("重写完成,共耗时:{} 秒", (System.currentTimeMillis() - start) / 1000);}private static void mappingBusiness(List<Kv> mappingList) {// TODO}private static void mappingFastJson(List<Kv> mappingList) {// TODO}private static void mappingMybatis(List<Kv> mappingList) {// PagemappingList.add(Kv.by("old", "com.baomidou.mybatisplus.extension.plugins.pagination.Page").set("new", "com.baomidou.mybatisplus.solon.plugins.pagination.Page"));// ModelmappingList.add(Kv.by("old", "com.baomidou.mybatisplus.extension.activerecord.Model").set("new", "com.baomidou.mybatisplus.solon.activerecord.Model"));// TenantLineHandlermappingList.add(Kv.by("old", "com.baomidou.mybatisplus.extension.plugins.handler.TenantLineHandler").set("new", "com.baomidou.mybatisplus.solon.plugins.handler.TenantLineHandler"));// MybatisPlusInterceptormappingList.add(Kv.by("old", "com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor").set("new", "com.baomidou.mybatisplus.solon.plugins.MybatisPlusInterceptor"));// PaginationInnerInterceptormappingList.add(Kv.by("old", "com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor").set("new", "com.baomidou.mybatisplus.solon.plugins.inner.PaginationInnerInterceptor"));// TenantLineInnerInterceptormappingList.add(Kv.by("old", "com.baomidou.mybatisplus.extension.plugins.inner.TenantLineInnerInterceptor").set("new", "com.baomidou.mybatisplus.solon.plugins.inner.TenantLineInnerInterceptor"));// FastjsonTypeHandlermappingList.add(Kv.by("old", "com.baomidou.mybatisplus.extension.handlers.FastjsonTypeHandler").set("new", "com.baomidou.mybatisplus.solon.handlers.FastjsonTypeHandler"));}private static void mappingHutool(List<Kv> mappingList) {// StrUtil;mappingList.add(Kv.by("old", "cn.hutool.core.util.StrUtil").set("new", "org.dromara.hutool.core.text.StrUtil"));// Convert;mappingList.add(Kv.by("old", "cn.hutool.core.convert.Convert").set("new", "org.dromara.hutool.core.convert.ConvertUtil"));mappingList.add(Kv.by("old", "Convert\\.").set("new", "ConvertUtil."));// IdUtil;mappingList.add(Kv.by("old", "cn.hutool.core.util.IdUtil").set("new", "org.dromara.hutool.core.data.id.IdUtil"));// DateUtil;mappingList.add(Kv.by("old", "cn.hutool.core.date.DateUtil").set("new", "org.dromara.hutool.core.date.DateUtil"));// ReUtilmappingList.add(Kv.by("old", "cn.hutool.core.util.ReUtil").set("new", "org.dromara.hutool.core.regex.ReUtil"));// Spring CollectionUtils;mappingList.add(Kv.by("old", "org.springframework.util.CollectionUtils").set("new", "org.dromara.hutool.core.collection.CollUtil"));mappingList.add(Kv.by("old", "CollectionUtils").set("new", "CollUtil"));// CollectionUtil;mappingList.add(Kv.by("old", "cn.hutool.core.collection.CollectionUtil").set("new", "org.dromara.hutool.core.collection.CollUtil"));mappingList.add(Kv.by("old", "CollectionUtil").set("new", "CollUtil"));// LocalDateTimeUtilmappingList.add(Kv.by("old", "cn.hutool.core.date.LocalDateTimeUtil").set("new", "org.dromara.hutool.core.date.TimeUtil"));mappingList.add(Kv.by("old", "LocalDateTimeUtil").set("new", "TimeUtil"));// FileUtilmappingList.add(Kv.by("old", "cn.hutool.core.io.FileUtil").set("new", "org.dromara.hutool.core.io.file.FileUtil"));// IoUtilmappingList.add(Kv.by("old", "cn.hutool.core.io.IoUtil").set("new", "org.dromara.hutool.core.io.IoUtil"));}private static void mappingSpringBoot(List<Kv> mappingList) {// annotationmappingList.add(Kv.by("old", "org.springframework.web.bind.annotation\\.\\*").set("new", "org.noear.solon.annotation.*"));// ComponentmappingList.add(Kv.by("old", "org.springframework.stereotype.Component").set("new", "org.noear.solon.annotation.Component"));// ServicemappingList.add(Kv.by("old", "org.springframework.stereotype.Service").set("new", "org.noear.solon.annotation.Component"));mappingList.add(Kv.by("old", "@Service").set("new", "@Component"));// AutowiredmappingList.add(Kv.by("old", "org.springframework.beans.factory.annotation.Autowired").set("new", "org.noear.solon.annotation.Inject"));mappingList.add(Kv.by("old", "@Autowired").set("new", "@Inject"));// ControllermappingList.add(Kv.by("old", "org.springframework.web.bind.annotation.RestController").set("new", "org.noear.solon.annotation.Controller"));mappingList.add(Kv.by("old", "@RestController").set("new", "@Controller"));// RequestMappingmappingList.add(Kv.by("old", "org.springframework.web.bind.annotation.RequestMapping").set("new", "org.noear.solon.annotation.Mapping"));mappingList.add(Kv.by("old", "@RequestMapping").set("new", "@Mapping"));// MediaType;mappingList.add(Kv.by("old", "import org.springframework.http.MediaType;").set("new", ""));mappingList.add(Kv.by("old", ", produces = MediaType.APPLICATION_JSON_UTF8_VALUE").set("new", ""));mappingList.add(Kv.by("old", "import org.springframework.core.env.Environment;").set("new", ""));// PostMappingmappingList.add(Kv.by("old", "org.springframework.web.bind.annotation.PostMapping").set("new", "org.noear.solon.annotation.Post"));mappingList.add(Kv.by("old", "@PostMapping").set("new", "@Post\n@Mapping"));// GetMappingmappingList.add(Kv.by("old", "org.springframework.web.bind.annotation.GetMapping").set("new", "org.noear.solon.annotation.Get"));mappingList.add(Kv.by("old", "@GetMapping").set("new", "@Post\n@Mapping"));// PutMappingmappingList.add(Kv.by("old", "org.springframework.web.bind.annotation.PutMapping").set("new", "org.noear.solon.annotation.Put"));mappingList.add(Kv.by("old", "@PutMapping").set("new", "@Put\n@Mapping"));// DeleteMappingmappingList.add(Kv.by("old", "org.springframework.web.bind.annotation.DeleteMapping").set("new", "org.noear.solon.annotation.Delete"));mappingList.add(Kv.by("old", "@DeleteMapping").set("new", "@Delete\n@Mapping"));// @RequestBodymappingList.add(Kv.by("old", "org.springframework.web.bind.annotation.RequestBody").set("new", "org.noear.solon.annotation.Body"));mappingList.add(Kv.by("old", "@RequestBody").set("new", "@Body"));// @RequestParammappingList.add(Kv.by("old", "org.springframework.web.bind.annotation.RequestParam").set("new", "org.noear.solon.annotation.Param"));mappingList.add(Kv.by("old", "@RequestParam").set("new", "@Param"));// RequestPartmappingList.add(Kv.by("old", "org.springframework.web.bind.annotation.RequestPart").set("new", "org.noear.solon.annotation.Param"));mappingList.add(Kv.by("old", "@RequestPart").set("new", "@Param"));// Repository;mappingList.add(Kv.by("old", "import org.springframework.stereotype.Repository;").set("new", ""));mappingList.add(Kv.by("old", "@Repository").set("new", ""));// ValidatedmappingList.add(Kv.by("old", "org.springframework.validation.annotation.Validated").set("new", "org.noear.solon.validation.annotation.Validated"));mappingList.add(Kv.by("old", "@Validated\n").set("new", ""));// Configuration;mappingList.add(Kv.by("old", "org.springframework.context.annotation.Configuration").set("new", "org.noear.solon.annotation.Configuration"));// RefreshScope;mappingList.add(Kv.by("old", "import org.springframework.cloud.context.config.annotation.RefreshScope;").set("new", ""));mappingList.add(Kv.by("old", "@RefreshScope\n").set("new", ""));// EnableAsync;mappingList.add(Kv.by("old", "import org.springframework.scheduling.annotation.EnableAsync;").set("new", ""));mappingList.add(Kv.by("old", "@EnableAsync").set("new", ""));// Bean;mappingList.add(Kv.by("old", "org.springframework.context.annotation.Bean").set("new", "org.noear.solon.annotation.Bean"));// ConfigurationProperties;mappingList.add(Kv.by("old", "org.springframework.boot.context.properties.ConfigurationProperties").set("new", "org.noear.solon.annotation.Inject"));mappingList.add(Kv.by("old", "@ConfigurationProperties").set("new", "@Inject"));// CommandLineRunnermappingList.add(Kv.by("old", "import org.springframework.boot.CommandLineRunner;").set("new","import org.noear.solon.core.event.AppLoadEndEvent;\nimport org.noear.solon.core.event.EventListener;"));mappingList.add(Kv.by("old", "CommandLineRunner").set("new", "EventListener<AppLoadEndEvent>"));// Resource;mappingList.add(Kv.by("old", "javax.annotation.Resource").set("new", "org.noear.solon.annotation.Inject"));mappingList.add(Kv.by("old", "@Resource\\(name").set("new", "@Inject\\(value"));// Lazy;mappingList.add(Kv.by("old", "import org.springframework.context.annotation.Lazy;\n").set("new", ""));mappingList.add(Kv.by("old", "@Lazy").set("new", ""));// ValidmappingList.add(Kv.by("old", "javax.validation.Valid").set("new", "org.noear.solon.validation.annotation.Valid"));mappingList.add(Kv.by("old", "javax.validation.constraints.NotEmpty").set("new", "org.noear.solon.validation.annotation.NotEmpty"));mappingList.add(Kv.by("old", "javax.validation.constraints.NotNull").set("new", "org.noear.solon.validation.annotation.NotNull"));// TODO: 补充更多// HttpServletResponse;mappingList.add(Kv.by("old", "javax.servlet.http.HttpServletResponse").set("new", "org.noear.solon.core.handle.Context"));mappingList.add(Kv.by("old", "HttpServletResponse").set("new", "Context"));// MultipartFile;mappingList.add(Kv.by("old", "org.springframework.web.multipart.MultipartFile").set("new", "org.noear.solon.core.handle.UploadedFile"));mappingList.add(Kv.by("old", "MultipartFile").set("new", "UploadedFile"));// consumes = {MediaType.MULTIPART_FORM_DATA_VALUE}mappingList.add(Kv.by("old", ",\n\\s+consumes = \\{MediaType.MULTIPART_FORM_DATA_VALUE\\}").set("new", ""));mappingList.add(Kv.by("old", ", consumes = \\{MediaType.MULTIPART_FORM_DATA_VALUE\\}").set("new", ""));}private static Collection<File> listFiles(String projectBaseDir) {Collection<File> files = FileUtil.loopFiles(new File(projectBaseDir));// 移除 IDEA、Git 自身的文件、Node 编译出来的文件files =files.stream().filter(file ->!file.getPath().contains(separator + "target" + separator)&& !file.getPath().contains(separator + "node_modules" + separator)&& !file.getPath().contains(separator + ".idea" + separator)&& !file.getPath().contains(separator + ".git" + separator)&& !file.getPath().contains(separator + "dist" + separator)&& !file.getPath().contains(separator + "build" + separator)&& !file.getPath().contains(separator + "out" + separator)&& !file.getPath().contains(".iml")&& !file.getPath().contains(".html.gz")&& !file.getPath().contains("build.gradle")&& !file.getPath().contains("pom.xml")).collect(Collectors.toList());return files;}private static String replaceFileContent(File file, List<Kv> mappingList) {String content = FileUtil.readString(file, StandardCharsets.UTF_8);// 如果是白名单的文件类型,不进行重写String fileType = FileTypeUtil.getType(file);if (WHITE_FILE_TYPES.contains(fileType)) {return content;}for (Kv kv : mappingList) {content = content.replaceAll(kv.getStr("old"), kv.getStr("new"));}return content;}private static void writeFile(File file,String fileContent,String oldProjectDir,String oldPageName,String newProjectDir,String newPackageName) {String newPath =buildNewFilePath(file, oldProjectDir, oldPageName, newProjectDir, newPackageName);FileUtil.writeUtf8String(fileContent, newPath);}private static void copyFile(File file,String oldProjectDir,String oldPackageName,String newProjectDir,String newPackageName) {String newPath =buildNewFilePath(file, oldProjectDir, oldPackageName, newProjectDir, newPackageName);FileUtil.copy(file, new File(newPath), true);}private static String buildNewFilePath(File file,String oldProjectDir,String oldPackageName,String newProjectDir,String newPackageName) {return file.getPath().replace(oldProjectDir, newProjectDir).replace(oldPackageName.replaceAll("\\.", Matcher.quoteReplacement(separator)),newPackageName.replaceAll("\\.", Matcher.quoteReplacement(separator)));}
}
注意
再次提醒这个工具不是无缝迁移的,也就是说不能说迁移完就可以直接启动的,这个工具只是减少工作量。