{"id":21915740,"url":"https://github.com/admin4j/plugin","last_synced_at":"2026-05-19T04:38:55.750Z","repository":{"id":154626035,"uuid":"630342474","full_name":"admin4j/plugin","owner":"admin4j","description":"java plugin","archived":false,"fork":false,"pushed_at":"2023-12-15T07:15:14.000Z","size":80,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":1,"default_branch":"master","last_synced_at":"2025-01-27T09:26:15.021Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"Java","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/admin4j.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null}},"created_at":"2023-04-20T07:18:08.000Z","updated_at":"2023-04-20T08:26:02.000Z","dependencies_parsed_at":"2023-12-15T08:26:41.178Z","dependency_job_id":"3713d5fb-0f74-4178-8738-9f4360f4ec27","html_url":"https://github.com/admin4j/plugin","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/admin4j%2Fplugin","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/admin4j%2Fplugin/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/admin4j%2Fplugin/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/admin4j%2Fplugin/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/admin4j","download_url":"https://codeload.github.com/admin4j/plugin/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":244936279,"owners_count":20534980,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2024-11-28T19:13:58.007Z","updated_at":"2026-05-19T04:38:55.710Z","avatar_url":"https://github.com/admin4j.png","language":"Java","funding_links":[],"categories":[],"sub_categories":[],"readme":"# 概述\n\n前置项目中，经常有定制的需求，如何更优雅实现定制的需求\n\n前置云端拆分第一阶段，是把前置云端的代码拆分，第二阶段的目标是实现前置云端共用一个 SDK，如何实现扩展性成了一个问题，我试着给出自己的思考\n\n设计模式\n====\n\n对于一些有可能发生变化的地方，进行封装（关注点分离），应用策略模式、责任链模式等手段进行扩展点设计。\n\n![](https://mmbiz.qpic.cn/mmbiz_jpg/gInEnAhFf5wu59mrQx0ucec7S8DaC8AcHhXjxZVOtoiaQDpw2VxBrsWlPAzzP7DL8XwwbLP2M7mlx7OKiauAfBNg/640?wx_fmt=jpeg)\n\n扩展点\n===\n\nJava 原生 SPI\n-----------\n\nJAVA SPI = 基于接口的编程＋策略模式＋配置文件 的动态加载机制\n\n对于 Java 中的 SPI，有如下的约定：\n\n1.  在 META-INF/services / 目录中创建以接口全限定名命名的文件该文件内容为 Api 具体实现类的全限定名\n\n2.  使用 ServiceLoader 类动态加载 META-INF 中的实现类\n\n3.  如 SPI 的实现类为 Jar 则需要放在主程序 classPath 中\n\n4.  Api 具体实现类必须有一个不带参数的构造方法\n\n实际上就是 JDK 的就是 ServiceLoader 会遍历所有 jar 查找 META-INF/services / 接口类名 文件，找到后就加载文件中配置的文件进行实例化使用。\n\nSpring 的扩展点\n-----------\n\nSPI 实现机制\n\n在 Spring 中提供了 SPI 机制，我们只需要在 META-INF/spring.factories 中配置接口实现类名，即可通过服务发现机制，在运行时加载接口的实现类：\n\n```\norg.springframework.boot.autoconfigure.EnableAutoConfiguration=\\org.springframework.boot.autoconfigure.admin.SpringApplicationAdminJmxAutoConfiguration\n```\n\n配置好 spring.factories 文件后，我们就可以通过 SpringFactoriesLoader 动态加载接口实现类了\n\nBeanFactoryPostProcessor 接口\n\nBeanFactory 生成后，如果想对 BeanFactory 进行一些处理，该怎么办呢？BeanFactoryPostProcessor 接口就是用来处理 BeanFactory 的。\n\nBeanDefinitionRegistryPostProcessor 接口\n\n在 Spring 容器初始化时，首先会读取应用程序中的配置文件，并解析出所有的 Bean 定义，然后将这些 Bean 定义注册到容器中。在这个过程中，BeanDefinitionRegistryProcessor 提供了一种机制，允许开发人员在 Bean 定义注册之前和之后对 Bean 定义进行自定义处理，例如添加，修改或删除 Bean 定义等。\n\nMybatis 中 org.mybatis.spring.mapper.MapperScannerConfigurer 就实现了该方法，在只有接口没有实现类的情况下找到接口方法与 sql 之间的联系从而生成 BeanDefinition 并注册。\n\nBeanPostProcessor 接口\n\n在 Spring 容器实例化 bean 之后，在执行 bean 的初始化方法前后，允许我们自定义修改新的 bean 实例，如修改 bean 的属性，可以给 bean 生成一个动态代理实例等等。\n\nSpring AOP 的底层处理也是通过实现 BeanPostProcessor 来执行代理包装逻辑的。\n\nAOP\n===\n\nAOP 为 Aspect Oriented Programming 的缩写，意为：面向切面编程，通过预编译方式和运行动态代理实现程序功能的统一维护的一种技术。AOP 是 OOP 的延续，是软件开发中的一个热点，也是 Spring 框架中的一个重要内容，是函数式编程的一种衍生范型。利用 AOP 可以对业务逻辑的各个部分进行隔离，从而使得业务逻辑各部分之间的耦合度降低，提高程序的可重用性，同时提高开发的效率。\n\n我们一般做活动的时候，一般对每一个接口都会做活动的有效性校验（是否开始、是否结束等等）、以及这个接口是不是需要用户登录。\n\n![](https://mmbiz.qpic.cn/mmbiz_png/gInEnAhFf5wu59mrQx0ucec7S8DaC8AcJyCVLkYUmyX9Vza8d2ymHbLiaZM0neUKIazFTOyNCIA5DjPOHZwdyMw/640?wx_fmt=png)\n\n![](https://mmbiz.qpic.cn/mmbiz_png/gInEnAhFf5wu59mrQx0ucec7S8DaC8AcqywsXeXnXkmff9BL1BTNmBM0rBXv3RnKIqG4XVCNGm2CmPPrM40TMg/640?wx_fmt=png)\n\n![](https://mmbiz.qpic.cn/mmbiz_png/gInEnAhFf5wu59mrQx0ucec7S8DaC8AcByILWKMZImDPwIOmbibz5vrL5fLkMcsclics191anWSIAesyORwuLWOg/640?wx_fmt=png)\n\nAOP + 设计模式\n\n可配置化\n\n插件化\n===\n\n常见的动态插件的实现方式有 SPI、OSGI 等方案，插件化开发模式正在很多编程语言或技术框架中得以广泛的应用实践，比如大家熟悉的 jenkins，docker 可视化管理平台 rancher，以及日常编码使用的编辑器 idea，eclipse 等，随处可见的带有热插拔功能的插件，让系统像插了翅膀一样，大大提升了系统的扩展性和伸缩性，也拓展了系统整体的使用价值。\n\n#### 1 模块解耦\n\n实现服务模块之间解耦的方式有很多，但是插件来说，其解耦的程度似乎更高，而且更灵活，可定制化、个性化更好。\n\n#### 2 提升扩展性和开放性\n\n插件化机制让系统的扩展性得以提升，从而可以丰富系统的周边应用生态。\n\n#### 3 方便第三方接入\n\n有了插件之后，第三方应用或系统如果要对接自身的系统，直接基于系统预留的插件接口完成一套适合自己业务的实现即可，而且对自身系统的侵入性很小，甚至可以实现基于配置参数的热加载，方便灵活，开箱即用。\n\n![](https://mmbiz.qpic.cn/mmbiz_png/gInEnAhFf5wu59mrQx0ucec7S8DaC8Ac7ic9zw6RibQfSLSO4QwNImSD6CWplMNOXfnVD0Opj3QUqaNuvknnE6icw/640?wx_fmt=png)\n\n**最佳实践**\n\n1.  面向接口编程\n\n2.  方法粒度足够细（对应一个功能点），方法异常能够区分\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fadmin4j%2Fplugin","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fadmin4j%2Fplugin","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fadmin4j%2Fplugin/lists"}