Jimmy小站
小明也有大梦想 — 蒋明/铭当只有一个interface的时候,如何向spring容器中注入一个bean
2023-07-13 / 未分类 / 4207 次围观 / 22 次吐槽当只有一个interface的时候,如何向spring容器中注入一个bean
为什么有这样的疑问?
类似Dubbo HSF等,消费者引入了一个二方库,这个二方库中定义了一个service的interface。
明明在我的应用中压根没有这个类的实现,也没有任何代码去向spring容器去注入这个bean。我们为啥能直接从容器中@resource这个类,然后去调用它的方法?
它是怎么做到的
主要涉及到的知识点
- spring boot autoconfig
- Proxy + Invocation 动态代理
- Reflection 反射
- FactoryBean
- Annotation 运行时注解
- 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;
}
注意事项
这里有好几个细节需要注意
GPSAutoService的注解要明确,RetentionPolicy.RUNTIME表示运行时保留注解,ElementType.FIELD表示注解只能打在类中的字段上
@Target({ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)使用时要跟HSF消费者类似,增加配置类
// 注意这里一定要是configuration,否则在GPSBeanFactoryPostProcessor类中扫描需要注入spring容器的类的时候,扫描不到这个注解。具体原因未知,应该是component和configuration内在区别导致,后续原理再分析。 @Configuration public class AutoServiceTes { @GPSAutoService(beanName = "testBeanBean") ActionNodesChainGPService actionNodesChainGPService; }
原理
后续更新
推荐您阅读更多有关于“”的文章
- 上一篇:甲乙丁辛醇管线 化学清洗施工方案
- 下一篇:锅炉化学清洗方案
本月热文
Copyright © Jimmy小站 Allrights Reserved.备案号:桂ICP备 15005996
已有22位网友发表了看法:
发表评论