背景
项目采用spring cloud alibaba (nacos openfeign)
我们业务需要调用第三方平台接口。所以一个应用承担 transformer (接口参数转化接口) 接口转化和connector(连接器)等职责。
业务模块
分层如下
dto
public abstract class ApiBaseRequest<T> {
/**
* 平台编码
*/
@NotBlank(message = "平台编码不能为空")
private String platformCode = PlatformCode.ME_ELE.getCode();
public String getPlatformCode() {
return PlatformCode.ME_ELE.getCode();
}
public abstract String getMethod();
public Class<T> getResponseClass() {
ParameterizedType parameterizedType = (ParameterizedType) this.getClass().getGenericSuperclass();
return (Class) parameterizedType.getActualTypeArguments()[0];
}
}
EleNewRetailActivityCreateRequest
public class EleNewRetailActivityCreateRequest extends ApiBaseRequest<ActivityResponse, EleNewRetailNrMktActRule> {
@Override
public String getMethod() {
return MethodCode.ACTIVITY_CREATE.getMethod();
}
}
client
@FeignClient(name = "open-platform")
@Api(tags = "单品直降、品类满减、N选一活动")
public interface NGiftMClient {
@PostMapping(ApiConstants.PREFIX + "/activity/create")
@ApiOperation("创建单品直降、品类满减、N选一活动")
@ResponseBody
CommonResult<ActivityResponse> createActivity(@RequestBody EleNewRetailActivityCreateRequest request);
}
controller
@RestController
public class NGiftMClientImpl implements NGiftMClient {
@Autowired
private ApiProcessService apiProcessService;
@Override
public CommonResult<ActivityResponse> createActivity(EleNewRetailActivityCreateRequest request) {
return apiProcessService.process(request);
}
}
优化
这么写!有没有问题呢?没有,但非常枯燥无味。都是些重复代理。
有没有办法优化,有!
利用Import注解 + ImportBeanDefinitionRegistar + ClassPathBeanDefinitionScanner +FactoryBean+Proxy组合实现。
设计
Import注解
spring容器启动时,将Import的值注册到ioc容器。
ImportBeanDefinitionRegistar
将自动调用 ImportBeanDefinitionRegistar的registerBeanDefinitions方法。并注入BeanDefinition Registry。为实例化ClassPathBeanDefinitionScanner做准备。
ClassPathBeanDefinitionScanner
提供扫描类的能力。
Proxy
为我们生成代理类
RequestMappingHandlerMapping
编程式注册spring mvc http接口。
代码
FeignControllerMapper
自定义注解提供编辑扫描包路径编辑能力
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
@Import(ControllerScannerConfigurer.class)
public @interface FeignControllerMapper {
/**
* Base packages to scan for MyBatis interfaces. Note that only interfaces with at least one method will be
* registered; concrete classes will be ignored.
*
* @return base package names for scanning mapper interface
*/
String[] basePackages() default {};
}
ControllerScannerConfigurer
实现了ImportBeanDefinitionRegistrar接口,获得BeanDefinitionRegistry接口。
public class ControllerScannerConfigurer implements ImportBeanDefinitionRegistrar {
@Override
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry, BeanNameGenerator importBeanNameGenerator) {
AnnotationAttributes mapperScanAttrs = AnnotationAttributes
.fromMap(importingClassMetadata.getAnnotationAttributes(FeignControllerMapper.class.getName()));
ImportBeanDefinitionRegistrar.super.registerBeanDefinitions(importingClassMetadata, registry, importBeanNameGenerator);
ClasspathControllerScanner classpathControllerScanner = new ClasspathControllerScanner(registry);
classpathControllerScanner.registerFilters();
classpathControllerScanner.scan(mapperScanAttrs.getStringArray("basePackages"));
}
}
ClassPathControllerScanner
实现了ClasspathBeanDefinitionScanner接口。将扫描的bean类型设置成ControllerFactoryBean
public class ClasspathControllerScanner extends ClassPathBeanDefinitionScanner {
public ClasspathControllerScanner(BeanDefinitionRegistry registry) {
super(registry);
}
protected void registerFilters() {
addIncludeFilter( new AnnotationTypeFilter(FeignClient.class));
}
@Override
protected Set<BeanDefinitionHolder> doScan(String... basePackages) {
Set<BeanDefinitionHolder> beanDefinitions = super.doScan(basePackages);
BeanDefinitionRegistry registry = getRegistry();
if (beanDefinitions.isEmpty()) {
logger.warn( "No FeignClient was found in '" + Arrays.toString(basePackages)
+ "' package. Please check your configuration.");
}
beanDefinitions.forEach(holder->{
log.info("beanName:{}",holder.getBeanName());
AbstractBeanDefinition beanDefinition = (AbstractBeanDefinition) holder.getBeanDefinition();
beanDefinition.getConstructorArgumentValues().addGenericArgumentValue(beanDefinition.getBeanClassName());
beanDefinition.setBeanClass(ControllerFactoryBean.class);
});
return beanDefinitions;
}
@Override
protected boolean isCandidateComponent(AnnotatedBeanDefinition beanDefinition) {
return beanDefinition.getMetadata().isInterface() && beanDefinition.getMetadata().isIndependent();
}
}
ControllerFactoryBean
实现了FactoryBean接口,内部是个Proxy。
使用AnnotatedElementUtils 拿到RequestMapping元数据
实现了CommandLineRunner接口。springboot启动完成后注册http接口
@Slf4j
public class ControllerFactoryBean<T> implements FactoryBean<T>, CommandLineRunner,BeanFactoryAware {
private final Class<T> controllerInterface;
private BeanFactory beanFactory;
@Autowired
private RequestMappingHandlerMapping requestMappingHandlerMapping;
public ControllerFactoryBean(Class<T> controllerInterface) {
this.controllerInterface = controllerInterface;
}
@Override
public T getObject() throws Exception {
return new ControllerProxyFactory<>(controllerInterface).newInstance(new ControllerProxy<>(controllerInterface, beanFactory));
}
@Override
public Class<?> getObjectType() {
return controllerInterface;
}
@Override
public void run(String... args) throws Exception {
log.info("mappingHandlerMapping:{}",requestMappingHandlerMapping);
ReflectionUtils.doWithLocalMethods(controllerInterface, method -> {
log.info("method:{}",method.getName());
RequestMapping requestMapping = AnnotatedElementUtils.findMergedAnnotation(method, RequestMapping.class);
if(requestMapping == null){
log.info("not found requestMapping");
return ;
}
RequestMappingInfo.Builder builder = RequestMappingInfo
.paths(requestMapping.path())
.methods(requestMapping.method())
;
requestMappingHandlerMapping.registerMapping(builder.build(),beanFactory.getBean(controllerInterface),method);
log.info("requestMapping:{}",requestMapping);
});
}
@Override
public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
this.beanFactory = beanFactory;
}
}
ControllerProxy
sdkProxy代理,调用第三方服务。
@Slf4j
public class ControllerProxy<T> implements InvocationHandler {
private final Class<T> controllerInterface;
private BeanFactory beanFactory;
public ControllerProxy(Class<T> controllerInterface, BeanFactory beanFactory) {
this.controllerInterface = controllerInterface;
this.beanFactory = beanFactory;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//当前的方法属于object
if (Object.class.equals(method.getDeclaringClass())) {
return method.invoke(this, args);
}
log.info("调用方法:{}",method.getName());
if(args.length==0){
throw new IllegalStateException("由于method:"+method.getName()+";不能处理");
}
Object obj = args[0];
if(ApiBaseRequest.class.isAssignableFrom(obj.getClass())){
ApiBaseRequest apiBaseRequest = (ApiBaseRequest) obj;
PlatformCode platformCode = PlatformCode.convert(apiBaseRequest.getPlatformCode());
MethodCode methodCode = MethodCode.convert(apiBaseRequest.getMethod());
ActivityService activityService = beanFactory.getBean(ActivityService.class);
activityService.send(apiBaseRequest,apiBaseRequest.getResponseClass());
return CommonResult.success(RemoteCmdElementHelper.invoke(platformCode,methodCode,obj));
}
throw new IllegalStateException("由于method:"+method.getName()+"入参不是ApiBaseRequest实现类。不能处理此次请求。");
}
}