有时在进行多个第三方对接时,定义接口的请求参数和响应参数,不想此接口有具体实现(参考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)
}