Jimmy小站

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

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

2023-07-13 / 未分类 / 4207 次围观 / 22 次吐槽

当只有一个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

已有22位网友发表了看法:

1#www.501h.com  2024-11-14 03:34:24 回复该评论
《最后的模样》剧情片高清在线免费观看:https://www.jgz518.com/xingkong/52440.html
2#访客1  2024-08-08 17:24:10 回复该评论
识别码:byjzcv67831OY-这游戏真不错!http://www.a5km.com/yxgl/jdqs/28340.html
3#访客1  2024-08-07 21:05:38 回复该评论
识别码:lqivwf85740XY-这游戏真不错!http://www.dnf70.com/2885.html
4#访客  2024-08-06 06:08:44 回复该评论
识别码:ueqovk65834XE-农信银行信用卡申请http://www.zsdai.com/post/3308.html
5#访客直播APP  2024-07-30 08:30:35 回复该评论
识别码:mpztxr30268JR-怀旧范儿的狂欢节http://www.chongdeschool.com
6#访客  2024-07-29 19:58:12 回复该评论
识别码:tyuqkr62578EX-线上贷款哪个平台最快捷方便http://www.zsdai.com/post/38344.html
7#访客  2024-07-29 19:57:33 回复该评论
识别码:cqsgwe37805XR-16岁借钱急用小额apphttp://www.zsdai.com/post/22857.html
8#访客游戏攻略网  2024-07-27 11:22:38 回复该评论
识别码:pvwluk73540DY-文章写的真不错https://www.ldyyouxi.com/
9#访客游戏攻略网  2024-07-27 07:16:17 回复该评论
识别码:wbokmg43250JU-文章写的真不错https://www.ldyyouxi.com/
10#访客游戏攻略网  2024-07-25 23:29:30 回复该评论
识别码:zkyvpd75190MN-文章写的真不错https://www.ldyyouxi.com/
11#访客游戏攻略网  2024-07-24 03:13:54 回复该评论
识别码:gevpoc30862VL-文章写的真不错https://www.gaofeick.com/
12#访客游戏攻略网  2024-07-23 17:17:55 回复该评论
识别码:lqshpe71490YR-文章写的真不错https://www.ldyyouxi.com/
13#访客游戏攻略网  2024-07-23 15:52:42 回复该评论
识别码:cbtxky05867YK-文章写的真不错https://www.ldyyouxi.com/
14#访客游戏攻略网  2024-07-23 13:36:39 回复该评论
识别码:yhvbrl35916BM-文章写的真不错https://www.ldyyouxi.com/
15#访客高分游戏攻略  2024-07-22 15:05:17 回复该评论
识别码:qnwysg72513FJ-文章写的真不错https://www.gaofeick.com/
16#访客  2024-07-22 06:51:39 回复该评论
识别码:oqlsxb62574LY-借钱平台100%通过小额贷款http://www.zsdai.com/post/68390.html
17#访客高分游戏攻略  2024-07-22 02:49:15 回复该评论
识别码:ceitdq93056SH-文章写的真不错https://www.gaofeick.com/
识别码:coexpm74312KB-文章写的真不错https://www.gaofeick.com/
19#访客高分游戏攻略  2024-07-21 07:26:09 回复该评论
识别码:qioubj37184NA-文章写的真不错https://www.gaofeick.com/
20#访客11  2024-07-20 10:20:30 回复该评论
识别码:nfcubs82694HP-文章写的真不错https://www.gaofeick.com/

发表评论

必填

选填

选填

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

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