# 深入spring IOC+JDK动态代理编写一个http声明式组件

深入spring IOC+JDK动态代理编写一个http声明式组件

前置知识

  1. JDK动态代理
  2. 几个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请求功能。后续可很容易扩展到所有请求方式和请求方法。

编码

  1. 编写一个注解HttpClient加在接口上,对接口生成动态代理
    /**
     * @author 树荫下的天空
     * @date 2019/8/4 12:09
     */
    @Inherited
    @Target(ElementType.TYPE)
    @Documented
    @Retention(RetentionPolicy.RUNTIME)
    public @interface HttpClient{
    }
  2. 扫描目标类
    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;
                }
            };
        }
    }
    
  3. 编写一个工厂类,用户动态代理接口的实现类,如下
    /**
     * @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将返回的结果,根据接口声明返回类型进行封装。

  4. 编写声明式客户端
    @HttpClient
    public interface ITestClient{
        /**
         * 访问百度
         * @return 
         */
        @RequestMapping(value = "http://baidu.com")
        String baidu();
    
        /**
         * 访问搜狐
         * @return
         */
        @RequestMapping(value = "http://www.sohu.com/")
        String sohu();
    }
  5. 客户端代用代码和返回结果如下:
        @Autowired
        ITestClient mTestClient;
    
        @GetMapping("/sohu")
        public String sohu(){
            return mTestClient.sohu();
        }
        @GetMapping("/baidu")
        public String baidu(){
            return mTestClient.baidu();
        }
  6. 请求结果
    百度:


    搜狐:

posted @ 2019-08-04 21:17  树荫下的天空  阅读(357)  评论(0编辑  收藏  举报