当前位置: 首页 > news >正文

不使用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)

首先&#xff0c;说明一下功能需求&#xff0c;平时定义一个接口&#xff0c;就要使用implements关键字来实现接口。那么&#xff0c;当不使用此关键字的时候&#xff0c;是否也能使相关接口也能够绑定实现类呢&#xff1f; 答案是肯定的。 此篇文章的主要功能有两个&#xf…...

antd4里table的滚动是如何实现的?

rc-table里Header、Footer、TableBody实现保持同频滚动的方法 场景&#xff1a;Header、Footer都有&#xff0c;Table设置了scrollX&#xff0c;才关注同频滚动 那么是如何实现的&#xff1f; 监听onScroll方法获取到滚动条向左的滚动的距离scrollLeft&#xff1b;同时给三个…...

抓取namenode 50070 jmx的指标信息

在生产实践过程中&#xff0c;需要把data退役之后需要停机下线&#xff0c;在下线之前需要确认机器是否已下线完成&#xff0c;要去namenode的50070界面上查看显然效率低&#xff0c;为了能够快速拿到节点信息&#xff0c;写了简单的脚本。jmx/50070还有很多信息可以获取&#…...

aspnetcore-browser-refresh.js和Visual Studio Browser Link

我在调试ASP.NET Core web应用时&#xff0c;发现请求的页面文档底部多了一部分文件&#xff0c;而在我的页面中却没有包含&#xff0c;故查询资料&#xff0c;在此记录&#xff1a; 图中&#xff0c;可以看到红框部分是多出来了2个脚本 1.aspnetcore-browser-refresh.js 这里…...

hadoop 集群常用命令(学习笔记) —— 筑梦之路

概念介绍 #HDFS 概述Hadoop Distributed File System&#xff0c;简称HDFS&#xff0c;是一个分布式文件系统。&#xff08;1&#xff09;NameNode&#xff08;nn&#xff09;&#xff1a;存储文件的元数据&#xff0c;如文件名&#xff0c;文件目录结构&#xff0c;文件属性&…...

ARC142D Deterministic Placing

ARC142D Deterministic Placing 题目大意 有一棵nnn个顶点的树&#xff0c;每个点上最多放一张卡片&#xff0c;你可以做如下操作&#xff1a; 同时将所有的卡片移到它所在顶点的相邻的一个顶点上 一个操作我们说它是好的&#xff0c;当下列条件满足&#xff1a; 每条边最…...

阶段八:服务框架高级(第二章:分布式事务)

阶段八&#xff1a;服务框架高级&#xff08;第二章&#xff1a;分布式事务&#xff09;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&#xff0c;更好使用RPC&#xff0c;须从RPC框架整体性能考虑问题。得知道如何提升RPC框架的性能、稳定性、安全性、吞吐量及如何在分布式下快速定位问题。RPC框架如何压榨单机吞吐量&#xff1f; 1 前言 TPS一直上不去&#xff0c;压测时CPU压到40%&#xff5e;50%就…...

C# 多窗口切换的实现

1、目的在主窗口中根据不同的按钮选择不同的子窗口显示。2、实现&#xff08;1&#xff09;、创建Winform窗体程序&#xff0c;放入SplitContainer控件splitContainer1将窗体分成左右2部分&#xff1b;&#xff08;2&#xff09;、在左侧splitContainer1.panel1中放入3个Button…...

【深度学习】RNN

1. 什么是RNN 循环神经网络&#xff08;Recurrent Neural Network, RNN&#xff09;是一类以序列&#xff08;sequence&#xff09;数据为输入&#xff0c;在序列的演进方向进行递归&#xff08;recursion&#xff09;且所有节点&#xff08;循环单元&#xff09;按链式连接的递…...

招聘岗位,机会难得

