Spring中FactoryBean应用

背景

        想自己实现一个服务间调用组件,类似于forest这种基于http的调用方式,使得调用其他业务服务就如同调用接口方法的一样简洁。

        这中间关键点就需要能将接口交给Spring容器管理,此时就想到了FactoryBean + Jdk动态代理(因为这个是基于接口的代理),代理的目的是因为Spring无法直接管理接口,得对接口进行代理实现,实际上最后让Spring管理的是接口的代理对象。至于为啥选择FactoryBean ,这也是因为看了Mybatis中mapper代理对象生成的源码,有所感悟。

  public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
    final MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory<T>) knownMappers.get(type);
    if (mapperProxyFactory == null) {
      throw new BindingException("Type " + type + " is not known to the MapperRegistry.");
    }
    try {
      return mapperProxyFactory.newInstance(sqlSession);
    } catch (Exception e) {
      throw new BindingException("Error getting mapper instance. Cause: " + e, e);
    }
  }

实现步骤

1.  我们先要创建一个类来实现FactoryBean<T>,用来获取代理对象

public class ClientFactoryBean<T> implements FactoryBean<T> {

    private final Class<T> clientInterface;

    public ClientFactoryBean(Class<T> clientInterface) {
        this.clientInterface = clientInterface;
    }

    @Override
    public T getObject() throws Exception {
        ClientProxyFactory<T> clientProxyFactory = new ClientProxyFactory<>(clientInterface);
        return clientProxyFactory.newInstance(new ClientProxy<>(clientInterface));
    }

    @Override
    public Class<?> getObjectType() {
        return this.clientInterface;
    }
}

2.  需要将ClientFactoryBean交给Spring容器管理,就是通过读取@EnableOrinsClient注解的配置,获取要扫描包的路径,获取是接口同时有@OrinsClient注解的,然后将这类接口其封装成BeanDefinition,注册到Spring容器,然后扫描接口中方法,进行解析缓存,以供调用时查找

@Slf4j
public class ClientBeanScannerRegister implements ImportBeanDefinitionRegistrar, EnvironmentAware {

    private Environment environment;

    private ResourcePatternResolver resourcePatternResolver;

    private MetadataReaderFactory metadataReaderFactory;

    static final String DEFAULT_RESOURCE_PATTERN = "**/*.class";

    private final Class<? extends ClientFactoryBean> clientFactoryBeanClass = ClientFactoryBean.class;

    static final String FACTORY_BEAN_OBJECT_TYPE = "factoryBeanObjectType";


