Spring Boot 扩展点学习一ImportBeanDefinitionRegistrar扫描注册接口

57 阅读3分钟

有时在进行多个第三方对接时,定义接口的请求参数和响应参数,不想此接口有具体实现(参考Mapper),通过扫描相关包下带有标记注解指定接口,进行动态代理实现,从而使代码更优雅,扩展性更好

1.定义接口

@PBXApi  // 此注解标记的接口会进行动态代理注入
public interface PBXCallApi {

    @ApiParam(url = "/openapi/v1.0/get_token",hasAccessToken = false)  // 此注解标识第三方接口请求地址、请求方式、请求参数类型,是否需要token等,根据自己实际情况定义
    GetAccessTokenResponse getAccessToken(GetAccessTokenRequest request);

}

2.定义ApiParam注解

import cn.hutool.http.ContentType;
import cn.hutool.http.Method;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
 * 描述:接口方法请求信息描述
 *
 * @author Barry崔
 */
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface ApiParam {

    // 接口地址
    String url();

    // 是否需要token
    boolean hasAccessToken() default true;

    // 请求数据类型
    ContentType contentType() default ContentType.JSON;

    // 请求方式
    Method method() default Method.POST;
}

3.定义PBXApi注解

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
 * 描述;标记接口的自定义注解,被标记的接口会自动进行动态代理
 *
 * @author Barry崔
 */
@Target({ElementType.TYPE}) // 作用于类上
@Retention(RetentionPolicy.RUNTIME)
public @interface PBXApi {


}

4.定义ApiScan注解

import org.springframework.context.annotation.Import;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
 * 描述:扫描指定包下@PBXApi标记的接口
 *
 * @author Barry崔
 */
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Import(PBXImportBeanDefinitionRegister.class)
public @interface ApiScan {

    String [] basePackages();
}

5.创建一个FactoryBean实现,用于创建动态代理对象

import org.springframework.beans.factory.FactoryBean;

import java.lang.reflect.Proxy;

/**
 * 描述:创建一个FactoryBean实现,用于创建动态代理对象
 *
 * @author Barry崔
 */
public class PBXProxyFactoryBean<T> implements FactoryBean<T> {

    private final Class<T> serviceInterface;
    private final PBXInvocationHandler invocationHandler;

    public PBXProxyFactoryBean(Class<T> serviceInterface, PBXInvocationHandler invocationHandler) {
        this.serviceInterface = serviceInterface;
        this.invocationHandler = invocationHandler;
    }

    @Override
    public T getObject() {
        return (T) Proxy.newProxyInstance(this.serviceInterface.getClassLoader(),
                new Class[]{serviceInterface}, this.invocationHandler);
    }

    @Override
    public Class<?> getObjectType() {
        return serviceInterface;
    }
}

6.创建一个通用的InvocationHandler实现,用于处理所有被标记接口的调用

import cn.hutool.core.lang.TypeReference;
import cn.hutool.json.JSONUtil;
import org.springframework.util.ClassUtils;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Type;
import java.util.Arrays;

/**
 * 描述:创建一个通用的InvocationHandler实现,用于处理所有被标记接口的调用
 *
 * @author Barry崔
 */
public class PBXInvocationHandler implements InvocationHandler {

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {

        Class<?> declaringClass = method.getDeclaringClass();
        System.out.println("判断类=" + ClassUtils.isAssignable(declaringClass,PBXCallApi.class));
        System.out.println("simpleName=" + declaringClass.getSimpleName());

        if (args != null) {
            System.out.println("请求参数=" +Arrays.toString(args));
        }
        System.out.println("方法名=" + method.getName());
        ApiParam apiParam = method.getDeclaredAnnotation(ApiParam.class);
        System.out.println("请求URL=" + apiParam.url());
        System.out.println("请求参数类型=" + apiParam.contentType().getValue());
        System.out.println("是否有token=" + apiParam.hasAccessToken());
        System.out.println("请求方式=" + apiParam.method().name());

//        System.out.println("代理对象=" + proxy.getClass().getName());
//        System.out.println("代理对象=" + proxy.getClass().getSimpleName());

        GetAccessTokenResponse response = new GetAccessTokenResponse();
        response.setErrcode(0);
        response.setErrmsg("success");
        response.setAccess_token("111111111111111");
        response.setAccess_token_expire_time(60*30);
        response.setRefresh_token("22222222222222");
        response.setRefresh_token_expire_time(60*24);


        Class<?> returnType = method.getReturnType();
        String json = JSONUtil.toJsonStr(response);

        return JSONUtil.toBean(json, new TypeReference<Object>() {
            @Override
            public Type getType() {
                return returnType;
            }
        }, false);
    }

}

