前言
- 最近遇到这么一个需求:需要调用很多某第三方的api接口,然后将结果进一步处理后返回给前端;有点像nginx的反向代理,但是由于第三方接口的ip地址不固定(需要根据当前用户信息找到对应的ip),所以想了下还是只能通过代码来实现;
- 因为会发起若干的网络请求,代码都类似(设置url、设置请求方式、设置请求参数、接收响应结果、打印日志等),所以就会编写很多重复的代码,而这些代码又都是非业务代码,所以我想到了AOP,想到了Feign,想到了Mybatis的Mapper;
- Feign我不太熟,只是用过,体验过那种只需编写一个接口类,加一些SpringMVC的注解,就可以发起网络请求的爽快感觉;
- 此时我就心想,能不能自己实现一个类似于Feign的网络请求封装(对接口类进行代理增强);
- 试过用AOP实现,发现AOP好像只能代理增强某个class,代理增强interface好像不得行,feign我不太熟嘛,我就心想Mybatis的Mapper接口是怎么做到代理增强的?并且还注入到了IOC容器中?
- 带着问题百度了一翻,大概了解到Mybatis是用到了JDK动态代理,再加上实现Spring的
BeanDefinitionRegistryPostProcessor
,将代理类注入到了IOC容器中;(估计Feign也是这样的原理,后面有空去看看) - 实现思路大概想清楚了(JDK代理 + BeanDefinitionRegistryPostProcessor),下面是代码实现;
项目结构
![项目结构](https://i-blog.csdnimg.cn/blog_migrate/44e8db4335af96febfd59172127a9c18.png)
- 红框中为四个主要的类;作用就是扫描指定包下的interface,并创建动态代理类,然后注入到IOC容器中;
代码
1. ApiLoginService
:
package demo.proxy.service.api;
import demo.proxy.common.annotation.ApiURL;
import demo.proxy.common.annotation.JsonParamData;
import demo.proxy.common.annotation.JsonParamTimestamp;
import demo.proxy.common.base.JsonResult;
import demo.proxy.model.LoginData;
import org.springframework.stereotype.Service;
import org.springframework.web.bind.annotation.RequestMethod;
@Service
public interface ApiLoginService {
static final String API_LOGIN_URL = "/api/mobile/auth/login";
@ApiURL(value = API_LOGIN_URL, requestMethod = RequestMethod.POST)
public JsonResult login(@JsonParamData LoginData loginData);
}
2. ApiServiceScannerConfig
:扫描指定的包,并注册到spring中
package demo.proxy.common.config;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.beans.factory.support.BeanDefinitionRegistryPostProcessor;
import org.springframework.stereotype.Component;
@Component
public class ApiServiceScannerConfig implements BeanDefinitionRegistryPostProcessor {
private static final String BASE_PACKAGE = "demo.proxy.service.api";
@Override
public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry beanDefinitionRegistry) throws BeansException {
ApiServiceScanner apiServiceScanner = new ApiServiceScanner(beanDefinitionRegistry);
apiServiceScanner.scan(BASE_PACKAGE);
}
@Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory configurableListableBeanFactory) throws BeansException {
}
}
3. ApiServiceScanner
:自定义的bean扫描器
为每个BeanDefinition指定一个bean工厂(ApiServiceFactory
)来产生代理对象;
package demo.proxy.common.config;
import lombok.extern.slf4j.Slf4j;
import org.mybatis.spring.mapper.ClassPathMapperScanner;
import org.springframework.beans.factory.annotation.AnnotatedBeanDefinition;
import org.springframework.beans.factory.config.BeanDefinitionHolder;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.beans.factory.support.GenericBeanDefinition;
import org.springframework.context.annotation.ClassPathBeanDefinitionScanner;
import java.util.Set;
@Slf4j
public class ApiServiceScanner extends ClassPathBeanDefinitionScanner {
public ApiServiceScanner(BeanDefinitionRegistry registry) {
super(registry);
}
@Override
protected Set<BeanDefinitionHolder> doScan(String... basePackages) {
Set<BeanDefinitionHolder> beanDefinitionHolders = super.doScan(basePackages);
processBeanDefinitions(beanDefinitionHolders);
return beanDefinitionHolders;
}
private void processBeanDefinitions(Set<BeanDefinitionHolder> beanDefinitionHolders) {
GenericBeanDefinition beanDefinition;
for (BeanDefinitionHolder holder : beanDefinitionHolders) {
beanDefinition = (GenericBeanDefinition) holder.getBeanDefinition();
try {
Class<?> interfaceType = Class.forName(beanDefinition.getBeanClassName());
beanDefinition.getConstructorArgumentValues().addGenericArgumentValue(interfaceType);
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
beanDefinition.setBeanClass(ApiServiceFactory.class);
beanDefinition.setAutowireMode(GenericBeanDefinition.AUTOWIRE_BY_TYPE);
}
}
public void registerFilters(){
}
@Override
protected boolean isCandidateComponent(AnnotatedBeanDefinition beanDefinition) {
return beanDefinition.getMetadata().isInterface();
}
}
4. ApiServiceFactory
:bean的实例化工厂
通过JDK代理产生一个代理对象;
package demo.proxy.common.config;
import org.springframework.beans.factory.FactoryBean;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Proxy;
public class ApiServiceFactory<T> implements FactoryBean<T> {
private final Class<T> interfaceType;
public ApiServiceFactory(Class<T> interfaceType) {
this.interfaceType = interfaceType;
}
@Override
public T getObject() throws Exception {
InvocationHandler handler = new ApiServiceProxy(interfaceType);
return (T) Proxy.newProxyInstance(interfaceType.getClassLoader(),
new Class[]{interfaceType}, handler);
}
@Override
public Class<T> getObjectType() {
return interfaceType;
}
@Override
public boolean isSingleton() {
return true;
}
}
5. ApiServiceProxy
:JDK动态代理 的具体增强逻辑,
这里我根据目标方法上的注解标记,获取发起网络请求的一些必要参数(url、请求方式、请求参数等),然后再根据当前登录用户获取到对应的baseURL,最后发起网络请求,打印日志,返回结果。
package demo.proxy.common.config;
import demo.proxy.common.annotation.ApiURL;
import demo.proxy.common.annotation.JsonParamData;
import demo.proxy.common.annotation.JsonParamTimestamp;
import demo.proxy.common.annotation.UrlParam;
import demo.proxy.common.base.JsonParam;
import demo.proxy.common.base.JsonResult;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpEntity;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Component;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.client.RestTemplate;
import java.lang.annotation.Annotation;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
import java.util.HashMap;
import java.util.Map;
@Slf4j
@Component
public class ApiServiceProxy implements InvocationHandler {
private static RestTemplate restTemplate;
private Class<?> interfaceType;
public ApiServiceProxy(Class<?> interfaceType) {
this.interfaceType = interfaceType;
}
public ApiServiceProxy() {
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
return sendRequestByAnnotationInfo(method, args);
}
private Object sendRequestByAnnotationInfo(Method method, Object[] args) {
ApiURL apiUrlAnnotation = method.getAnnotation(ApiURL.class);
if (apiUrlAnnotation == null || StringUtils.isEmpty(apiUrlAnnotation.value())) {
log.error("缺少@ApiURL信息");
return null;
}
String apiUrl = baseUrl() + apiUrlAnnotation.value();
log.info("请求地址:{}", apiUrl);
Map<String, Object> urlParams = getUrlParams(method, args);
urlParams.forEach((key, value) -> log.info("url param {}:{}", key, value));
ResponseEntity<JsonResult> responseEntity;
if (apiUrlAnnotation.requestMethod() == RequestMethod.POST) {
Object data = getParamValueByAnnotation(method, args, JsonParamData.class);
String timestamp = getJsonParamTimestamp(method, args);
log.info("json data:{}", data);
responseEntity = postRequest(apiUrl, urlParams, data, timestamp);
} else if (apiUrlAnnotation.requestMethod() == RequestMethod.GET) {
responseEntity = getRequest(apiUrl, urlParams);
} else {
log.error("不支持的请求方式,{}", apiUrl);
return null;
}
log.info("响应码:{}", responseEntity.getStatusCodeValue());
log.info("业务响应体:{}", responseEntity.getBody());
return responseEntity.getBody();
}
private static Map<String, Object> getUrlParams(Method method, Object[] paramValues) {
Map<String, Object> urlParams = new HashMap<>(4);
Parameter[] parameters = method.getParameters();
for (int i = 0; i < parameters.length; i++) {
if (parameters[i].isAnnotationPresent(UrlParam.class)) {
if (parameters[i].getType() == Map.class) {
return (Map) paramValues[i];
}
urlParams.put(parameters[i].getName(), paramValues[i]);
}
}
return urlParams;
}
private static String getJsonParamTimestamp(Method method, Object[] paramValues) {
Object timestamp = getParamValueByAnnotation(method, paramValues, JsonParamTimestamp.class);
if (timestamp == null) {
return null;
} else if (timestamp instanceof String) {
return (String) timestamp;
} else {
throw new IllegalArgumentException("参数类型错误:timestamp只能是String类型");
}
}
private static Object getParamValueByAnnotation(Method method, Object[] paramValues, Class<? extends Annotation> clazz) {
Parameter[] parameters = method.getParameters();
for (int i = 0; i < parameters.length; i++) {
if (parameters[i].isAnnotationPresent(clazz)) {
return paramValues[i];
}
}
return null;
}
private static ResponseEntity<JsonResult> getRequest(String apiUrl, Map<String, Object> urlParams) {
return restTemplate.getForEntity(apiUrl, JsonResult.class, urlParams);
}
private static ResponseEntity<JsonResult> postRequest(String apiUrl, Map<String, Object> urlParams, Object data, String timestamp) {
JsonParam jsonParam;
if (timestamp != null) {
jsonParam = new JsonParam(data, timestamp);
} else {
jsonParam = new JsonParam(data);
}
HttpEntity<JsonParam> httpEntity = new HttpEntity<>(jsonParam);
return restTemplate.postForEntity(
apiUrl,
httpEntity,
JsonResult.class,
urlParams);
}
private String baseUrl() {
return "http://localhost:8080";
}
@Autowired
public void setRestTemplate(RestTemplate restTemplate) {
ApiServiceProxy.restTemplate = restTemplate;
}
}
小总结
- 利用JDK代理为interface动态创建实例,从而实现动态增强(特殊的动态代理?代理模式的妙用?🙀);
- 通过实现
BeanDefinitionRegistryPostProcessor
接口,对beanDefinition进行预处理,自定义bean实例化的逻辑(对spring bean生命周期又有了新的认识); - 重复代码还是能不写就不写的😂;
参考文章