    @Override
    public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
        AnnotationAttributes annoAttrs = AnnotationAttributes.fromMap(importingClassMetadata.getAnnotationAttributes(EnableOrinsClient.class.getName()));
        if (annoAttrs != null) {
            List<String> basePackages = Arrays.stream(annoAttrs.getStringArray("basePackages")).filter(StringUtils::hasText).collect(Collectors.toList());
            if (basePackages.isEmpty()) {
                throw new ClientDefineException("client包扫描配置异常");
            }
            try {
                for (String basePackage : basePackages) {
                    // 解析出所有的包路径
                    String packageSearchPath = ResourcePatternResolver.CLASSPATH_ALL_URL_PREFIX +
                            resolveBasePackage(basePackage) + '/' + DEFAULT_RESOURCE_PATTERN;

                    Resource[] resources = getResourcePatternResolver().getResources(packageSearchPath);
                    AbstractBeanDefinition definition;
                    for (Resource resource : resources) {
                        MetadataReader metadataReader = getMetadataReaderFactory().getMetadataReader(resource);
                        // 校验是不是接口 、是否有@OrinsClient注解
                        AnnotationMetadata metadata = metadataReader.getAnnotationMetadata();
                        if (!metadata.isInterface() || !metadata.hasAnnotation(OrinsClient.class.getName())) {
                            continue;
                        }
                        parseClientBean(metadataReader);
                        definition = new ScannedGenericBeanDefinition(metadataReader);
                        definition.setSource(resource);
                        String beanClassName = definition.getBeanClassName();
                        definition.getConstructorArgumentValues().addGenericArgumentValue(beanClassName);
                        definition.setBeanClass(this.clientFactoryBeanClass);
                        definition.setAttribute(FACTORY_BEAN_OBJECT_TYPE, beanClassName);
                        definition.setScope(ConfigurableBeanFactory.SCOPE_SINGLETON);
                        BeanDefinitionHolder holder = new BeanDefinitionHolder(definition, metadataReader.getClassMetadata().getClassName());
                        BeanDefinitionReaderUtils.registerBeanDefinition(holder, registry);
                    }
                }
            } catch (Exception e) {
                log.error("解析注入client-bean失败。", e);
            }
        }
    }

    private void parseClientBean(MetadataReader metadataReader) throws ClassNotFoundException {
        String className = metadataReader.getClassMetadata().getClassName();
        Class<?> clientBeanClass = Class.forName(className);
        OrinsClient orinsClient = clientBeanClass.getAnnotation(OrinsClient.class);
        if (orinsClient == null) {
            // 跳过这个类
            return;
        }

        Method[] methods = clientBeanClass.getMethods();
        for (Method method : methods) {
            Request request = method.getAnnotation(Request.class);
            if (request == null) {
                throw new ClientMethodDefineException(String.format("%s接口定义有误,不能正常执行,请检查", method.getDeclaringClass().getName()));
            }
            List<Param> params = null;
            if (request.method() == MethodEnum.GET) {
                params = new ArrayList<>();
                Annotation[][] parameterAnnotations = method.getParameterAnnotations();
                for (Annotation[] parameterAnnotation : parameterAnnotations) {
                    for (Annotation annotation : parameterAnnotation) {
                        if (annotation instanceof Param) {
                            params.add((Param) annotation);
                        }
                    }
                }
                Class<?>[] parameterTypes = method.getParameterTypes();
                if (parameterTypes.length != params.size()) {
                    throw new ClientMethodDefineException(String.format("%s接口定义有误,不能正常执行,请检查", method.getDeclaringClass().getName()));
                }
            }
            // 封装接口中的方法

            ClientMethodRegister.ClientMethod clientMethod = new ClientMethodRegister.ClientMethod();
            clientMethod
                    .setBaseUrl(orinsClient.baseUrl())
                    .setRunMode(orinsClient.runMode())
                    .setServiceName(orinsClient.value())
                    .setMethodName(method.getDeclaringClass().getName() + "." + method.getName())
                    .setRequest(request)
                    .setHeaders(method.getAnnotation(Headers.class))
                    .setParams(params);
            ClientMethodRegister.register(clientMethod.getMethodName(), clientMethod);
        }
    }

    @Override
    public void setEnvironment(Environment environment) {
        this.environment = environment;
    }

    protected String resolveBasePackage(String basePackage) {
        return ClassUtils.convertClassNameToResourcePath(this.environment.resolveRequiredPlaceholders(basePackage));
    }

    private ResourcePatternResolver getResourcePatternResolver() {
        if (this.resourcePatternResolver == null) {
            this.resourcePatternResolver = new PathMatchingResourcePatternResolver();
        }
        return this.resourcePatternResolver;
    }

    public final MetadataReaderFactory getMetadataReaderFactory() {
        if (this.metadataReaderFactory == null) {
            this.metadataReaderFactory = new CachingMetadataReaderFactory();
        }
        return this.metadataReaderFactory;
    }
}

3.  将ClientBeanScannerRegister交给Spring容器,也就是能让Spring扫描到,这里使用的是@Import注解 ,顺便提一嘴,@Import注解的扫描是在ConfigurationClassPostProcessor中完成的,这个类是一个BeanFactoryPostProcessor

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
@Import(ClientBeanScannerRegister.class)
public @interface EnableOrinsClient {

    @AliasFor("basePackages")
    String[] value() default {};
}



@Configuration
@ComponentScan({"com.xxx.registry.client"})
@EnableOrinsClient({"com.xxx.registry.client.api"})
public class RegisterAutoConfiguration {

}

最终效果

@OrinsClient("http://localhost:8080/")
public interface UserApi {

    @Request(value = "/user/app/heartbeat", method = MethodEnum.POST)
    public void heartbeat(RegisterSupports.ServerReq serverReq);

    @Request(value = "/user/app/register", method = MethodEnum.POST)
    public void register(RegisterSupports.ServerReq serverReq);

    @Request(value = "/user/app/queryOnlineRegisterList", method = MethodEnum.GET, retry = true, retryTimes = 3, retryInterval = 1000)
    public List<RegisterRespVO> queryOnlineRegisterList();

}


// 就可以直接将这个接口注入到Spring的Bean中,开始使用了

说明

 实现步骤代码缺失没有全部贴出来,主要是考虑到稍微有点多,本文主要想表达的是对FactoryBean的应用,有兴趣的话,可以评论私聊共享。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值