不使用implements关键字实现实现类(类似于mapper)
首先,说明一下功能需求,平时定义一个接口,就要使用implements关键字来实现接口。那么,当不使用此关键字的时候,是否也能使相关接口也能够绑定实现类呢?
答案是肯定的。
此篇文章的主要功能有两个:
1)从实现原理上,更深层次的理解mybatis的映射逻辑;
2)此功能实战中可以通过配置的方式,在不同环境或者客户中执行不同的业务逻辑;
1.创建接口和实现类,但不使用implements
接口:
public interface ProductService {void getProductName(String name);
}
未实现implements关键字的实现类:
@ImplService(parentUrl = "com.example.springdragoncommon.hbl.yms.spring.mapper.service.ProductService")
public class ProductServiceImpl {public ProductServiceImpl(){System.out.println("我是构造函数!");}public void getProductName(String name){System.out.println("我是一个被代理的实现方法!");}
}
可以看到此时没有使用implements关键字,但是使用了一个@ImplService自定义注解,这里的注解就有点类似于mybaties中的<mapper namespace="">
2.创建自定义注解
这里需要两个自定义注解:
1)@ServiceScan注解
用于定义需要扫描的路径,类似于mybatis中的MapperScan功能
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
@Import(ScanServiceImportBeanDefinitionRegistrar.class)
public @interface ServiceScan {/*** 需要扫描的实现类的路径* @return*/String[] packageScan() default {};
}
2)@ImplService注解
此注解就是指定此类实现了哪一个接口的功能。
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
public @interface ImplService {String parentUrl() default "";
}
3.ScanServiceImportBeanDefinitionRegistrar类
此类是ServiceScan注解中,使用@Import注解引入的类,它实现ImportBeanDefinitionRegistrar接口,
public class ScanServiceImportBeanDefinitionRegistrar implements ImportBeanDefinitionRegistrar {@Overridepublic void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {List<String> packages = findPackages(importingClassMetadata);ClassPathScanner classPathScanner = new ClassPathScanner(registry);classPathScanner.addIncludeFilterCustom();classPathScanner.doScan(StringUtils.toStringArray(packages));}/*** 获取扫描注解解析的类* @param importingClassMetadata* @return*/private List<String> findPackages(AnnotationMetadata importingClassMetadata) {AnnotationAttributes annotationAttributes = AnnotationAttributes.fromMap(importingClassMetadata.getAnnotationAttributes(ServiceScan.class.getName()));List<String> packages = new ArrayList<>();for (String className :annotationAttributes.getStringArray("packageScan")){if (StringUtils.hasText(className)) {packages.add(className);}}return packages;}
}
此处主要的类是ClassPathScanner类,它本身实现了ClassPathBeanDefinitionScanner类,重写了doScan方法和addIncludeFilter方法,
public class ClassPathScanner extends ClassPathBeanDefinitionScanner {private BeanDefinitionRegistry registry;private ScanClassBeanFactory scanClassBeanFactory = new ScanClassBeanFactory();public ClassPathScanner(BeanDefinitionRegistry registry) {super(registry);this.registry=registry;}@Overrideprotected Set<BeanDefinitionHolder> doScan(String... basePackages) {Set<BeanDefinitionHolder> beanDefinitionHolders = super.doScan(basePackages);if (beanDefinitionHolders == null || beanDefinitionHolders.isEmpty()){logger.error("not find target class");}else {this.postProcessBeanDefinition(beanDefinitionHolders);}return beanDefinitionHolders;}protected void postProcessBeanDefinition(Set<BeanDefinitionHolder> beanDefinitionHolders){if (beanDefinitionHolders ==null || beanDefinitionHolders.isEmpty()){return;}//此处为了防止多实现,防止注入异常,默认第一个加载Map<String,String> removeMap = new ConcurrentHashMap<>();beanDefinitionHolders.stream().forEach(p->{ScannedGenericBeanDefinition beanDefinition = (ScannedGenericBeanDefinition) p.getBeanDefinition();String parentUrl = beanDefinition.getMetadata().getAnnotations().get(ImplService.class).getString("parentUrl");if (!StringUtils.hasText(removeMap.get(parentUrl))){String targetClassName = beanDefinition.getMetadata().getClassName();try {beanDefinition.getConstructorArgumentValues().addIndexedArgumentValue(0,Class.forName(parentUrl));beanDefinition.getConstructorArgumentValues().addIndexedArgumentValue(1,Class.forName(targetClassName));beanDefinition.setBeanClass(this.scanClassBeanFactory.getClass());} catch (ClassNotFoundException e) {e.printStackTrace();}removeMap.put(parentUrl,targetClassName);}});}public void addIncludeFilterCustom() {//添加扫描拦截器判断addIncludeFilter(new TypeFilter() {@Overridepublic boolean match(MetadataReader metadataReader, MetadataReaderFactory metadataReaderFactory) throws IOException {return true;}});}
}
1)doScan方法
调用了父类的doScan方法,即获取当前basePackages下的所有实现类的BeanDefinitionHolder,
2)postProcessBeanDefinition方法
此方法为关键方法:
它首先获取了实现类的bean定义,即上面ProductServiceImpl类的bean定义;
然后获取到了要实现的接口,然后通过bean定义提供的操作,将ProductServiceImpl的bean定义转换成了ScanClassBeanFactory的bean定义,即一个实现类对应一个ScanClassBeanFactory的bean定义;
在此过程中,就可以进行不同环境或客户提取不同的实现类,此处没有实现,可以直接配置一个环境变量,类似于key-value这种参数,不同环境下取什么实现类,然后在此处判断处理即可;
3)addIncludeFilterCustom方法
此处添加的是类生成定义时候使用的过滤器,不重写的话可能存在问题,生成不了自己需要的bean定义
4.ScanClassBeanFactory类
public class ScanClassBeanFactory<T> implements FactoryBean {private Class<T> targetClass;private Class<T> targetImplClassName;public ScanClassBeanFactory(){}public ScanClassBeanFactory(Class<T> targetClass, Class<T> targetImplClassName) {this.targetClass = targetClass;this.targetImplClassName = targetImplClassName;}@Overridepublic Object getObject() throws Exception {Object object = targetImplClassName.newInstance();BeanScanInvocationHandler beanScanInvocationHandler = new BeanScanInvocationHandler(object,targetClass);return Proxy.newProxyInstance(this.getClass().getClassLoader(), new Class[]{targetClass}, beanScanInvocationHandler);}@Overridepublic Class<?> getObjectType() {return this.targetClass;}
}
它实现了FactoryBean接口,所以会调用一次getObject方法,此方法使用了代理方式,即给接口代理实际的实现类;
5.BeanScanInvocationHandler类
public class BeanScanInvocationHandler implements InvocationHandler {private Object target;private Class<?> interfaces;private Map<Method,Method> methodMap;public BeanScanInvocationHandler(Object target,Class<?> interfaces){this.target = target;this.interfaces = interfaces;this.methodMap = getMethods(target.getClass(),interfaces);}@Overridepublic Object invoke(Object proxy, Method method, Object[] args) throws Throwable {Method targetMethod = methodMap.get(method);return targetMethod.invoke(target,args);}private Map<Method, Method> getMethods(Class<?> delegate, Class<?>... interfaces){Map<Method, Method> map;List<Method> methods = new ArrayList<>();for (Class<?> sourceClass : interfaces) {methods.addAll(getMethods(sourceClass));}map = new HashMap<>(methods.size(), 1.0f);for (Method method : methods) {try {map.put(method, delegate.getMethod(method.getName(), method.getParameterTypes()));} catch (NoSuchMethodException ignore) {throw new RuntimeException(ignore);}}return map;}private Collection<? extends Method> getMethods(Class<?> sourceClass) {Set<Method> result = new HashSet<>();Class<?> searchType = sourceClass;while (searchType != null && searchType != Object.class) {result.addAll(filterPublicMethods(Arrays.asList(sourceClass.getDeclaredMethods())));if (sourceClass.isInterface()) {Class<?>[] interfaces = sourceClass.getInterfaces();for (Class<?> interfaceClass : interfaces) {result.addAll(getMethods(interfaceClass));}searchType = null;} else {searchType = searchType.getSuperclass();}}return result;}private Collection<? extends Method> filterPublicMethods(List<Method> methods) {List<Method> result = new ArrayList<>(methods.size());for (Method method : methods) {if (Modifier.isPublic(method.getModifiers())) {result.add(method);}}return result;}
}
此方法主要先通过接口获取其所有的类信息,然后在通过代理的实现调用实现类的对应方法;
到此,功能代码结束,运行一下看一下效果

