Jimmy小站

小明也有大梦想 — 蒋明/铭
当前位置:网站首页 / 未分类 / 正文

当只有一个interface的时候,如何向spring容器中注入一个bean

2023-07-13 / 未分类 / 808 次围观 / 0 次吐槽

当只有一个interface的时候,如何向spring容器中注入一个bean

为什么有这样的疑问?

类似Dubbo HSF等,消费者引入了一个二方库,这个二方库中定义了一个service的interface。
明明在我的应用中压根没有这个类的实现,也没有任何代码去向spring容器去注入这个bean。我们为啥能直接从容器中@resource这个类,然后去调用它的方法?

它是怎么做到的

主要涉及到的知识点

  1. spring boot autoconfig
  2. Proxy + Invocation 动态代理
  3. Reflection 反射
  4. FactoryBean
  5. Annotation 运行时注解
  6. spring容器生命周期

实操一把

定义一个annotation

package ascp.plan.general.platform.core.proxy;


import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
 * @Description :
 * @Author : sulong
 * @Date: 2023-07-13 14:54
 */
@Target({ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface GPSAutoService {

    // 自定义在spring容器中的beanName,默认使用:GPS+interface类名
    String beanName() default "";
}

BeanDefinitionRegistryPostProcessor
这里的作用是:打有注解的字段 视作需要注入容器的类,根据GPSAutoService注解中指定的code,向spring容器中注入这个code的Beandefinition

@Component
public class GPSBeanFactoryPostProcessor implements BeanDefinitionRegistryPostProcessor {

    @Override
    public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {

    }

    @Override
    public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException {


        for (String beanDefinitionName : registry.getBeanDefinitionNames()) {
            BeanDefinition beanDefinition = registry.getBeanDefinition(beanDefinitionName);
            String beanClassName = beanDefinition.getBeanClassName();
            if (beanClassName == null) {
                continue;
            }
            Class<?> clazz = ClassUtils.resolveClassName(beanClassName, null);
            // 看类中成员有没有打GPSAutoService注解,有的话,就是一个GPSservice 执行自动注入容器动作
            ReflectionUtils.doWithFields(clazz, field -> {
                GPSAutoService annotation = AnnotationUtils.getAnnotation(field, GPSAutoService.class);
                if (annotation == null) {
                    return;
                }
                Class<?> fieldClazz = field.getType();
                GPSMetadata metadata = new GPSMetadata();
                metadata.setIfClazz(fieldClazz);
                metadata.setInterfaceName(fieldClazz.getName());
                String beanName = annotation.beanName();

                // 默认使用接口类名
                if (StringUtils.isBlank(beanName)) {
                    beanName = "GPS"+fieldClazz.getSimpleName();
                }

                BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(GPSFactoryBean.class);

                builder.addPropertyValue("metadata", metadata);
                builder.addPropertyReference("applicationContextHelper", "applicationContextHelper");

                registry.registerBeanDefinition(beanName, builder.getBeanDefinition());

            });
        }

    }
}

GPSFactoryBean
Proxy与Spring容器的桥接器。FactoryBean与普通Bean的区别是,容器中获取FactoryBean的时候,获取的是getObject方法返回的对象,而不是FactoryBean本身。

@Getter
@Setter
public class GPSFactoryBean<T> implements FactoryBean<T> {

    private GPSMetadata metadata;

    private GeneralPlatformSpiRouter<T> router = new GeneralPlatformSpiRouter<>();

    private ApplicationContextHelper applicationContextHelper;

    @Override
    public T getObject() throws Exception {

        router.setApplicationContextHelper(applicationContextHelper);
        GeneralPlatformConfigRouter generalPlatformConfigRouter = applicationContextHelper
                .getBean("generalPlatformConfigRouter", GeneralPlatformConfigRouter.class);
        router.setGeneralPlatformConfigRouter(generalPlatformConfigRouter);

        return (T) Proxy.newProxyInstance(GPSFactoryBean.class.getClassLoader(),
                new Class[] { getObjectType() }, new GPSInvocationHandler(metadata, router));
    }

    @Override
    public Class<?> getObjectType() {

        if (metadata == null) {
            return null;
        }

        if(metadata.getIfClazz() != null) {
            return metadata.getIfClazz();
        }
        String interfaceName = metadata.getInterfaceName();
        try {
            return interfaceName == null ? null : ClassUtils.forName(interfaceName);
        } catch (ClassNotFoundException ignore) {

        }
        return null;
    }
}

为什么要这样做?
回答:桥接
这样的好处是,能够在spring容器获取bean和实例化、初始化bean的过程中额外做很多骚操作,比如。。。

public class GPSInvocationHandler implements InvocationHandler {

    GPSMetadata metadata;
    GeneralPlatformSpiRouter router;

    public GPSInvocationHandler(GPSMetadata metadata, GeneralPlatformSpiRouter router) {
        this.metadata = metadata;
        this.router = router;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {

        if (method.getName().equals("equals")
                || method.getName().equals("toString")
                || method.getName().equals("hashCode")) {
            return method.invoke(Proxy.getInvocationHandler(proxy), args);
        }

        Identity identity = (Identity) args[0];
        Object target = router.routeService(identity, metadata.getIfClazz());

        return method.invoke(target, args);
    }
}
@Data
public class GPSMetadata {

    private Class<?> ifClazz;

    private String interfaceName;
}

注意事项

这里有好几个细节需要注意

  1. GPSAutoService的注解要明确,RetentionPolicy.RUNTIME表示运行时保留注解,ElementType.FIELD表示注解只能打在类中的字段上
    @Target({ElementType.FIELD})
    @Retention(RetentionPolicy.RUNTIME)

  2. 使用时要跟HSF消费者类似,增加配置类

    // 注意这里一定要是configuration,否则在GPSBeanFactoryPostProcessor类中扫描需要注入spring容器的类的时候,扫描不到这个注解。具体原因未知,应该是component和configuration内在区别导致,后续原理再分析。
    @Configuration
    public class AutoServiceTes {
    
     @GPSAutoService(beanName = "testBeanBean")
     ActionNodesChainGPService actionNodesChainGPService;
    }

原理

后续更新

推荐您阅读更多有关于“”的文章

[一个Java程序猿的转型之路,读研深造,专注机器学习推荐算法]
本站所有文章如无特别注明均为原创。作者:吉米酱 ,复制或转载请以超链接形式注明转自 Jimmy小站
原文地址《当只有一个interface的时候,如何向spring容器中注入一个bean
额 本文暂时没人评论 来添加一个吧

发表评论

必填

选填

选填

◎欢迎参与讨论,请在这里发表您的看法、交流您的观点。

Copyright © Jimmy小站 Allrights Reserved.备案号:桂ICP备 15005996