模仿Spring注入接口的代理类全过程

前言

在使用mybatis或者openFeign时只定义了一个接口类,并无实现类,可以把接口注入到service中并且能调用方法返回值。一个接口并无实现类,为什么可以实例化并且交给了spring管理。mybatis,OpenFeign又是怎么实现的?接下来给大家一一揭晓

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();
    }

}

2、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));
        }

    }

}

3、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);
        });
    }
}

4、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);
        });
    }

5、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;
    }
}

6、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;
    }
}

7、测试

增加接口类,并且没有任何类实现此接口

 Controller里面注入接口,调用Controller接口方法,日志是代理类打印出来

 

  • 1
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

爱敲代码的小松

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值