IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> Java知识库 -> 模仿mybatis,OpenFeign实现springboot自定义扫描接口,注入代理类 -> 正文阅读

[Java知识库]模仿mybatis,OpenFeign实现springboot自定义扫描接口,注入代理类

说明

大家在使用mybatis或者openFeign时只定义了一个接口类,并无实现类,可以把接口注入到service中并且能调用方法返回值。
一个接口并无实现类,为什么可以实例化并且交给了spring管理。mybatis,OpenFeign又是怎么实现的?
看mybatis源代码理解的,删减了很多,保留一些关键初始化步骤。

直接上代码,链接(gitee上面)

1.先自定义注解,用于SpringBootApplication启动类。启动类加上CkScan注解,注解值即需要扫描那些包接口。springboot在启动时,发现注解里面Import导入CkScannerRegistrar类,会解析此类,此步就是实现入口。CkScannerRegistrar类下面会讲解

@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE})
@Documented
@Import({CkScannerRegistrar.class})
public @interface CkScan {

    String[] value() default {};

    String[] basePackages() default {};

}
@org.springframework.boot.autoconfigure.SpringBootApplication
@MapperScan("com.ck.datacenter.**.dao")
@CkScan("com.ck.datacenter.itf")
@EnableOpenApi
public class SpringBootApplication {

    public static void main(String[] args) {
        SpringApplication application = new SpringApplication(SpringBootApplication.class);
        application.run();
    }

}

CkScannerRegistrar类实现,解析此类时发现实现了spring的ImportBeanDefinitionRegistrar接口并且重新写了registerBeanDefinitions方法,会调用此方法。里面关键点new CkClassPathScanner类并且调用了doScan。

public class CkScannerRegistrar implements ImportBeanDefinitionRegistrar {

    @Override
    public void registerBeanDefinitions(AnnotationMetadata annotationMetadata, BeanDefinitionRegistry beanDefinitionRegistry) {
        // 获取SpringBootApplication自定义注解CkScan值
        AnnotationAttributes attrs = AnnotationAttributes.fromMap(annotationMetadata.getAnnotationAttributes(CkScan.class.getName()));

        if (attrs != null) {
            List<String> basePackages = new ArrayList<>();
            basePackages.addAll(Arrays.stream(attrs.getStringArray("value")).filter(StringUtils::hasText).collect(Collectors.toList()));
            basePackages.addAll(Arrays.stream(attrs.getStringArray("basePackages")).filter(StringUtils::hasText).collect(Collectors.toList()));

            //将接口转换为BeanDefinition对象放入spring中
            //CkClassPathScanner为自定义扫描类
            CkClassPathScanner classPathScanner = new CkClassPathScanner(beanDefinitionRegistry);
            classPathScanner.doScan(StringUtils.collectionToCommaDelimitedString(basePackages));
        }

    }

}

CkClassPathScanner实现,继承ClassPathBeanDefinitionScanner扫描类,重写里面doScan,上步已经调用了doScan方法,进入此方法,调用了父类super.doScan(basePackages)得到所有满足条件BeanDefinitionHolder对象(接口一个包装类)。
扫描过滤条件:doScan中的addIncludeFilter方法可以增加过滤条件,isCandidateComponent方法也可以进行条件过滤

得到所有BeanDefinitionHolder对象后,调用processBeanDefinitions进行加工处理,此方法也是关键

public class CkClassPathScanner extends ClassPathBeanDefinitionScanner {

    public CkClassPathScanner(BeanDefinitionRegistry registry) {
        super(registry, false);
    }

    @Override
    protected boolean isCandidateComponent(AnnotatedBeanDefinition beanDefinition) {
        // 增加过滤,为接口类,并且接口上包含CkInterfaceAnnotation注解
        return beanDefinition.getMetadata().isInterface() &&
                beanDefinition.getMetadata().isIndependent() &&
                beanDefinition.getMetadata().hasAnnotation(CkInterfaceAnnotation.class.getName());
    }

    @Override
    protected boolean checkCandidate(String beanName, BeanDefinition beanDefinition) {
        if (super.checkCandidate(beanName, beanDefinition)) {
            return true;
        } else {
            System.out.println("Skipping MapperFactoryBean with name '" + beanName + "' and '" + beanDefinition.getBeanClassName() + "' mapperInterface. Bean already defined with the same name!");
            return false;
        }
    }

    @Override
    public Set<BeanDefinitionHolder> doScan(String... basePackages) {
        // spring默认不会扫描接口,此处设置为true,不做过滤
        this.addIncludeFilter((metadataReader, metadataReaderFactory) -> true);

        Set<BeanDefinitionHolder> beanDefinitions = super.doScan(basePackages);
        if (beanDefinitions.isEmpty()) {
            System.out.println("未扫描到有CkInterfaceAnnotation注解接口");
        } else {
            this.processBeanDefinitions(beanDefinitions);
        }

        return beanDefinitions;
    }

