当前位置: 首页 > 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;只是增…...

8k长序列建模,蛋白质语言模型Prot42仅利用目标蛋白序列即可生成高亲和力结合剂

蛋白质结合剂&#xff08;如抗体、抑制肽&#xff09;在疾病诊断、成像分析及靶向药物递送等关键场景中发挥着不可替代的作用。传统上&#xff0c;高特异性蛋白质结合剂的开发高度依赖噬菌体展示、定向进化等实验技术&#xff0c;但这类方法普遍面临资源消耗巨大、研发周期冗长…...

解决Ubuntu22.04 VMware失败的问题 ubuntu入门之二十八

现象1 打开VMware失败 Ubuntu升级之后打开VMware上报需要安装vmmon和vmnet&#xff0c;点击确认后如下提示 最终上报fail 解决方法 内核升级导致&#xff0c;需要在新内核下重新下载编译安装 查看版本 $ vmware -v VMware Workstation 17.5.1 build-23298084$ lsb_release…...

如何将联系人从 iPhone 转移到 Android

从 iPhone 换到 Android 手机时&#xff0c;你可能需要保留重要的数据&#xff0c;例如通讯录。好在&#xff0c;将通讯录从 iPhone 转移到 Android 手机非常简单&#xff0c;你可以从本文中学习 6 种可靠的方法&#xff0c;确保随时保持连接&#xff0c;不错过任何信息。 第 1…...

【服务器压力测试】本地PC电脑作为服务器运行时出现卡顿和资源紧张(Windows/Linux)

要让本地PC电脑作为服务器运行时出现卡顿和资源紧张的情况&#xff0c;可以通过以下几种方式模拟或触发&#xff1a; 1. 增加CPU负载 运行大量计算密集型任务&#xff0c;例如&#xff1a; 使用多线程循环执行复杂计算&#xff08;如数学运算、加密解密等&#xff09;。运行图…...

三体问题详解

从物理学角度&#xff0c;三体问题之所以不稳定&#xff0c;是因为三个天体在万有引力作用下相互作用&#xff0c;形成一个非线性耦合系统。我们可以从牛顿经典力学出发&#xff0c;列出具体的运动方程&#xff0c;并说明为何这个系统本质上是混沌的&#xff0c;无法得到一般解…...

Java入门学习详细版(一)

大家好&#xff0c;Java 学习是一个系统学习的过程&#xff0c;核心原则就是“理论 实践 坚持”&#xff0c;并且需循序渐进&#xff0c;不可过于着急&#xff0c;本篇文章推出的这份详细入门学习资料将带大家从零基础开始&#xff0c;逐步掌握 Java 的核心概念和编程技能。 …...

OpenPrompt 和直接对提示词的嵌入向量进行训练有什么区别

OpenPrompt 和直接对提示词的嵌入向量进行训练有什么区别 直接训练提示词嵌入向量的核心区别 您提到的代码: prompt_embedding = initial_embedding.clone().requires_grad_(True) optimizer = torch.optim.Adam([prompt_embedding...

均衡后的SNRSINR

本文主要摘自参考文献中的前两篇&#xff0c;相关文献中经常会出现MIMO检测后的SINR不过一直没有找到相关数学推到过程&#xff0c;其中文献[1]中给出了相关原理在此仅做记录。 1. 系统模型 复信道模型 n t n_t nt​ 根发送天线&#xff0c; n r n_r nr​ 根接收天线的 MIMO 系…...

Neko虚拟浏览器远程协作方案:Docker+内网穿透技术部署实践

前言&#xff1a;本文将向开发者介绍一款创新性协作工具——Neko虚拟浏览器。在数字化协作场景中&#xff0c;跨地域的团队常需面对实时共享屏幕、协同编辑文档等需求。通过本指南&#xff0c;你将掌握在Ubuntu系统中使用容器化技术部署该工具的具体方案&#xff0c;并结合内网…...

篇章二 论坛系统——系统设计

目录 2.系统设计 2.1 技术选型 2.2 设计数据库结构 2.2.1 数据库实体 1. 数据库设计 1.1 数据库名: forum db 1.2 表的设计 1.3 编写SQL 2.系统设计 2.1 技术选型 2.2 设计数据库结构 2.2.1 数据库实体 通过需求分析获得概念类并结合业务实现过程中的技术需要&#x…...