不使用implements关键字实现实现类(类似于mapper)

news2024/11/17 9:33:46

首先,说明一下功能需求,平时定义一个接口,就要使用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 {
    @Override
    public 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;
    }

    @Override
    protected 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() {
            @Override
            public 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;
    }

    @Override
    public Object getObject() throws Exception {
        Object object = targetImplClassName.newInstance();
        BeanScanInvocationHandler beanScanInvocationHandler = new BeanScanInvocationHandler(object,targetClass);
        return  Proxy.newProxyInstance(this.getClass().getClassLoader(), new Class[]{targetClass}, beanScanInvocationHandler);
    }

    @Override
    public 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);
    }

    @Override
    public 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调用,直接注入接口调用即可;代码很简单就不在写出来了。 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/375838.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

知识类问答数据集资源对外开放:百万级百度知道、社区问答及六大领域级小规模语料概述

随着chatgpt的火热&#xff0c;中文知识类问答数据集由收到诸多关注&#xff0c;其作为高质量的QA数据&#xff0c;可以用于SFT阶段以及pretrain预训练阶段。 本文主要介绍目前开源可下载的两个较大规模的知识类数据集&#xff0c;包括147万百度知道知识类数据集、425万社区问…

IOS逆向前期环境准备笔记

ios系统由于效验问题&#xff0c;只能升级不能降级&#xff0c;需要特别注意&#xff0c; 刷系统可以在爱思上搞定&#xff1b; 越狱推荐使用u盘镜像及本地启动盘制作&#xff1a; 注意&#xff0c;要进去bios,关闭安全启动&#xff0c;不然直接失败&#xff1a; Checkra1n镜…

linux升级gcc版本详细教程

0.前言一般linux操作系统默认的gcc版本都比较低&#xff0c;例如centos7系统默认的gcc版本为4.8.5。gcc是从4.7版本开始支持C11的&#xff0c;4.8版本对C11新特性的编译支持还不够完善&#xff0c;因此如果需要更好的体验C11以及以上版本的新特性&#xff0c;需要升级gcc到一个…

九、Vben之可拖拽穿梭框和水印背景如何添加

近期在开发的过程中遇到了两个难题&#xff0c;一个是目前的穿梭框不支持产品的要求&#xff0c;不能够上下拖拽&#xff0c;二是vben没有水印的api&#xff0c;需要我们自己来开发。 一、可拖拽穿梭框 做成功的效果如下&#xff1a; 思路&#xff1a; 将table放入transfer的…

操作系统权限提升(十七)之绕过UAC提权-Windows令牌概述和令牌窃取攻击

系列文章 操作系统权限提升(十二)之绕过UAC提权-Windows UAC概述 操作系统权限提升(十三)之绕过UAC提权-MSF和CS绕过UAC提权 操作系统权限提升(十四)之绕过UAC提权-基于白名单AutoElevate绕过UAC提权 操作系统权限提升(十五)之绕过UAC提权-基于白名单DLL劫持绕过UAC提权 操作系…

android EditText设置后缀

有两种实现方案。 方案一&#xff1a;是自己写一个TextWatcher。 方案二&#xff1a;是重写TextView的getOffsetForPosition方法&#xff0c;返回一个计算好的offset。 我在工作时&#xff0c;使用的是方案一。在离职之后&#xff0c;我还是对这个问题耿耿于怀&#xff0c;所以…

git在工作中的正常使用

开发A和B功能后进行发版。。 一、拉取代码 git clone http://ntc.ntsvars.com:8090/lvweijie/test.git二、开发功能A任务 创建A任务本地分支 #创建A分支&#xff0c;并切换A分支 git checkout -b A三、开发A任务 四、提交A功能文件到本地分支 git add .五、添加提交A功能备…

Tina_Linux打包流程说明指南_new

OpenRemoved_Tina_Linux_打包流程_说明指南_new 1 概述 1.1 编写目的 介绍Allwinner 平台上打包流程。 1.2 适用范围 Allwinner 软件平台Tina v3.0 版本以上。 1.3 相关人员 适用Tina 平台的广大客户&#xff0c;想了解Tina 打包流程的开发人员。 2 固件打包简介 固件…

Jenkins+Gitlab实现代码自动构建部署

一、环境准备 主机名ip安装软件jenkins192.168.75.149jenkinsgitlab192.168.75.147gitlabweb192.168.75.155部署应用 二、jenkins服务器配置 1、生产公钥 [rootjenkins ~]# ssh-keygen &#xff08;2&#xff09;获取公钥信息 公钥信息在配置 Gitlab SSH Keys 时用到。 &am…

Tcpdump抓包验证zookeeper的心跳机制

