前言
- 最近遇到这么一个需求:需要调用很多某第三方的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),下面是代码实现;
项目结构

- 红框中为四个主要的类;作用就是扫描指定包下的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;@ServicepublicinterfaceApiLoginService{staticfinal String API_LOGIN_URL="/api/mobile/auth/login";@ApiURL(value= API_LOGIN_URL, requestMethod= RequestMethod.POST)public JsonResultlogin(@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;@ComponentpublicclassApiServiceScannerConfigimplementsBeanDefinitionRegistryPostProcessor{privatestaticfinal String BASE_PACKAGE="demo.proxy.service.api";@OverridepublicvoidpostProcessBeanDefinitionRegistry(BeanDefinitionRegistry beanDefinitionRegistry)throws BeansException{
ApiServiceScanner apiServiceScanner=newApiServiceScanner(beanDefinitionRegistry);
apiServiceScanner.scan(BASE_PACKAGE);}@OverridepublicvoidpostProcessBeanFactory(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;@Slf4jpublicclassApiServiceScannerextendsClassPathBeanDefinitionScanner{publicApiServiceScanner(BeanDefinitionRegistry registry){super(registry);}@Overrideprotected Set<BeanDefinitionHolder>doScan(String... basePackages){
Set<BeanDefinitionHolder> beanDefinitionHolders=super.doScan(basePackages);processBeanDefinitions(beanDefinitionHolders);return beanDefinitionHolders;}privatevoidprocessBeanDefinitions(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);}}publicvoidregisterFilters(){}@OverrideprotectedbooleanisCandidateComponent(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;publicclassApiServiceFactory<T>implementsFactoryBean<T>{privatefinal Class<T> interfaceType;publicApiServiceFactory(Class<T> interfaceType){this.interfaceType= interfaceType;}@Overridepublic TgetObject()throws Exception{
InvocationHandler handler=newApiServiceProxy(interfaceType);return(T) Proxy.newProxyInstance(interfaceType.getClassLoader(),newClass[]{interfaceType}, handler);}@Overridepublic Class<T>getObjectType(){return interfaceType;}@OverridepublicbooleanisSingleton(){returntrue;}}
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@ComponentpublicclassApiServiceProxyimplementsInvocationHandler{privatestatic RestTemplate restTemplate;private Class<?> interfaceType;publicApiServiceProxy(Class<?> interfaceType){this.interfaceType= interfaceType;}publicApiServiceProxy(){}@Overridepublic Objectinvoke(Object proxy, Method method, Object[] args)throws Throwable{returnsendRequestByAnnotationInfo(method, args);}private ObjectsendRequestByAnnotationInfo(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);}elseif(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();}privatestatic Map<String, Object>getUrlParams(Method method, Object[] paramValues){
Map<String, Object> urlParams=newHashMap<>(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;}privatestatic StringgetJsonParamTimestamp(Method method, Object[] paramValues){
Object timestamp=getParamValueByAnnotation(method, paramValues, JsonParamTimestamp.class);if(timestamp== null){return null;}elseif(timestampinstanceofString){return(String) timestamp;}else{thrownewIllegalArgumentException("参数类型错误:timestamp只能是String类型");}}privatestatic ObjectgetParamValueByAnnotation(Method method, Object[] paramValues, Class<?extendsAnnotation> clazz){
Parameter[] parameters= method.getParameters();for(int i=0; i< parameters.length; i++){if(parameters[i].isAnnotationPresent(clazz)){return paramValues[i];}}return null;}privatestatic ResponseEntity<JsonResult>getRequest(String apiUrl, Map<String, Object> urlParams){return restTemplate.getForEntity(apiUrl, JsonResult.class, urlParams);}privatestatic ResponseEntity<JsonResult>postRequest(String apiUrl, Map<String, Object> urlParams, Object data, String timestamp){
JsonParam jsonParam;if(timestamp!= null){
jsonParam=newJsonParam(data, timestamp);}else{
jsonParam=newJsonParam(data);}
HttpEntity<JsonParam> httpEntity=newHttpEntity<>(jsonParam);return restTemplate.postForEntity(
apiUrl,
httpEntity,
JsonResult.class,
urlParams);}private StringbaseUrl(){return"http://localhost:8080";}@AutowiredpublicvoidsetRestTemplate(RestTemplate restTemplate){
ApiServiceProxy.restTemplate= restTemplate;}}
小总结
- 利用JDK代理为interface动态创建实例,从而实现动态增强(特殊的动态代理?代理模式的妙用?🙀);
- 通过实现
BeanDefinitionRegistryPostProcessor
接口,对beanDefinition进行预处理,自定义bean实例化的逻辑(对spring bean生命周期又有了新的认识); - 重复代码还是能不写就不写的😂;
参考文章