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


    前言

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

    1.先自定义注解

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

    1. @Retention(RetentionPolicy.RUNTIME)
    2. @Target({ElementType.TYPE})
    3. @Documented
    4. @Import({CkScannerRegistrar.class})
    5. public @interface CkScan {
    6. String[] value() default {};
    7. String[] basePackages() default {};
    8. }
    1. @org.springframework.boot.autoconfigure.SpringBootApplication
    2. @MapperScan("com.ck.datacenter.**.dao")
    3. @CkScan("com.ck.datacenter.itf")
    4. @EnableOpenApi
    5. public class SpringBootApplication {
    6. public static void main(String[] args) {
    7. SpringApplication application = new SpringApplication(SpringBootApplication.class);
    8. application.run();
    9. }
    10. }

    2、CkScannerRegistrar类实现

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

    1. public class CkScannerRegistrar implements ImportBeanDefinitionRegistrar {
    2. @Override
    3. public void registerBeanDefinitions(AnnotationMetadata annotationMetadata, BeanDefinitionRegistry beanDefinitionRegistry) {
    4. // 获取SpringBootApplication自定义注解CkScan值
    5. AnnotationAttributes attrs = AnnotationAttributes.fromMap(annotationMetadata.getAnnotationAttributes(CkScan.class.getName()));
    6. if (attrs != null) {
    7. List<String> basePackages = new ArrayList<>();
    8. basePackages.addAll(Arrays.stream(attrs.getStringArray("value")).filter(StringUtils::hasText).collect(Collectors.toList()));
    9. basePackages.addAll(Arrays.stream(attrs.getStringArray("basePackages")).filter(StringUtils::hasText).collect(Collectors.toList()));
    10. //将接口转换为BeanDefinition对象放入spring中
    11. //CkClassPathScanner为自定义扫描类
    12. CkClassPathScanner classPathScanner = new CkClassPathScanner(beanDefinitionRegistry);
    13. classPathScanner.doScan(StringUtils.collectionToCommaDelimitedString(basePackages));
    14. }
    15. }
    16. }

    3、CkClassPathScanner实现

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

    1. public class CkClassPathScanner extends ClassPathBeanDefinitionScanner {
    2. public CkClassPathScanner(BeanDefinitionRegistry registry) {
    3. super(registry, false);
    4. }
    5. @Override
    6. protected boolean isCandidateComponent(AnnotatedBeanDefinition beanDefinition) {
    7. // 增加过滤,为接口类,并且接口上包含CkInterfaceAnnotation注解
    8. return beanDefinition.getMetadata().isInterface() &&
    9. beanDefinition.getMetadata().isIndependent() &&
    10. beanDefinition.getMetadata().hasAnnotation(CkInterfaceAnnotation.class.getName());
    11. }
    12. @Override
    13. protected boolean checkCandidate(String beanName, BeanDefinition beanDefinition) {
    14. if (super.checkCandidate(beanName, beanDefinition)) {
    15. return true;
    16. } else {
    17. System.out.println("Skipping MapperFactoryBean with name '" + beanName + "' and '" + beanDefinition.getBeanClassName() + "' mapperInterface. Bean already defined with the same name!");
    18. return false;
    19. }
    20. }
    21. @Override
    22. public Set<BeanDefinitionHolder> doScan(String... basePackages) {
    23. // spring默认不会扫描接口,此处设置为true,不做过滤
    24. this.addIncludeFilter((metadataReader, metadataReaderFactory) -> true);
    25. Set<BeanDefinitionHolder> beanDefinitions = super.doScan(basePackages);
    26. if (beanDefinitions.isEmpty()) {
    27. System.out.println("未扫描到有CkInterfaceAnnotation注解接口");
    28. } else {
    29. this.processBeanDefinitions(beanDefinitions);
    30. }
    31. return beanDefinitions;
    32. }
    33. private void processBeanDefinitions(Set<BeanDefinitionHolder> beanDefinitions) {
    34. // 此段作用,将所有带CkInterfaceAnnotation注解接口,定义成beanDefinition对象
    35. // beanDefinitions中的bean对象指向接口的代理类
    36. // 在使用@Autowired注解注入接口时,其实注入的是接口代理对象
    37. beanDefinitions.forEach((BeanDefinitionHolder holder) -> {
    38. GenericBeanDefinition definition = (GenericBeanDefinition) holder.getBeanDefinition();
    39. String beanClassName = definition.getBeanClassName();
    40. System.out.println("接口名称" + beanClassName);
    41. // 设置CkFactoryBean构造方法参数
    42. definition.getConstructorArgumentValues().addGenericArgumentValue(beanClassName);
    43. definition.setBeanClass(CkFactoryBean.class);
    44. definition.setAutowireMode(GenericBeanDefinition.AUTOWIRE_BY_TYPE);
    45. });
    46. }
    47. }

    4、processBeanDefinitions方法实现

    直接看注释理解

    1. private void processBeanDefinitions(Set<BeanDefinitionHolder> beanDefinitions) {
    2. // 可以理解为,将所有接口类转换为beanDefinition对象,
    3. // beanName为接口名称,bean对应实际实例化对象需要从CkFactoryBean对象对应的getObject获取
    4. // 在使用@Autowired注解注入接口时,其实注入的是接口代理对象,即CkFactoryBean类中getObject方法获取对象
    5. beanDefinitions.forEach((BeanDefinitionHolder holder) -> {
    6. GenericBeanDefinition definition = (GenericBeanDefinition) holder.getBeanDefinition();
    7. String beanClassName = definition.getBeanClassName();
    8. System.out.println("接口名称" + beanClassName);
    9. // 实例化CkFactoryBean类时构造方法传的参数
    10. definition.getConstructorArgumentValues().addGenericArgumentValue(beanClassName);
    11. definition.setBeanClass(CkFactoryBean.class);
    12. definition.setAutowireMode(GenericBeanDefinition.AUTOWIRE_BY_TYPE);
    13. });
    14. }

    5、CkFactoryBean类实现

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

    1. public class CkFactoryBean<T> implements FactoryBean<T> {
    2. private Class<T> ckInterface;
    3. public CkFactoryBean() {
    4. }
    5. public CkFactoryBean(Class<T> ckInterface) {
    6. this.ckInterface = ckInterface;
    7. }
    8. /**
    9. * bean实例化对象,指向代理类即可
    10. */
    11. @Override
    12. public T getObject() throws Exception {
    13. // 返回CkInterfaceProxy代理对象
    14. return (T) Proxy.newProxyInstance(ckInterface.getClassLoader(),
    15. new Class[]{ckInterface},
    16. new CkInterfaceProxy<>(ckInterface));
    17. }
    18. /**
    19. * bean对象类型
    20. */
    21. @Override
    22. public Class<T> getObjectType() {
    23. return this.ckInterface;
    24. }
    25. @Override
    26. public boolean isSingleton() {
    27. return true;
    28. }
    29. }

    6、CkInterfaceProxy代理类的实现

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

    1. public class CkInterfaceProxy<T> implements InvocationHandler, Serializable {
    2. private final Class<T> ckInterface;
    3. public CkInterfaceProxy(Class<T> ckInterface) {
    4. this.ckInterface = ckInterface;
    5. }
    6. @Override
    7. public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    8. if (this.ckInterface.isAnnotationPresent(CkInterfaceAnnotation.class)) {
    9. // 读取类上注解
    10. CkInterfaceAnnotation interfaceAnnotation = this.ckInterface.getAnnotation(CkInterfaceAnnotation.class);
    11. System.out.println("调用接口类名:" + interfaceAnnotation.value());
    12. if (method.isAnnotationPresent(CkMethodAnnotation.class)) {
    13. // 读取方法上注解
    14. CkMethodAnnotation methodAnnotation = method.getAnnotation(CkMethodAnnotation.class);
    15. System.out.println("调用接口方法名:" + methodAnnotation.value());
    16. }
    17. }
    18. return null;
    19. }
    20. }

    7、测试

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

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

     

  • 相关阅读:
    Hadoop(MapReduce)
    flink1.18.0 自定义函数 接收row类型的参数
    医院住院管理系统(Java+Web+JSP+MySQL)
    mac的vscode配置vue项目环境
    持续进阶,软通动力稳步推动云智能战略
    R语言的物种气候生态位动态量化与分布特征模拟实践技术
    2022面试相关- reactnatvie相关
    gitlab 合并分支
    开源驰骋低代码-积极拥抱AI时代
    SSM+校园好货APP的设计与实现 毕业设计-附源码121619
  • 原文地址:https://blog.csdn.net/qq_41625866/article/details/128174570