岗位需求 费话不多说&#xff0c;直接上JD&#xff1a; 嵌入式开发工程师&#xff1a; 17:411.计算机、通信等相关专业。 2.熟悉网络基础知识&#xff0c;熟悉802.11a/b/g/n/ac协议&#xff0c;能通过抓包等分析手段排查定位各种wifi相关问题。 3.熟悉路由器主要功能及实现原…...

web打印的几种方法(2023)

在工作中出现web打印的情况是非常多的&#xff0c;其实这也是一个比较烦人的问题&#xff0c;这篇博客整理一下关于Web打印的一些方法或者方式。 1. window.print() 这个方法是用来打印网页的&#xff0c;页面上的其他的元素也会被打印处理&#xff0c;在打印的时候页眉页脚是…...

代码随想录算法训练营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 实用插件推荐 背景&#xff1a;电脑重装了&#xff0c;重新下载了最新版的IntelliJ IDEA&#xff0c;感觉默认模式有点枯燥&#xff0c;于是决定从网上下载一些实用美观的插件优化自己以后吃饭的工具&#xff0c;现在推荐的都是目前还能用的&#xff08;亲身实践…...

WideDeep模型

google提出的Wide&deep模型&#xff0c;将线性模型与DNN很好的结合起来&#xff0c;在提高模型泛化能力的同时&#xff0c;兼顾模型的记忆性。wide&deep这种将线性模型与DNN的并行连接模式&#xff0c;后来称为推荐领域的经典模式&#xff0c;奠定了后面深度学习模型的…...

nacos集群模式+keepalived搭建高可用服务

实际工作中如果nacos这样的核心服务停掉了或者整个服务器宕机了&#xff0c;那整个系统也就gg了&#xff0c;所以像这样的核心服务我们必须要搞个3个或者3个以上的nacos集群部署&#xff0c;实现高可用&#xff1b; 部署高可用版本之前&#xff0c;首先你要会部署单机版的naco…...

吉利「银河」负重突围

吉利控股集团最新公布的数据显示&#xff0c;2022年&#xff0c;吉利控股集团汽车总销量超230万辆&#xff0c;同比增长4.3%。其中&#xff0c;新能源汽车销量超64万辆&#xff0c;同比增长100.3%。 在中国本土市场&#xff0c;2022年吉利集团旗下品牌乘用车总交付量为135.84万…...

QT之图形视图框架概述——Graphics View Framework

QT之图形视图框架概述——Graphics View Framework1. 概述2. 核心类3. 事件传递4. Graphics View 坐标系统5. 参考1. 概述 Graphics View Framework是子Qt 4.2引入的&#xff0c;用来取代之前版本中的QCanvas。Graphics View Framework提拱了用于大量2D图形项的管理和交互的能…...

【SQL开发实战技巧】系列(二十二):数仓报表场景(上) 从分析函数效率一定快吗聊一聊结果集分页和隔行抽样实现方式

系列文章目录 【SQL开发实战技巧】系列&#xff08;一&#xff09;:关于SQL不得不说的那些事 【SQL开发实战技巧】系列&#xff08;二&#xff09;&#xff1a;简单单表查询 【SQL开发实战技巧】系列&#xff08;三&#xff09;&#xff1a;SQL排序的那些事 【SQL开发实战技巧…...

小米无线AR眼镜探索版细节汇总

在MWC 2023期间&#xff0c;小米正式发布了一款无线AR眼镜&#xff0c;虽然还没看过实机&#xff0c;但XDA提前上手体验&#xff0c;我们从中进行总结。首先我要说的是&#xff0c;小米这款眼镜和高通无线AR眼镜参考设计高度重叠&#xff0c;产品卖点几乎一致&#xff0c;只是增…...

React 第五十五节 Router 中 useAsyncError的使用详解

前言 useAsyncError 是 React Router v6.4 引入的一个钩子&#xff0c;用于处理异步操作&#xff08;如数据加载&#xff09;中的错误。下面我将详细解释其用途并提供代码示例。 一、useAsyncError 用途 处理异步错误&#xff1a;捕获在 loader 或 action 中发生的异步错误替…...

