# 深入spring IOC+JDK动态代理编写一个http声明式组件
深入spring IOC+JDK动态代理编写一个http声明式组件
前置知识
- JDK动态代理
- 几个spring接口的使用,包括
//用于获取一些环境相关的数据 org.springframework.context.EnvironmentAware //用于自定义注入bean到容器的回调接口 org.springframework.context.annotation.ImportBeanDefinitionRegistrar //自定义扫描过滤策略 org.springframework.context.annotation.ClassPathScanningCandidateComponentProvider //自定义bean到容器的装配方式 org.springframework.beans.factory.FactoryBean
目的
目前网络请求大多使用httpclient或OKhttp3,如果在代码自行拼装url、参数、封装返回值,那么代码量会变得冗长且后续维护难度较大。
本次目的是仿造feign,写一个自己的声明式http客户端,由于本章主要是学习spring容器注入过程和JDK动态代理,网络请求和解析内容不是重点。自定义的组件只实现get请求功能。后续可很容易扩展到所有请求方式和请求方法。
编码
- 编写一个注解
HttpClient
加在接口上,对接口生成动态代理
/** * @author 树荫下的天空 * @date 2019/8/4 12:09 */ @Inherited @Target(ElementType.TYPE) @Documented @Retention(RetentionPolicy.RUNTIME) public @interface HttpClient{ }
- 扫描目标类
public class HttpClientRegister implements EnvironmentAware, ImportBeanDefinitionRegistrar{ private Environment environment; @Override public void setEnvironment(Environment environment){ this.environment = environment; } @Override public void registerBeanDefinitions(AnnotationMetadata metadata,BeanDefinitionRegistry registry){ ClassPathScanningCandidateComponentProvider scanner = getScanner(); // 扫描带有@HttpClient注解的类 scanner.addIncludeFilter(new AnnotationTypeFilter(HttpClient.class)); // 获取基本包路径(MainApplication所在包) String basePackage = ClassUtils.getPackageName(metadata.getClassName()); //扫描到的类 Set<BeanDefinition> beanDefinitions = scanner.findCandidateComponents(basePackage); for(BeanDefinition beanDefinition : beanDefinitions){ //以类的全路径名作为spring的bean名称 String beanName = beanDefinition.getBeanClassName(); ScannedGenericBeanDefinition genericBeanDefinition = (ScannedGenericBeanDefinition) beanDefinition; if(!genericBeanDefinition.getMetadata().isInterface()){ throw new IllegalStateException("HttpClient注解只能加在接口上"); } //通过工厂类动态代理生成实现类,并注入到spring容器 AbstractBeanDefinition definition = BeanDefinitionBuilder .genericBeanDefinition(HttpClientFactory.class).addConstructorArgValue(beanName).getBeanDefinition(); System.out.println(beanName); registry.registerBeanDefinition(beanName,definition); } } //扫描器规则配置 protected ClassPathScanningCandidateComponentProvider getScanner(){ return new ClassPathScanningCandidateComponentProvider(false,this.environment){ @Override protected boolean isCandidateComponent(AnnotatedBeanDefinition beanDefinition){ boolean isCandidate = false; if(beanDefinition.getMetadata().isIndependent()){ if(!beanDefinition.getMetadata().isAnnotation()){ isCandidate = true; } } return isCandidate; } }; } }
- 编写一个工厂类,用户动态代理接口的实现类,如下
/** * @author 树荫下的天空 * @date 2019/8/4 12:07 */ public class HttpClientFactory<T> implements FactoryBean<T>{ private Class<T> clientClass; public HttpClientFactory(Class<T> clientClass){ this.clientClass = clientClass; } public HttpClientFactory(String className){ try{ this.clientClass = (Class<T>) Class.forName(className); }catch(ClassNotFoundException e){ e.printStackTrace(); } } @Override public T getObject() throws Exception{ Object instance = Proxy.newProxyInstance(clientClass.getClassLoader(),new Class[]{clientClass},(proxy,method,args) -> { RequestMapping mapping = method.getAnnotation(RequestMapping.class); OkHttpClient client = new OkHttpClient(); String[] url = mapping.value(); Request request = new Request.Builder() .url(url[0]) .method("GET",null) .build(); String response = client.newCall(request).execute().body().string(); Class<?> returnType = method.getReturnType(); return JsonUtil.parse(response,returnType); }); return clientClass.cast(instance); } @Override public Class<?> getObjectType(){ return clientClass; } }
>
实现了org.springframework.beans.factory.FactoryBean接口,
动态代理创建接口子类,取到@RequestMapping注解,通过注解获取url,然后通过okhttp3请求。
使用Jackson将返回的结果,根据接口声明返回类型进行封装。
- 编写声明式客户端
@HttpClient public interface ITestClient{ /** * 访问百度 * @return */ @RequestMapping(value = "http://baidu.com") String baidu(); /** * 访问搜狐 * @return */ @RequestMapping(value = "http://www.sohu.com/") String sohu(); }
- 客户端代用代码和返回结果如下:
@Autowired ITestClient mTestClient; @GetMapping("/sohu") public String sohu(){ return mTestClient.sohu(); } @GetMapping("/baidu") public String baidu(){ return mTestClient.baidu(); }
- 请求结果
百度:
搜狐: