模仿mybatis-spring 实现动态生成controller

背景

  1. 项目采用spring cloud alibaba (nacos openfeign)

  1. 我们业务需要调用第三方平台接口。所以一个应用承担 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实现类。不能处理此次请求。");

    }
}
  • 0
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

jiguansheng

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值