一、背景 在分布式系统中&#xff0c;zookeeper可以作为服务注册中心&#xff0c;所有提供服务的节点都可以在zookeeper上面注册&#xff0c;并作为一个node被组织起来&#xff0c;如下图&#xff1a; 在RPC框架中&#xff0c;这些服务提供者就是RPC服务的提供者。zookeeper注…

【测试】Python手机自动化测试库uiautomator2和weditor的详细使用

1.说明 我们之前在电脑操作手机进行自动化测试&#xff0c;基本上都是通过Appium的&#xff0c;这个工具确实强大&#xff0c;搭配谷歌官方的UiAutomator基本上可以完成各种测试&#xff0c;但缺点也很明显&#xff0c;配置环境太麻烦了&#xff0c;需要jdk、sdk等&#xff0c…

利用较新版本的IDEA 2022.3.2 创建Java Web的maven项目

1.创建项目 正常三步走&#xff0c;没什么可说的 2.用模板创建项目&#xff08;重要&#xff09; 第一步&#xff0c;一定要选Jakarta EE。这个模板是基于JavaWeb的一个标准模板&#xff0c;如果选了maven中的JavaWeb模板&#xff0c;那就变成了web目录在根目录下&#xff0c;…

测试跟踪模块UX交互升级,多个X-Pack功能开放至开源版,MeterSphere开源持续测试平台v2.7.0发布

2023年2月24日&#xff0c;MeterSphere一站式开源持续测试平台正式发布v2.7.0版本。 在这一版本中&#xff0c;MeterSphere在测试跟踪模块进行了UX交互升级&#xff0c;整个页面采用轻量化设计进行整体降噪&#xff0c;页面信息更加清晰易懂&#xff0c;操作流程更顺畅&#x…

【学习笔记】深入理解JVM之类加载机制

【学习笔记】深入理解JVM之类加载机制 以后基本上都在语雀上面更新&#xff0c;大家有兴趣可以看看嗷&#xff01; 首发地址&#xff1a; 知识库 文章流程图&#xff1a; 1、概述 首先我们先来看看一个 Class 文件所需要经过的一个流程图&#xff1a; 而我们今天要重点需讲的…

如何保护阿里云、政采云等云市场三方账号安全?

什么是云市场&#xff1f;根据百度百科释义&#xff0c;云市场是指物联网中分布在不同地点的海量的商品生产者和消费者之间各种经济关系的集合体&#xff0c;是通过相对集中的云平台资源联合物联网各个感知节点信息资源的方式&#xff0c;以运行分布在不同地点的海量的经济交换…

Java——数组

目录 前言 一、数组的定义 二、数组声明和创建 三、三种初始化及内存分析 Java内存分析 三种初始化 静态初始化 动态初始化 数组的默认初始化 数组的四个基本特点 四、下标越界及小结 五、数组的使用 For-Each循环 数组作方法入参 数组作返回值 六、二维数组 七…

项目管理工具dhtmlxGantt甘特图入门教程(十四):导出/导入 Excel到 iCal

这篇文章给大家讲解利用dhtmlxgantt导入/导出Excel到iCal的操作方法。 dhtmlxGantt是用于跨浏览器和跨平台应用程序的功能齐全的Gantt图表&#xff0c;可满足应用程序的所有需求&#xff0c;是完善的甘特图图表库 DhtmlxGantt正版试用下载&#xff08;qun&#xff1b;765665…

中国跨境平台出海,产业带依然是最大优势

对外贸工厂来说&#xff0c;借助跨境电商服务平台开拓海外市场可行吗&#xff1f;2023年2月11日&#xff0c;在郑州荥阳举办的Starday线下招商会联合线上直播荥阳站上&#xff0c;这是很多现场参会的企业负责人面对大屏上的招商介绍宣传时&#xff0c;大脑飞速思考的问题。2023…

android kotlin 协程(六) 源码浅析

android kotlin 协程(六) 源码浅析 前言: kotlin协程源码十分庞大, 本篇只能吧我理解的源码聊一聊,不会特别深入研究,只会浅浅的看看表层. 本来计划协程系列是10篇左右,后续是flow热流冷流之类的, 冷流操作符之类的应该不会在写了, flow当作Rxjava来用就可以,后续可能还会写一…

Bitlocker加密,与解除加密

引文&#xff1a;应为C盘空间不够用了&#xff0c;想着用U盘从新给C盘分下区。操作时才发现我系统里的磁盘都是Bitlocker加密的&#xff0c;分区工具操作不了磁盘&#xff0c;所以就找到一下方法来解决。1&#xff0c;先讲一下解除加密&#xff1a;直接点击 &#xff1a;设置-&…