制作自定义pom依赖-属于自己的SDK 2024-11-09 ## 背景 前一段时间的暑假, 自己按照项目要求做了一个SDK, 可以以pom依赖的方式在其他的项目引入, 实现一些功能. ![](https://cdn.jsdmirror.com/gh/faken233/imgs/save/2265677703.png) 本文将讲解**不同方案**下, 我如何编写一套**自定义日志收集&接口访问信息统计**的SDK, 同时也希望给一些想自己开发SDK的朋友一些开发框架上的灵感. > 本文中, 引用依赖的项目为使用Spring Boot搭建的web项目. 为了阅读本文, 读者应有一定的Spring Boot基础, 最好有了解过spring自动装配原理以及配置类. > 事先声明, 笔者也只是简略的开发了一个SDK, 水平不如其他的大牛, 本文仅供一个开发的分享, 讲述一下面对某个需求, 笔者如何通过两种方案进行SDK的设计. 同时, 本文也只着重强调**如何做依赖**, 不会详细讲解我做了什么依赖 ## 基于IOC容器 IOC容器可以帮助我们管理应用中的各种组件`Bean`, 实现了**控制反转**和**依赖注入**. 在SDK中, 不少的组件需要在启动应用时进行配置读取和初始化. 在初始化以后, 这些组件对应的**实例**就可以**放在IOC容器中**, 他们的生命周期以及对应的注入操作**交由框架处理**. 我的依赖中有四个重要的组件. - ServerProperties 配置属性 - DataSender 发信器 - RequestManageProcessor 请求解析器 - ManageRegister 接口注册器 > 着重说明的是如何开发依赖. 对于组件的详情, 略. 对于配置属性, 我的期望是可以读取配置文件, 然后创建一个实例存储读取到的配置信息. > 延伸一下: spring框架对于Bean默认都是单例创建. 类似的这种存储配置信息的实例适合使用单例模式去设计, 符合框架的默认模式. 余下三个组件都是功能组件, 依赖于包含配置信息的ServerProperties实例, 进行自身的初始化, 同时三者之间也有委托的关系. ```java public RequestManageProcessor(ManageRegister manageRegister, DataSender dataSender) { this.modelPathMap = manageRegister.getModelPathMap(); this.apiPathMap = manageRegister.getApiPathMap(); this.dataSender = dataSender; } ``` 现在需要做的, 是在项目中引入依赖后, 实现配置读取, 组件初始化以及将组件交给框架处理. ### 配置读取 基于IOC容器这种方案下, 配置读取操作比后面要讲的方案麻烦很多**(方案2真的很方便).** 这个方案单纯就是读取并解析properties文件, 对实例的成员变量进行赋值--就是这么无脑的操作. 顺便复习一下读取properties文件的操作吧. 上代码! ```java public class ServerProperties { private String serverPort; private String serverIp; private String projectId; private boolean enabledAccessLog = false; private boolean enableErrorLog = false; private int initialDelay = 0; private int period = 5; private TimeUnit timeUnit = TimeUnit.SECONDS; // 使用代码块进行加载 { Properties properties = new Properties(); // 获取当前类的类加载器, 读取properties文件, 对静态成员字段进行赋值 ClassLoader classLoader = ServerProperties.class.getClassLoader(); // 定死配置文件的命名为 settings.properties URL resource = classLoader.getResource("settings.properties"); if (resource != null) { try { properties.load(new InputStreamReader(resource.openStream())); } catch (IOException e) { // TODO 自定义异常处理 throw new RuntimeException("Failed to load settings.properties", e); } } try { serverPort = properties.getProperty("server.port"); serverIP = properties.getProperty("server.ip"); projectId = properties.getProperty("project.id"); } catch (Exception e) { throw new RuntimeException("Failed to load settings.properties, maybe wrong key-value settings", e); } } } ``` 注意执行顺序: 实例变量赋值->非静态代码快->构造函数. ### 组件初始化-组件交给IOC容器管理 编写组件时, 自定义初始化操作要么在构造函数中执行, 要么在代码块中执行, 或者在两者之一调用类里面的自定义方法, 总之**交给IOC容器的组件必须是已经初始化的**. 在我的方案中, 各个组件的初始化操作都交给了构造函数执行. 那么在项目中, **创建一个配置类以后, 在配置类中的相应方法调用各个组件的构造函数**, 不就可以完成初始化操作以及控制反转了吗? ```java @Configuration public class ManageConfig { @Bean public ServerProperties addServerProperties() { return new ServerProperties(); } @Bean public ManageRegister addManageRegister() { return new ManageRegister( AccountController.class, AdminController.class, AuthorizationController.class, HardwareController.class, SoftwareController.class, UserController.class ); } @Bean public DataSender addDataSender(ServerProperties serverProperties) { return new DataSender( serverProperties, //... ); } @Bean public RequestManageProcessor addRequestManageProcessor(ManageRegister manageRegister, DataSender dataSender) { return new RequestManageProcessor( manageRegister, dataSender ); } } ``` 四个组件在配置类中初始化完, 随机被加入IOC容器. 互相委托所需要的传参也实现了, 例如DataSender需要一个ServerProperties组件, 而这个组件已经被加入到IOC容器了.(执行顺序) ### 打包 执行`mvn install`, 本地的外部项目就可以通过pom文件引入这个依赖. ### 方案评价 这个方案没有很好的把配置流程简化. 虽然**约定大于配置**, 和使用者说明这种配置方式其实也无伤大雅, 但是又是要另外写一个配置文件, 又要连着配置四个组件, 多少有点复杂. **打包后的引入依赖实际上就是把一群类, 注解, 异常之类的导入进去, 每一个对象都需要new出来, 再手动地交给IOC容器**. 下面介绍的方案就大大地简化配置过程, 充分使用spring的功能, 配置以及使用体验不亚于其他常用的依赖. ## 基于spring自动装配 上面的方案将很多事情交给用户自己手动完成, 那有没有一种方案可以**让框架帮我们进行初始化**, 我们只需要进行必要的手动配置就行了? 换句话说, 我把**配置类直接写好在第三方依赖**中, 项目启动时可以读取到这些配置类, 不就不需要用户来做这件事了? Spring Boot 的启动类(通常带有 `@SpringBootApplication` 注解)默认扫描**当前包及其子包中的组件**。这意味着如果我们在自制的第三方依赖中定义了配置类(如 `@Configuration` 类),并且这些类不在启动类所在的包及其子包中,Spring Boot 默认是不会扫描到这些类的。 下面列举几种方法, 可以确保框架扫描到第三方依赖中的配置类. ### @Component注解 在启动类上加上@Component注解, 手动指定要扫描的包. 指定之后**其包及其子包下**的配置类以及`@Component`注解标记的组件都会被扫描到。 ### @Import注解 在启动类上加上@Import注解, 手动导入第三方依赖的配置类或组件 > 启动类上常见的@Enablexxx注解, 其中一般都会嵌套一个@Import(xxx.class)注解, 用于手动导入配置类, 加上这种注解后, 被导入的依赖才会被自动配置. ### 使用 `@SpringBootApplication` 的 `scanBasePackages` 属性 你可以在启动类上使用 `@Import` 注解, 手动导入第三方依赖中的配置类. 例如: ```java @SpringBootApplication(scanBasePackages = {"com.example.yourproject", "com.example.thirdparty"}) public class YourApplication { public static void main(String[] args) { SpringApplication.run(YourApplication.class, args); } } ``` 在这个例子中, `com.example.thirdparty` 是第三方依赖的包路径. ### 编写自动配置信息 **这个是最推荐, 也是最简单的方法, 充分体现了约定大于配置的原则.** `org.springframework.boot.autoconfigure.AutoConfiguration.imports` 文件是Spring Boot框架自动配置机制的一部分. 这个文件一般位于`META-INF` 目录下, 用于指定自动配置类. 并且, 所有外部库的META-INF目录下的这个文件, 都会被框架读取到, 当你开发一个自制的第三方依赖库, 并且希望这个库中的自动配置类能够被 Spring Boot 自动加载时, 你可以将这些自动配置类的全限定类名添加到 `AutoConfiguration.imports` 文件中。 例如, 我的sdk原本需要用户在项目中手写配置类进行组件配置, 现在我可以直接把这个配置类写在sdk源码中. ![image-20241109164345332](https://cdn.jsdmirror.com/gh/faken233/imgs/save/image-20241109164345332-1731168945916-1.png) ![image-20241109173559051](https://cdn.jsdmirror.com/gh/faken233/imgs/save/image-20241109173559051-1731169013510-4.png) ```java @Configuration @EnableConfigurationProperties({ServerProperties.class}) @ConditionalOnBean({ManageRegister.class}) public class ManageYourProjectAutoConfiguration { public ManageYourProjectAutoConfiguration() { } @Bean @ConditionalOnMissingBean public ServerProperties serverProperties() { return new ServerProperties(); } @Bean @ConditionalOnMissingBean public DataSender dataSender(ServerProperties serverProperties) { return new DataSender(serverProperties, serverProperties.isEnabledAccessLog(), serverProperties.isEnableErrorLog(), (long)serverProperties.getInitialDelay(), (long)serverProperties.getPeriod(), serverProperties.getTimeUnit()); } @Bean @ConditionalOnBean({ManageRegister.class}) public RequestManageProcessor requestManageProcessor(ManageRegister manageRegister, DataSender dataSender) { return new RequestManageProcessor(manageRegister, dataSender); } } ``` 如此下来, 四个组件中有三个硬编码就解决配置的组件就完成初始化和控制反转了. 项目引入依赖, 启动之后, 框架就会读取`org.springframework.boot.autoconfigure.AutoConfiguration.imports` 文件, 进行配置类的读取. > 在这个SDK中, 用户必须在项目中手动注入最后一个组件, 因为需要用户传入Controller的字节码文件. 所以这里会出现一些Bean初始化顺序的问题. 这个问题笔者暂时没有研究明白, 看后面会不会再详细说一下有关自定义依赖中组件初始化以及项目组件初始化的互相依赖以及执行顺序问题. ### 添加配置元数据, 让IDE实现配置信息自动联想 不论配置多自动, 总会有一些配置需要用户自定义, 例如我的SDK需要配置服务器端口, 地址以及项目信息, 这些必须要用户填写. 而application.yml文件里不知道为什么读取不到我ServerProperties类上添加的`@ConfigurationProperties(prefix = "manage-platform")`信息, 一直不能自动联想配置项. 查阅资料后, 发现当我SDK中引入一个依赖之后, 依赖帮我生成了一个`spring-configuration-metadata.json`文件, 这个文件被IDE识别之后似乎就能在`application.yml`文件中自动联想配置信息了. ![image-20241109180042635](https://cdn.jsdmirror.com/gh/faken233/imgs/save/image-20241109180042635-1731169020115-7.png) ### 方案评价 这个方案说实话, 了解spring这些机制的人会明白, 但是对我来说, 第一次深入去了解这些机制还是十分困难的. 但是这个方案确实优于上一个方案, 把各种配置放在自己的依赖中, 基于框架进行自动配置. 本文作者: 松鼠 原文链接: 制作自定义pom依赖-属于自己的SDK 版权声明: 本站所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处! 免责声明: 文中如涉及第三方资源,均来自互联网,仅供学习研究,禁止商业使用,如有侵权,联系我们24小时内删除! « 上一篇没有了 下一篇 »记录一次非常不应该的错误-406响应码
评论2
dgnaepwhcn
真棒!
saqlfniesw
博主太厉害了!