Vue记事本应用实现教程

文章目录 1. 项目介绍2. 开发环境准备3. 设计应用界面4. 创建Vue实例和数据模型5. 实现记事本功能5.1 添加新记事项5.2 删除记事项5.3 清空所有记事 6. 添加样式7. 功能扩展&#xff1a;显示创建时间8. 功能扩展&#xff1a;记事项搜索9. 完整代码10. Vue知识点解析10.1 数据绑…...

Xshell远程连接Kali(默认 | 私钥)Note版

前言:xshell远程连接&#xff0c;私钥连接和常规默认连接 任务一 开启ssh服务 service ssh status //查看ssh服务状态 service ssh start //开启ssh服务 update-rc.d ssh enable //开启自启动ssh服务 任务二 修改配置文件 vi /etc/ssh/ssh_config //第一…...

Psychopy音频的使用

Psychopy音频的使用 本文主要解决以下问题&#xff1a; 指定音频引擎与设备&#xff1b;播放音频文件 本文所使用的环境&#xff1a; Python3.10 numpy2.2.6 psychopy2025.1.1 psychtoolbox3.0.19.14 一、音频配置 Psychopy文档链接为Sound - for audio playback — Psy…...

【RockeMQ】第2节|RocketMQ快速实战以及核⼼概念详解(二)

升级Dledger高可用集群 一、主从架构的不足与Dledger的定位 主从架构缺陷 数据备份依赖Slave节点&#xff0c;但无自动故障转移能力&#xff0c;Master宕机后需人工切换&#xff0c;期间消息可能无法读取。Slave仅存储数据&#xff0c;无法主动升级为Master响应请求&#xff…...

AspectJ 在 Android 中的完整使用指南

一、环境配置&#xff08;Gradle 7.0 适配&#xff09; 1. 项目级 build.gradle // 注意&#xff1a;沪江插件已停更&#xff0c;推荐官方兼容方案 buildscript {dependencies {classpath org.aspectj:aspectjtools:1.9.9.1 // AspectJ 工具} } 2. 模块级 build.gradle plu…...

代理篇12|深入理解 Vite中的Proxy接口代理配置

在前端开发中,常常会遇到 跨域请求接口 的情况。为了解决这个问题,Vite 和 Webpack 都提供了 proxy 代理功能,用于将本地开发请求转发到后端服务器。 什么是代理(proxy)? 代理是在开发过程中,前端项目通过开发服务器,将指定的请求“转发”到真实的后端服务器,从而绕…...

CSS设置元素的宽度根据其内容自动调整

width: fit-content 是 CSS 中的一个属性值&#xff0c;用于设置元素的宽度根据其内容自动调整&#xff0c;确保宽度刚好容纳内容而不会超出。 效果对比 默认情况&#xff08;width: auto&#xff09;&#xff1a; 块级元素&#xff08;如 <div>&#xff09;会占满父容器…...

CVE-2020-17519源码分析与漏洞复现(Flink 任意文件读取)

漏洞概览 漏洞名称&#xff1a;Apache Flink REST API 任意文件读取漏洞CVE编号&#xff1a;CVE-2020-17519CVSS评分&#xff1a;7.5影响版本&#xff1a;Apache Flink 1.11.0、1.11.1、1.11.2修复版本&#xff1a;≥ 1.11.3 或 ≥ 1.12.0漏洞类型&#xff1a;路径遍历&#x…...

C# 表达式和运算符(求值顺序)

求值顺序 表达式可以由许多嵌套的子表达式构成。子表达式的求值顺序可以使表达式的最终值发生 变化。 例如&#xff0c;已知表达式3*52&#xff0c;依照子表达式的求值顺序&#xff0c;有两种可能的结果&#xff0c;如图9-3所示。 如果乘法先执行&#xff0c;结果是17。如果5…...