7.创建一个实现ImportBeanDefinitionRegistrar,用于扫描和注册接口,ApiScan注解上@Import(PBXImportBeanDefinitionRegister.class)

import org.springframework.beans.factory.annotation.AnnotatedBeanDefinition;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.context.annotation.ClassPathBeanDefinitionScanner;
import org.springframework.context.annotation.ImportBeanDefinitionRegistrar;
import org.springframework.core.annotation.AnnotationAttributes;
import org.springframework.core.type.AnnotationMetadata;
import org.springframework.core.type.filter.AnnotationTypeFilter;

import java.util.Set;

/**
 * 描述:创建一个实现`ImportBeanDefinitionRegistrar`,用于扫描和注册接口
 *
 * @author Barry崔
 */
public class PBXImportBeanDefinitionRegister implements ImportBeanDefinitionRegistrar {

    @Override
    public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
    
        // 获取导入类所在注解元数据信息
        AnnotationAttributes attributes = AnnotationAttributes.fromMap(importingClassMetadata
                .getAnnotationAttributes(ApiScan.class.getName()));

        if (attributes == null) {
            return;
        }
        
        PBXClassPathBeanDefinitionScanner scanner = new PBXClassPathBeanDefinitionScanner(registry);
        // 添加需要扫描的自定义注解
        scanner.addIncludeFilter(new AnnotationTypeFilter(PBXApi.class));

        String[] basePackages = attributes.getStringArray("basePackages");
        for (String basePackage : basePackages) {
            
            Set<BeanDefinition> candidateComponents = scanner.findCandidateComponents(basePackage);
            for (BeanDefinition candidateComponent : candidateComponents) {
                try {
                    // 接口全限类名
                    String beanClassName = candidateComponent.getBeanClassName();
                    // 反射获取接口类Class对象
                    Class<?> serviceInterface = Class.forName(beanClassName);
                    // 获取动态代理类Bean定义
                    BeanDefinitionBuilder builder = BeanDefinitionBuilder.rootBeanDefinition(PBXProxyFactoryBean.class);
                    // 填充构造参数
                    builder.addConstructorArgValue(serviceInterface);
                    builder.addConstructorArgValue(new PBXInvocationHandler());
                    registry.registerBeanDefinition(serviceInterface.getSimpleName(),builder.getBeanDefinition());
                }
                catch (ClassNotFoundException e) {
                    e.printStackTrace();
                }

            }
        }

    }

    // 重写ClassPathBeanDefinitionScanner
    private class PBXClassPathBeanDefinitionScanner extends ClassPathBeanDefinitionScanner {

        PBXClassPathBeanDefinitionScanner(BeanDefinitionRegistry registry) {
            super(registry);
        }

        // 需重写才会扫描注册接口
        @Override
        protected boolean isCandidateComponent(AnnotatedBeanDefinition beanDefinition) {
            // 候选组件Bean为接口
            return beanDefinition.getMetadata().isInterface();
        }

    }
}

8. Spring Boot启动类加上@ApiScan(basePackages = "com.xxx.xxx.xxx")

@ApiScan(basePackages = "com.demo.sa.pbx")
@SpringBootApplication
@RequiredArgsConstructor
public class Application implements CommandLineRunner {


    public static void main(String[] args) throws InterruptedException {
        SpringApplication.run(Application.class, args);
    }

    // 注入接口
    private final PBXCallApi pbxCallApi;

    @Override
    public void run(String... args) {
        GetAccessTokenRequest request = new GetAccessTokenRequest();
        request.setUsername("xxx");
        request.setPassword("xxx");
        GetAccessTokenResponse accessToken =this.pbxCallApi.getAccessToken(request);
        System.out.println("accessToken=" + accessToken);
    }
    // -------------------------------本地执行结果如下-------------------------------
    //    判断类=true
    //    simpleName=PBXCallApi
    //    请求参数=[GetAccessTokenRequest(username=root, password=root)]
    //    方法名=getAccessToken
    //    请求URL=/openapi/v1.0/get_token
    //            请求参数类型=application/json
    //    是否有token=false
    //    请求方式=POST
    //    accessToken=GetAccessTokenResponse(access_token_expire_time=1800,    access_token=111111111111111, refresh_token_expire_time=1440, refresh_token=22222222222222)
}