Spring boot--自定义类扫描器

Spring boot--自定义类扫描器

  • 前言
  • ClassPathBeanDefinitionScanner简介
  • ImportBeanDefinitionRegistrar简介
  • 自定义注册过程
    • 自定义注解
    • 实现ImportBeanDefinitionRegistrar
    • 自定义类扫
    • 使用
  • 总结

前言

之前一直在写RPC框架是基于Spring的框架。通过扫描接口并将其注入代理工厂的方式是采用了Spring 自定义标签的方式。最近一直在玩Spring cloud,所以也希望自己的RPC框架能够直接在Spring boot上面运行。所以将其修改了一下。其实改成基于Spring boot框架的RPC主要是要修改两个地方。第一个地方是将在Spring 配置文件中的< bean >改成基于Java 的配置。
第二个就是自定义的类扫描器。

ClassPathBeanDefinitionScanner简介

用过Mybatis的小伙伴应该知道我们扫描包是在启动类上面加一个@MapperScan(“com.xxx.xxx.dao”)这种方式。
其实Mybatis 的Mapper注册器(ClassPathMapperScanner) 是同过继承ClassPathBeanDefinitionScanner,并且自定义了过滤器规则来实现的。那我只要仿造Mybatis的方式自己写一个就行

ImportBeanDefinitionRegistrar简介

spring官方就是用这种方式,实现了@Component、@Service等注解的动态注入机制。定义一个ImportBeanDefinitionRegistrar的实现类,然后在有@Configuration注解的配置类上使用@Import导入

自定义注册过程

我们需要定义3个个东西

  1. 第一个是自定义注解 用于启动类
  2. 第二个是一个实现ImportBeanDefinitionRegistrar的类 用于自定义注解通过@Import
  3. 第三个就是自定义类扫描器 实现我们对于接口的自定义注入

自定义注解

package com.ljl.rpc.annotation;

import com.ljl.rpc.handle.ClientPackageScan;
import org.springframework.context.annotation.Import;

import java.lang.annotation.*;

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Import(ClientPackageScan.class)
@Documented
/*
* 标注哪个包作为RPC消费接口 自定义注入
* */
public @interface RPCClientPackage {
    String[] basePackage() default {};
}

实现ImportBeanDefinitionRegistrar

package com.ljl.rpc.handle;


import com.ljl.rpc.annotation.RPCClientPackage;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.context.ResourceLoaderAware;
import org.springframework.context.annotation.ImportBeanDefinitionRegistrar;
import org.springframework.core.annotation.AnnotationAttributes;
import org.springframework.core.io.ResourceLoader;
import org.springframework.core.type.AnnotationMetadata;

/**
 * Created by Administrator on 2019/9/10 0010.
 */
public class ClientPackageScan  implements ImportBeanDefinitionRegistrar, ResourceLoaderAware {
    ResourceLoader resourceLoader;

    @Override
    public void setResourceLoader(ResourceLoader resourceLoader) {
        this.resourceLoader = resourceLoader;
    }

    @Override
    public void registerBeanDefinitions(AnnotationMetadata annotationMetadata, BeanDefinitionRegistry beanDefinitionRegistry) {
        AnnotationAttributes annoAttrs = AnnotationAttributes.fromMap(annotationMetadata.getAnnotationAttributes(RPCClientPackage.class.getName()));
        String[] basePackages = annoAttrs.getStringArray("basePackage");
        //自定义的 包扫描器
        ClientPackageScanHandle scanHandle = new ClientPackageScanHandle(beanDefinitionRegistry,false);
        //扫描指定路径下的接口
        scanHandle.doScan(basePackages);
    }
}



自定义类扫

这里添加工程bean是和原来的思路一样的

package com.ljl.rpc.handle;


import com.ljl.rpc.ProxyFactory.MethodProxyFactory;
import com.ljl.rpc.annotation.RPCClient;
import org.springframework.beans.factory.annotation.AnnotatedBeanDefinition;
import org.springframework.beans.factory.config.BeanDefinitionHolder;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.beans.factory.support.GenericBeanDefinition;
import org.springframework.context.annotation.ClassPathBeanDefinitionScanner;
import org.springframework.core.type.filter.AnnotationTypeFilter;

import java.util.Set;

/**
 * Created by Administrator on 2019/9/10 0010.
 *
 */
public class ClientPackageScanHandle extends ClassPathBeanDefinitionScanner {

    public ClientPackageScanHandle(BeanDefinitionRegistry registry, boolean useDefaultFilters) {
        super(registry, useDefaultFilters);
    }


    @Override
    protected Set<BeanDefinitionHolder> doScan(String... basePackages) {
        //添加过滤条件
        addIncludeFilter(new AnnotationTypeFilter(RPCClient.class));
        //调用spring的扫描
        Set<BeanDefinitionHolder> beanDefinitionHolders = super.doScan(basePackages);
        if(beanDefinitionHolders.size() != 0){
            //给扫描出来的接口添加上代理对象
            processBeanDefinitions(beanDefinitionHolders);
        }
        return beanDefinitionHolders;
    }


    /**
     * 给扫描出来的接口添加上代理对象
     * @param beanDefinitions
     */
    private void processBeanDefinitions(Set<BeanDefinitionHolder> beanDefinitions) {
        GenericBeanDefinition definition;
        for (BeanDefinitionHolder holder : beanDefinitions) {
            definition = (GenericBeanDefinition) holder.getBeanDefinition();
            //拿到接口的全路径名称
            String beanClassName = definition.getBeanClassName();
            //设置属性 即所对应的消费接口
            try {
                definition.getPropertyValues().add("interfaceClass", Class.forName(beanClassName));
                //设置Calss 即代理工厂
                definition.setBeanClass(MethodProxyFactory.class);
                //按 照查找Bean的Class的类型
                definition.setAutowireMode(GenericBeanDefinition.AUTOWIRE_BY_TYPE);
            } catch (ClassNotFoundException e) {
                e.printStackTrace();
            }
        }
    }

    @Override
    protected boolean isCandidateComponent(AnnotatedBeanDefinition beanDefinition) {
        return beanDefinition.getMetadata().isInterface() && beanDefinition.getMetadata().isIndependent();
    }

}

使用

@SpringBootApplication
@RPCClientPackage(basePackage = "com.ljl.server.service")
public class LjlRpcServerApplication {

	public static void main(String[] args) {
		SpringApplication.run(LjlRpcServerApplication.class, args);
	}

}

总结

我们可以发现Spring boot 真的很厉害,化繁为简。把许许多多的配置文件都省下来了,代码也美观了很多。
我们可以迁移之Spring boot 之后我们只需要配置这一点东西 加上几个注解就能完成一个第三方框架的引入。

zookeeper:
  hosts: 127.0.0.1:2181
  baseackage : com.ljl.server.service.Impl

netty:
  port: 8885

你可能感兴趣的:(RPC)