    private void processBeanDefinitions(Set<BeanDefinitionHolder> beanDefinitions) {

        // 此段作用,将所有带CkInterfaceAnnotation注解接口,定义成beanDefinition对象
        // beanDefinitions中的bean对象指向接口的代理类
        // 在使用@Autowired注解注入接口时,其实注入的是接口代理对象

        beanDefinitions.forEach((BeanDefinitionHolder holder) -> {
            GenericBeanDefinition definition = (GenericBeanDefinition) holder.getBeanDefinition();
            String beanClassName = definition.getBeanClassName();
            System.out.println("接口名称" + beanClassName);

            // 设置CkFactoryBean构造方法参数
            definition.getConstructorArgumentValues().addGenericArgumentValue(beanClassName);
            definition.setBeanClass(CkFactoryBean.class);
            definition.setAutowireMode(GenericBeanDefinition.AUTOWIRE_BY_TYPE);
        });
    }
}

processBeanDefinitions方法实现,直接看注释理解

private void processBeanDefinitions(Set<BeanDefinitionHolder> beanDefinitions) {
        // 可以理解为,将所有接口类转换为beanDefinition对象,
        // beanName为接口名称,bean对应实际实例化对象需要从CkFactoryBean对象对应的getObject获取
        // 在使用@Autowired注解注入接口时,其实注入的是接口代理对象,即CkFactoryBean类中getObject方法获取对象
        beanDefinitions.forEach((BeanDefinitionHolder holder) -> {
            GenericBeanDefinition definition = (GenericBeanDefinition) holder.getBeanDefinition();
            String beanClassName = definition.getBeanClassName();
            System.out.println("接口名称" + beanClassName);

            // 实例化CkFactoryBean类时构造方法传的参数
            definition.getConstructorArgumentValues().addGenericArgumentValue(beanClassName);
            definition.setBeanClass(CkFactoryBean.class);
            definition.setAutowireMode(GenericBeanDefinition.AUTOWIRE_BY_TYPE);
        });
    }

CkFactoryBean类实现,实现spring的FactoryBean接口即可。spring在初始化bean时,会调用getObject方法获取实例,我们只需要在此方法返回接口的代理类即可。在service中调用接口的方法时,实际就会调用到我们写的CkInterfaceProxy代理类

public class CkFactoryBean<T> implements FactoryBean<T> {

    private Class<T> ckInterface;

    public CkFactoryBean() {
    }

    public CkFactoryBean(Class<T> ckInterface) {
        this.ckInterface = ckInterface;
    }

    /**
     * bean实例化对象,指向代理类即可
     */
    @Override
    public T getObject() throws Exception {
        // 返回CkInterfaceProxy代理对象
        return (T) Proxy.newProxyInstance(ckInterface.getClassLoader(),
                new Class[]{ckInterface},
                new CkInterfaceProxy<>(ckInterface));
    }

    /**
     * bean对象类型
     */
    @Override
    public Class<T> getObjectType() {
        return this.ckInterface;
    }

    @Override
    public boolean isSingleton() {
        return true;
    }
}

CkInterfaceProxy代理类的实现。比如我们使用OpenFeign,我们在此步做处理,获取类上注解和方法上的注解,通过类上注解值再从注册中心获取实际的服务器,再拼接方法上注解路径,就得到完整请求路径

public class CkInterfaceProxy<T> implements InvocationHandler, Serializable {

    private final Class<T> ckInterface;

    public CkInterfaceProxy(Class<T> ckInterface) {
        this.ckInterface = ckInterface;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        if (this.ckInterface.isAnnotationPresent(CkInterfaceAnnotation.class)) {
            // 读取类上注解
            CkInterfaceAnnotation interfaceAnnotation = this.ckInterface.getAnnotation(CkInterfaceAnnotation.class);
            System.out.println("调用接口类名:" + interfaceAnnotation.value());
            if (method.isAnnotationPresent(CkMethodAnnotation.class)) {
                // 读取方法上注解
                CkMethodAnnotation methodAnnotation = method.getAnnotation(CkMethodAnnotation.class);
                System.out.println("调用接口方法名:" + methodAnnotation.value());
            }
        }

        return null;
    }
}

测试

增加接口类,并且没有任何类实现此接口
在这里插入图片描述
Controller里面注入接口,调用Controller接口方法,日志是代理类打印出来
在这里插入图片描述
在这里插入图片描述

  Java知识库 最新文章
计算距离春节还有多长时间
系统开发系列 之WebService(spring框架+ma
springBoot+Cache(自定义有效时间配置)
SpringBoot整合mybatis实现增删改查、分页查
spring教程
SpringBoot+Vue实现美食交流网站的设计与实
虚拟机内存结构以及虚拟机中销毁和新建对象
SpringMVC---原理
小李同学: Java如何按多个字段分组
打印票据--java
上一篇文章      下一篇文章      查看所有文章
加:2021-12-26 22:00:25  更:2021-12-26 22:01:38 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/24 7:29:17-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码