java项目开发 -- 学习记录(一) 项目资源访问路径问题在本地开发环境下我们访问资源一般都是使用了 File 类并且传入相对路径作为构造参数这个方法虽然方便但是我在使用过程中还是遇到了一个麻烦的问题。我需要遍历一个工程文件夹里的所有类将所有带有特定注解的类找出并反射创建我使用了 File 类去获取文件夹并遍历得到类文件对象通过该文件路径去拼接一个全限定名创建出来。但是有一个麻烦问题就是当我打包项目成jar文件后放入linux系统执行后发现使用 File 类根本获取不到这个 jar 包里的路径文件后面弄了很久找到了解决办法1 PathMatchingResourcePatternResolver 类去获取路径上能匹配到的资源2 使用 spring 提供的内置ClassPathScanningCandidateComponentProvider 类去扫描获取类这样就能直接避免路径的问题。//获取test文件夹下面的所有文件文件夹 ListResource resources Arrays.asList(new PathMatchingResourcePatternResolver() .getResources(ResourceUtils.CLASSPATH_URL_PREFIX /org/apache/test/*)); /** 下面就是通过遍历这个列表然后获取资源的名字将名字拼接成全限定名使用 Class.forName(xxx) 函数创建 **/将项目打成 jar 包后放入服务器里此时我们的资源访问路径发生了变化所以代码里的访问路径就要修改这样的话服务器运行和本地运行就需要写代码判断并且需要写死路径我就在想解决这个问题想不管在本地还是服务器只需要一套代码就可以实现资源访问而且不用判断是服务器还是本地运行以下是我想出的方法仅供参考。//直接获取该类在系统里面的绝对路径然后我们只需要规定工程文件夹就能以最小的限制获得比较方便的操作和简单的代码 //resourcePath是自己将资源放置的路径这个随自己定可以写在配置文件里也可以变量存着动态改变 XXXX.class.getProtectionDomain().getCodeSource().getLocation().getPath().split(MyProject/)[0] resourcePath;springboot 项目读取 application.properties/yml 文件使用 Value(“server.port”) 注解的方式获取在对象属性上使用使用 ConfigurationProperties 注解在组件类上使用使用方式和实体类一样适合配置多层级结构使用Environment抽象或Configuration类中的Bean方法配置属性源通过文件流的形式直接读取内容。public class Example { Value(${app.name}) private String appName; } Component ConfigurationProperties(prefix myapp) public class AppConfigProperties { }如果想要设置配置文件的内容只能是通过文件流的形式去设置好像没找到提供的工具类但是我们可以通过配置文件读取数据来改变内容也就是占位符写法但是对于保存配置内容服务器挂了也能看就没办法了。扫描某路径下的所有类并找出有特定注解的类或者继承特定类的子类承接上面的问题不再详细说明自己写工具还是有很多问题没必要重复造轮子但确实学到了东西。ListClass? annotatedClasses new ArrayList(); // 创建一个扫描器不使用默认的过滤器 ClassPathScanningCandidateComponentProvider scanner new ClassPathScanningCandidateComponentProvider(false); // 添加自定义的过滤器例如只扫描特定注解的类 TypeFilter annotationFilter new AnnotationTypeFilter(TaskFlag.class); scanner.addIncludeFilter(annotationFilter); //或者添加根据类的继承关系进行过滤 TypeFilter assignableFilter new AssignableTypeFilter(Object.class); scanner.addIncludeFilter(assignableFilter); for (BeanDefinition beanDefinition : scanner.findCandidateComponents(org.apache.test.services)) { annotatedClasses.add(Class.forName(beanDefinition.getBeanClassName())); }热加载字节码文件实现不用重启服务直接替换文件就起效我们知道如果一个字节码文件被加载链接后我们就算替换了该字节码文件程序依然创建原来的文件对象因为该文件被加载器加载了并且在 jvm 里一直存着所以只能通过重启服务器才能有效。由于我们创建类都是通过默认的类加载器这个加载器如果加载了类那么下次创建就不会重新加载所以我们需要使用自定义加载器去手动实现每次加载都是读取最新的字节码文件这样就可以实现热加载效果了。public class CustomClassLoader extends ClassLoader { byte[] classData ; //通过字节码数据去加载创建类必须重写该方法由于双亲委派机制如果遇到是该类是子类由于父类被默认加载器加载了 //所以就不会重新加载了加载的类数据针对于每个加载器是独立的所以默认加载器加载的类还是旧的类数据 Override public Class? findClass(String name) { if (classData null) {return null;} return defineClass(name, classData, 0, classData.length); } //使用 File 对象去加载字节码文件然后将字节码文件里的数据保存 public void loadClassDataFromFile(File file) throws IOException { //这种写法可以在 try 执行完毕后自动关闭释放输入流对象是Java新特性 try (FileInputStream fis new FileInputStream(file)) { byte[] classData new byte[(int) file.length()]; fis.read(classData); this.classData classData; } } }//通过自定义加载器去加载某个类 CustomClassLoader customClassLoader new CustomClassLoader(); customClassLoader.loadClassDataFromFile(directory); Class clazz customClassLoader.findClass(className); //判断该类是否有某个注解 if (clazz.isAnnotationPresent((Class? extends Annotation) annotationClass)) { .... }maven 打 jar 包在 idea 里面很方便的就可以打包在右边的 maven 工具栏里直接选择 lifecycle 下的 clean 和 package 执行就可以了。但是我遇到了打包出现找不到符号的错误这个错误原因很多大多数都是依赖没有找到但是我这个是 lombok 的原因因为 idea 内置了 lombok 注解器我们正常运行时是没问题的但是我们使用 maven 打包时就会引入 pom 文件里的 lombok 版本所以在依赖追踪检查机制就会通不过。解决方案如下在 pom 文件的 build 节点下加上这段代码即可版本和引入的版本要一致。plugin groupIdorg.apache.maven.plugins/groupId artifactIdmaven-compiler-plugin/artifactId configuration annotationProcessorPaths path groupIdorg.projectlombok/groupId artifactIdlombok/artifactId version1.18.36/version /path /annotationProcessorPaths /configuration /plugin如果我们想不打包某些配置文件或者其他资源文件到 jar 包里因为打包后资源访问路径就变化了通过路径访问就会有问题只能通过流的方式访问而且写回数据到 jar 包里就会非常麻烦所以可以将配置文件放在 jar 包外面这样也能支持动态配置了。//在pom文件里build节点添加以下代码就可以排除不想要打包的文件 plugin groupIdorg.apache.maven.plugins/groupId artifactIdmaven-jar-plugin/artifactId configuration excludes exclude**/*.properties/exclude /excludes /configuration /plugin使用 maven 将别人项目打包的 jar 文件引入本地仓库使用有些时候项目是多个厂商一起开发的那么就会遇到这种别人将他们的工程打包成 jar 文件我们用 maven 导入包后就可以使用他们的类和函数。如果手动直接将包放入 maven 本地仓库会报错因为缺少了很多信息文件。首先我们将 jar 包放入我们的工程文件最外层目录下然后使用 mvn install 命令将包引入到本地仓库里就可以了。//XXX.jar是包的路径如果是放在外面的就设置绝对路径后面就是包的厂商版本信息Dpackaging 是包的格式 mvn install:install-file -DfileXXX.jar -DgroupIdtest -DartifactIdtest-one -Dversion0.0 -Dpackagingjar项目 jar 包方式放置在服务器远程调试代码将项目 jar 包放入服务器执行以下代码//如果找不到 java 命令可以去jdk安装目录下的bin里面找1081是远程连接的端口不是服务器运行端口XXXX.jar 是项目包 //后面参数是指定项目启动配置文件目录也就是 application 那个文件如果我们没有把配置文件打包则可以指定 java -jar -Xdebug -Xrunjdwp:transportdt_socket,servery,suspendn,address1081 XXXX.jar --spring.config.location/home/xxx然后进入 IDEA 打开运行/调试配置窗口新增加远程jvm调试remote jvm debug填写好端口就是上面的1081和主机ip服务器ip然后选好对应的项目模块确认调试就可以了。数字转为字符串时在数字前补0通过 String.format 格式化的方式能够在数字没有足够位数时在前方自动补0。String.format(%02d, 5); //结果为 05对浮点型保留两位小数方法多种DecimalFormat 类 format 方法String.format 方法Math.round 方法BigDecimal 类 setScale 方法。DecimalFormatdfnewDecimalFormat(#.00);//保留两位小数格式固定不会四舍五入Stringresultdf.format(3.1415926);StringresultString.format(%.2f,3.1415926);//也是保留两位小数格式固定不会四舍五入//记住除法是100.0 不能写100结果会成为整数型doubleresultMath.round(3.1415926*100)/100.0;//round方法是四舍五入但是结果是整数所以乘上100左移2位后还原即可BigDecimalresultnewBigDecimal(3.1415926).setScale(2,BigDecimal.ROUND_HALF_UP);//第一个参数是保留2位第二个是需要四舍五入求某数字的小数位数首先最容易考虑到的是将数字转为字符串遍历每个字符但是JAVA里 BigDecimal 类的 scale 方法可以直接返回该结果非常方便高效。// 转为字符串是为了避免转换过程中丢失精度BigDecimalbdnewBigDecimal(String.valueOf(10086.4399));intlengthbd.scale();springboot读取解析excel文件可以使用 easyexcel 工具的实现使用非常简单方便首先在 maven 的 pom 文件里加入依赖。dependency groupIdcom.alibaba/groupId artifactIdeasyexcel/artifactId version3.3.2/version /dependency根据excel文件创建一个对应的实体类对象例如excel文件存的用户数据有三列分别是namegenderage//必须要加 lombok 的 Data 注解否则需要自己写getset方法 Data public class User { //这个写excel文件里实际的列名列名如果是中文的名字那就是 名字 ExcelProperty(name) private String a; ExcelProperty(gender) private String b; ExcelProperty(age) private Integer c; }然后创建一个类去实现 ReadListener User 接口重写 invoke doAfterAllAnalysed 方法invoke 方法会传入一个 User 对象这个对象保存了解析后的一行excel数据所以该方法是逐行解析doAfterAllAnalysed 是解析完excel所有数据后的回调方法。public class UserExcelListener implements ReadListenerUser { Override public void invoke(User data, AnalysisContext context) { // 逐行处理数据 System.out.println(data.getA()处理完!); } Override public void doAfterAllAnalysed(AnalysisContext context) { System.out.println(完成导入!); } }使用jdbc连接sqlserver数据库在某些场景下对方使用了sqlserver数据库我们需要连接该数据库但是在所有配置都正确的情况下连接的时候出现了服务器使用了TLS10不支持TLS13/TLS12的协议错误这个一般是都是数据库版本低而我们使用的jdbc包版本太高导致有几种解决方案修改jdk安装目录下的 java.security 文件找到 jdk.tls.disabledAlgorithms 配置删除 TLSv1 和 TLSv1.1。在jdbc连接url路径里添加 sslProtocol 参数但是根据我自己的试验该做法似乎没有作用。降低jdbc驱动的版本在mvn里引入低版本。dependency groupIdcom.microsoft.sqlserver/groupId artifactIdmssql-jdbc/artifactId version6.4.0.jre8/version /dependencyidea 启动报错java.lang.IndexOutOfBoundsException: Range [-1, -1 1025) out of bounds for length 1024遇到标题的错误后应该是项目在构建的时候电脑突然出现问题导致的可能是蓝屏可能是宕机等等可以通过 build 里的 rebuild project 重新构建项目才能启动完成。这个问题不会导致打包错误。操作Excel文件一般都是通过 apache 的 POI 包去实现操作有些时候如果发现引入依赖后导入不了类可以通过添加 easyexcel 依赖里面也引入了 POI 包所以可以变相的使用下面例子是打开一个 excel 文件模板然后另存。try { // 读取Excel模板 templateFilePath 是文件的路径 Workbook workbook WorkbookFactory.create(new FileInputStream(templateFilePath)); // 获取第一个 sheet 页工作表 Sheet sheet workbook.getSheetAt(0); // 向模板中写入数据 writeDataToSheet(sheet); // 将修改后的内容写入新文件 try (FileOutputStream fileOut new FileOutputStream(outputFilePath)) { workbook.write(fileOut); } // 关闭资源 workbook.close(); } catch (IOException e) { e.printStackTrace(); } private static void writeDataToSheet(Sheet sheet) { setCellValue(sheet, 1, 0, 张三); // 行索引从0开始1代表第2行列索引0代表A列 setCellValue(sheet, 1, 1, 30); setCellValue(sheet, 1, 2, 2025-01-01); mergeCells(sheet, 5, 6, 0, 2, 哈利路亚); //合并单元格方法 } private static void setCellValue(Sheet sheet, int rowIndex, int cellIndex, Object value) { // 获取行如果行不存在则创建 Row row sheet.getRow(rowIndex); if (row null) { row sheet.createRow(rowIndex); } // 获取单元格如果单元格不存在则创建 Cell cell row.getCell(cellIndex); if (cell null) { cell row.createCell(cellIndex); } // 根据值的类型设置单元格内容 if (value instanceof String) { cell.setCellValue((String) value); } else if (value instanceof Number) { cell.setCellValue(((Number) value).doubleValue()); } else if (value instanceof Boolean) { cell.setCellValue((Boolean) value); } else { cell.setCellValue(value.toString()); } } /** * 合并单元格并设置内容 * param sheet 工作表 * param firstRow 起始行索引0-based * param lastRow 结束行索引0-based * param firstCol 起始列索引0-based * param lastCol 结束列索引0-based * param content 要写入的内容写入到合并区域的第一个单元格 */ private static void mergeCells(Sheet sheet, int firstRow, int lastRow, int firstCol, int lastCol, String content) { // 创建合并区域 CellRangeAddress region new CellRangeAddress( firstRow, // 起始行 lastRow, // 结束行 firstCol, // 起始列 lastCol // 结束列 ); // 添加合并区域到工作表 sheet.addMergedRegion(region); // 在合并区域的第一个单元格写入内容 Row row sheet.getRow(firstRow); if (row null) { row sheet.createRow(firstRow); } Cell cell row.getCell(firstCol); if (cell null) { cell row.createCell(firstCol); } cell.setCellValue(content); // 可选设置合并单元格的样式例如居中对齐 CellStyle style sheet.getWorkbook().createCellStyle(); style.setAlignment(HorizontalAlignment.CENTER); // 水平居中 style.setVerticalAlignment(VerticalAlignment.CENTER); // 垂直居中 style.setBorderBottom(BorderStyle.THIN); style.setBorderRight(BorderStyle.THIN); style.setBorderLeft(BorderStyle.THIN); style.setBorderTop(BorderStyle.THIN); // 设置字体加粗 Font font sheet.getWorkbook().createFont(); font.setBold(true); style.setFont(font); cell.setCellStyle(style); // 遍历合并区域内的所有单元格,设置边框合并单元格设置边框必须全部单元格都设置 for (int r firstRow; r lastRow; r) { Row rowTmp sheet.getRow(r); if (rowTmp null) { rowTmp sheet.createRow(r); } for (int c firstCol; c lastCol; c) { Cell cellTmp rowTmp.getCell(c); if (cellTmp null) { cellTmp rowTmp.createCell(c); } cellTmp.setCellStyle(style); } } }同步不同的数据库数据时保证数据一致性方法当遇到主数据库需要同步副数据库数据时我们需要保障两个数据库的数据一致性。当然我们很容易可以想到就是在副数据库的表里面添加一个标识字段每次更改时修改这个字段然后同步的时候根据这个字段来同步同步完再次更改这个字段。对于数据量特别大而且副数据库不允许任何修改时我们可以通过时间将数据划分出来然后根据这个划分区间统计出一个数值出来对比主数据库和副数据库的数值是否一致然后再往下划分直到找到一个合理的区间再全部同步一遍就可以了示例对于每天的收款金额我们首先将两边的数据库里每一年的收款金额统计一个和对比找到数据不一致的那年然后再统计这年的每个月的金额对比找到不同的那个月然后直接将那个月的数据全部同步一遍就可以了。使用Spring JPA时saveALLAndFlush 方法无法持久化数据在我手动删除pgsql数据库里的某些数据后创建表相对应的实体类然后使用 JPA 持久化实体数据但是查看数据库后发现并没有添加这些数据之后我反复检查数据是否主键冲突连接库是否错误表是否产生死锁是否报错异常并没有发现问题然后我又修改了代码结构让批量保存换成循环单次保存或者添加事务手动提交依然没有解决既没有报错也没有保存数据这个不知道是什么问题我就猜测可能和缓存数据有关系导致主键冲突没法保存所以我将该方法改为以下方法顺序执行deleteAll - flush - saveAll - flush 先删除这些数据然后再保存试了后果然成功保存数据了只是原因依然没找到只是将问题解决了仅此记录一下等未来找到原因后更新。使用Spring JPA时需要切换数据库的不同 schema我们使用 JPA 的时候一般都通过配置 application.yml 文件里数据库连接参数来设置当前读取的数据库模式currentSchema如果没有设置则默认是public模式下但有些时候我们就需要读其他模式下的表数据我们可以在 currentSchema 后面多添加几个模式但是在写 sql 的时候就需要指定模式例如 public.table1避免同名表的情况但是有种方法我们可以简单实现切换 schema 就是我们在表实体类上的 TABLE 注解里设置 schema 参数这样就手动指定了该表在哪个模式下而不影响其他代码但是这只能用于切换schema不能用在切换数据库。//访问inquirypublic 两个模式的表jdbc:postgresql://x.x.x.x:5432/xxx?stringtypeunspecifiedcurrentSchemainquiry,public参考链接idea 远程调试 jar 包 https://blog.csdn.net/zhengge417/article/details/132689277保留两位小数 https://shuyeidc.com/wp/71945.htmlidea 启动报错 https://blog.csdn.net/ip36900044/article/details/137105622scene builder 官方 https://gluonhq.com/products/scene-builder/#downloadjava 使用 SSE 技术https://blog.csdn.net/2302_79840586/article/details/142589072动态上传代码热部署https://mp.weixin.qq.com/s/wu6MWzcL8qvzTbXSp9Lt7A