此处使用了postman调用,直接注入接口调用即可;代码很简单就不在写出来了。
相关文章:
不使用implements关键字实现实现类(类似于mapper)
首先,说明一下功能需求,平时定义一个接口,就要使用implements关键字来实现接口。那么,当不使用此关键字的时候,是否也能使相关接口也能够绑定实现类呢? 答案是肯定的。 此篇文章的主要功能有两个…...
antd4里table的滚动是如何实现的?
rc-table里Header、Footer、TableBody实现保持同频滚动的方法 场景:Header、Footer都有,Table设置了scrollX,才关注同频滚动 那么是如何实现的? 监听onScroll方法获取到滚动条向左的滚动的距离scrollLeft;同时给三个…...
抓取namenode 50070 jmx的指标信息
在生产实践过程中,需要把data退役之后需要停机下线,在下线之前需要确认机器是否已下线完成,要去namenode的50070界面上查看显然效率低,为了能够快速拿到节点信息,写了简单的脚本。jmx/50070还有很多信息可以获取&#…...
aspnetcore-browser-refresh.js和Visual Studio Browser Link
我在调试ASP.NET Core web应用时,发现请求的页面文档底部多了一部分文件,而在我的页面中却没有包含,故查询资料,在此记录: 图中,可以看到红框部分是多出来了2个脚本 1.aspnetcore-browser-refresh.js 这里…...
hadoop 集群常用命令(学习笔记) —— 筑梦之路
概念介绍 #HDFS 概述Hadoop Distributed File System,简称HDFS,是一个分布式文件系统。(1)NameNode(nn):存储文件的元数据,如文件名,文件目录结构,文件属性&…...
ARC142D Deterministic Placing
ARC142D Deterministic Placing 题目大意 有一棵nnn个顶点的树,每个点上最多放一张卡片,你可以做如下操作: 同时将所有的卡片移到它所在顶点的相邻的一个顶点上 一个操作我们说它是好的,当下列条件满足: 每条边最…...
阶段八:服务框架高级(第二章:分布式事务)
阶段八:服务框架高级(第二章:分布式事务)Day-分布式事务0.学习目标1.分布式事务问题1.1.本地事务1.2.分布式事务1.3.演示分布式事务问题2.理论基础2.1.CAP定理2.1.1.一致性2.1.2.可用性2.1.3.分区容错2.1.4.矛盾2.2.BASE理论2.3.解…...
RPC异步化原理
深入RPC,更好使用RPC,须从RPC框架整体性能考虑问题。得知道如何提升RPC框架的性能、稳定性、安全性、吞吐量及如何在分布式下快速定位问题。RPC框架如何压榨单机吞吐量? 1 前言 TPS一直上不去,压测时CPU压到40%~50%就…...
C# 多窗口切换的实现
1、目的在主窗口中根据不同的按钮选择不同的子窗口显示。2、实现(1)、创建Winform窗体程序,放入SplitContainer控件splitContainer1将窗体分成左右2部分;(2)、在左侧splitContainer1.panel1中放入3个Button…...
【深度学习】RNN
1. 什么是RNN 循环神经网络(Recurrent Neural Network, RNN)是一类以序列(sequence)数据为输入,在序列的演进方向进行递归(recursion)且所有节点(循环单元)按链式连接的递…...
招聘岗位,机会难得
岗位需求 费话不多说,直接上JD: 嵌入式开发工程师: 17:411.计算机、通信等相关专业。 2.熟悉网络基础知识,熟悉802.11a/b/g/n/ac协议,能通过抓包等分析手段排查定位各种wifi相关问题。 3.熟悉路由器主要功能及实现原…...
web打印的几种方法(2023)
在工作中出现web打印的情况是非常多的,其实这也是一个比较烦人的问题,这篇博客整理一下关于Web打印的一些方法或者方式。 1. window.print() 这个方法是用来打印网页的,页面上的其他的元素也会被打印处理,在打印的时候页眉页脚是…...
代码随想录算法训练营day44 | 动态规划之完全背包 518. 零钱兑换 II 377. 组合总和 Ⅳ
day44完全背包基础知识问题描述举个栗子518. 零钱兑换 II1.确定dp数组以及下标的含义2.确定递推公式3.dp数组如何初始化4.确定遍历顺序5.举例推导dp数组377. 组合总和 Ⅳ1.确定dp数组以及下标的含义2.确定递推公式3.dp数组如何初始化4.确定遍历顺序5.举例来推导dp数组完全背包基…...
IntelliJ IDEA 实用插件推荐(包含使用教程)
IntelliJ IDEA 实用插件推荐 背景:电脑重装了,重新下载了最新版的IntelliJ IDEA,感觉默认模式有点枯燥,于是决定从网上下载一些实用美观的插件优化自己以后吃饭的工具,现在推荐的都是目前还能用的(亲身实践…...
WideDeep模型
google提出的Wide&deep模型,将线性模型与DNN很好的结合起来,在提高模型泛化能力的同时,兼顾模型的记忆性。wide&deep这种将线性模型与DNN的并行连接模式,后来称为推荐领域的经典模式,奠定了后面深度学习模型的…...
nacos集群模式+keepalived搭建高可用服务
实际工作中如果nacos这样的核心服务停掉了或者整个服务器宕机了,那整个系统也就gg了,所以像这样的核心服务我们必须要搞个3个或者3个以上的nacos集群部署,实现高可用; 部署高可用版本之前,首先你要会部署单机版的naco…...
吉利「银河」负重突围
吉利控股集团最新公布的数据显示,2022年,吉利控股集团汽车总销量超230万辆,同比增长4.3%。其中,新能源汽车销量超64万辆,同比增长100.3%。 在中国本土市场,2022年吉利集团旗下品牌乘用车总交付量为135.84万…...
QT之图形视图框架概述——Graphics View Framework
QT之图形视图框架概述——Graphics View Framework1. 概述2. 核心类3. 事件传递4. Graphics View 坐标系统5. 参考1. 概述 Graphics View Framework是子Qt 4.2引入的,用来取代之前版本中的QCanvas。Graphics View Framework提拱了用于大量2D图形项的管理和交互的能…...
【SQL开发实战技巧】系列(二十二):数仓报表场景(上) 从分析函数效率一定快吗聊一聊结果集分页和隔行抽样实现方式
系列文章目录 【SQL开发实战技巧】系列(一):关于SQL不得不说的那些事 【SQL开发实战技巧】系列(二):简单单表查询 【SQL开发实战技巧】系列(三):SQL排序的那些事 【SQL开发实战技巧…...
小米无线AR眼镜探索版细节汇总
在MWC 2023期间,小米正式发布了一款无线AR眼镜,虽然还没看过实机,但XDA提前上手体验,我们从中进行总结。首先我要说的是,小米这款眼镜和高通无线AR眼镜参考设计高度重叠,产品卖点几乎一致,只是增…...
eNSP-Cloud(实现本地电脑与eNSP内设备之间通信)
说明: 想象一下,你正在用eNSP搭建一个虚拟的网络世界,里面有虚拟的路由器、交换机、电脑(PC)等等。这些设备都在你的电脑里面“运行”,它们之间可以互相通信,就像一个封闭的小王国。 但是&#…...
《通信之道——从微积分到 5G》读书总结
第1章 绪 论 1.1 这是一本什么样的书 通信技术,说到底就是数学。 那些最基础、最本质的部分。 1.2 什么是通信 通信 发送方 接收方 承载信息的信号 解调出其中承载的信息 信息在发送方那里被加工成信号(调制) 把信息从信号中抽取出来&am…...
2025 后端自学UNIAPP【项目实战:旅游项目】6、我的收藏页面
代码框架视图 1、先添加一个获取收藏景点的列表请求 【在文件my_api.js文件中添加】 // 引入公共的请求封装 import http from ./my_http.js// 登录接口(适配服务端返回 Token) export const login async (code, avatar) > {const res await http…...
精益数据分析(97/126):邮件营销与用户参与度的关键指标优化指南
精益数据分析(97/126):邮件营销与用户参与度的关键指标优化指南 在数字化营销时代,邮件列表效度、用户参与度和网站性能等指标往往决定着创业公司的增长成败。今天,我们将深入解析邮件打开率、网站可用性、页面参与时…...
MySQL的pymysql操作
本章是MySQL的最后一章,MySQL到此完结,下一站Hadoop!!! 这章很简单,完整代码在最后,详细讲解之前python课程里面也有,感兴趣的可以往前找一下 一、查询操作 我们需要打开pycharm …...
Modbus RTU与Modbus TCP详解指南
目录 1. Modbus协议基础 1.1 什么是Modbus? 1.2 Modbus协议历史 1.3 Modbus协议族 1.4 Modbus通信模型 🎭 主从架构 🔄 请求响应模式 2. Modbus RTU详解 2.1 RTU是什么? 2.2 RTU物理层 🔌 连接方式 ⚡ 通信参数 2.3 RTU数据帧格式 📦 帧结构详解 🔍…...
Electron简介(附电子书学习资料)
一、什么是Electron? Electron 是一个由 GitHub 开发的 开源框架,允许开发者使用 Web技术(HTML、CSS、JavaScript) 构建跨平台的桌面应用程序(Windows、macOS、Linux)。它将 Chromium浏览器内核 和 Node.j…...
使用 uv 工具快速部署并管理 vLLM 推理环境
uv:现代 Python 项目管理的高效助手 uv:Rust 驱动的 Python 包管理新时代 在部署大语言模型(LLM)推理服务时,vLLM 是一个备受关注的方案,具备高吞吐、低延迟和对 OpenAI API 的良好兼容性。为了提高部署效…...
AWSLambda之设置时区
目标 希望Lambda运行的时区是东八区。 解决 只需要设置lambda的环境变量TZ为东八区时区即可,即Asia/Shanghai。 参考 使用 Lambda 环境变量...
dvwa11——XSS(Reflected)
LOW 分析源码:无过滤 和上一关一样,这一关在输入框内输入,成功回显 <script>alert(relee);</script> MEDIUM 分析源码,是把<script>替换成了空格,但没有禁用大写 改大写即可,注意函数…...
