不使用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眼镜参考设计高度重叠,产品卖点几乎一致,只是增…...
Python|GIF 解析与构建(5):手搓截屏和帧率控制
目录 Python|GIF 解析与构建(5):手搓截屏和帧率控制 一、引言 二、技术实现:手搓截屏模块 2.1 核心原理 2.2 代码解析:ScreenshotData类 2.2.1 截图函数:capture_screen 三、技术实现&…...
生成xcframework
打包 XCFramework 的方法 XCFramework 是苹果推出的一种多平台二进制分发格式,可以包含多个架构和平台的代码。打包 XCFramework 通常用于分发库或框架。 使用 Xcode 命令行工具打包 通过 xcodebuild 命令可以打包 XCFramework。确保项目已经配置好需要支持的平台…...
7.4.分块查找
一.分块查找的算法思想: 1.实例: 以上述图片的顺序表为例, 该顺序表的数据元素从整体来看是乱序的,但如果把这些数据元素分成一块一块的小区间, 第一个区间[0,1]索引上的数据元素都是小于等于10的, 第二…...
使用VSCode开发Django指南
使用VSCode开发Django指南 一、概述 Django 是一个高级 Python 框架,专为快速、安全和可扩展的 Web 开发而设计。Django 包含对 URL 路由、页面模板和数据处理的丰富支持。 本文将创建一个简单的 Django 应用,其中包含三个使用通用基本模板的页面。在此…...
【kafka】Golang实现分布式Masscan任务调度系统
要求: 输出两个程序,一个命令行程序(命令行参数用flag)和一个服务端程序。 命令行程序支持通过命令行参数配置下发IP或IP段、端口、扫描带宽,然后将消息推送到kafka里面。 服务端程序: 从kafka消费者接收…...
ssc377d修改flash分区大小
1、flash的分区默认分配16M、 / # df -h Filesystem Size Used Available Use% Mounted on /dev/root 1.9M 1.9M 0 100% / /dev/mtdblock4 3.0M...
(转)什么是DockerCompose?它有什么作用?
一、什么是DockerCompose? DockerCompose可以基于Compose文件帮我们快速的部署分布式应用,而无需手动一个个创建和运行容器。 Compose文件是一个文本文件,通过指令定义集群中的每个容器如何运行。 DockerCompose就是把DockerFile转换成指令去运行。 …...
鸿蒙DevEco Studio HarmonyOS 5跑酷小游戏实现指南
1. 项目概述 本跑酷小游戏基于鸿蒙HarmonyOS 5开发,使用DevEco Studio作为开发工具,采用Java语言实现,包含角色控制、障碍物生成和分数计算系统。 2. 项目结构 /src/main/java/com/example/runner/├── MainAbilitySlice.java // 主界…...
【Go语言基础【12】】指针:声明、取地址、解引用
文章目录 零、概述:指针 vs. 引用(类比其他语言)一、指针基础概念二、指针声明与初始化三、指针操作符1. &:取地址(拿到内存地址)2. *:解引用(拿到值) 四、空指针&am…...
Golang——6、指针和结构体
指针和结构体 1、指针1.1、指针地址和指针类型1.2、指针取值1.3、new和make 2、结构体2.1、type关键字的使用2.2、结构体的定义和初始化2.3、结构体方法和接收者2.4、给任意类型添加方法2.5、结构体的匿名字段2.6、嵌套结构体2.7、嵌套匿名结构体2.8、结构体的继承 3、结构体与…...
