背景
想自己实现一个服务间调用组件,类似于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的应用,有兴趣的话,可以评论私聊共享。