从零开始写Feign

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/qq_35559756/article/details/80945805

从零开始写Feign

配置依赖

pom.xml:

<dependency>
   <groupId>org.springframework.cloud</groupId>
   <artifactId>spring-cloud-starter-feign</artifactId>
   <version>1.3.0.RELEASE</version>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-eureka</artifactId>
    <version>1.1.5.RELEASE</version>
</dependency>

配置配置文件

bootstrap.properties:

spring.application.name=cbm-service
spring.cloud.config.discovery.enabled=true
spring.cloud.config.discovery.serviceId=eas-config-server
spring.cloud.config.name=cbm-config
server.port=8082
eureka.client.serviceUrl.defaultZone=http://localhost:8761/eureka/
eureka.instance.prefer-ip-address=true
eureka.instance.perferIpAddress=true

配置负载均衡的RestTemplate

RestClientConfig:

package com.zteict.cbm.basicdata.yearCaryover.RestClient;

import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.cloud.client.loadbalancer.LoadBalanced;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.client.RestTemplate;

/**
 * @since : rest客户端配置
 * @author : tristan('[email protected]') nowDate: 2018/7/6 19:15
 */
@Configuration
public class RestClientConfig {

   /**
    * @since : 配置负载均衡的restTemplate
    * @author : tristan('[email protected]') nowDate: 2018/7/6 19:15
    */
   @Bean
   @LoadBalanced
   @ConditionalOnMissingBean(RestTemplate.class)
   public RestTemplate restTemplate() {
      return new RestTemplate();
   }
}

创建基本RestClient标记注解

RestClient:

package com.zteict.cbm.basicdata.yearCaryover.RestClient;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
 * @since : rest客户端的标记注解
 * @author : tristan('[email protected]') nowDate: 2018/7/6 19:17
 */
@Target({ ElementType.TYPE })
@Retention(RetentionPolicy.RUNTIME)
public @interface RestClient {
   String value() default ""; /*服务名称*/
}

Param:

package com.zteict.cbm.basicdata.yearCaryover.RestClient;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
 *@since : 指定映射参数名称
 *@author : tristan('[email protected]') nowDate: 2018/7/7 14:23
 */
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.PARAMETER)
public @interface Param {
   String value() default "";
}

将restClient接口类注册到spring中

RestClientRegistBeanConfig:

package com.zteict.cbm.basicdata.yearCaryover.RestClient;

import java.util.List;

import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.beans.factory.support.BeanDefinitionRegistryPostProcessor;
import org.springframework.beans.factory.support.GenericBeanDefinition;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.stereotype.Component;

@Component
public class RestClientRegistBeanConfig implements ApplicationContextAware, BeanDefinitionRegistryPostProcessor {

   public ApplicationContext ctx; // 当前的应用容器

   /**
    * @since : 注册bean之前
    * @author : tristan('[email protected]') nowDate: 2018/7/6 21:01
    */
   @Override
   public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException {
      try {
         List<Class> classByAnnotationAndPackage = new Scanner().getClassByAnnotationAndPackage("com.zteict",
               RestClient.class);
         if (classByAnnotationAndPackage != null && classByAnnotationAndPackage.size() > 0) {
            for (Class restClientClass : classByAnnotationAndPackage) {
               BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(restClientClass);
               GenericBeanDefinition definition = (GenericBeanDefinition) builder.getRawBeanDefinition();
               definition.getPropertyValues().add("interfaceClass", definition.getBeanClassName());
               definition.setBeanClass(RestClientFactoryBean.class);
               definition.setAutowireMode(GenericBeanDefinition.AUTOWIRE_BY_TYPE);
               String beanNameByClassName = RestClientUtils.getBeanNameByClassName(restClientClass.getName());
               System.out.println("restClient动态注册了一个restClient,该bean名称为: " + beanNameByClassName + "所在位置是: "
                     + restClientClass.getName());
               registry.registerBeanDefinition(beanNameByClassName, definition);
            }
         }
      } catch (Exception e) {
         e.printStackTrace();
      }
   }

   @Override
   public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
   }

   /**
    * @since : 专门设置容器上下文
    * @author : tristan('[email protected]') nowDate: 2018/7/6 21:01
    */
   @Override
   public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
      this.ctx = applicationContext;
   }
}

RestClientInvocationHandler:

package com.zteict.cbm.basicdata.yearCaryover.RestClient;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.stereotype.Component;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.client.RestTemplate;

/**
 * @since : rest客户端执行者
 * @author : tristan('[email protected]') nowDate: 2018/7/6 20:39
 */
@Component
public class RestClientInvocationHandler implements InvocationHandler, ApplicationContextAware {

   public ApplicationContext ctx; // 当前的应用容器

   /**
    * @since : 专门设置容器上下文
    * @author : tristan('[email protected]') nowDate: 2018/7/6 21:01
    */
   @Override
   public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
      this.ctx = applicationContext;
   }

   private Class<?> interfaceClass;

   public Object bind(Class<?> cls) {
      this.interfaceClass = cls;
      return Proxy.newProxyInstance(cls.getClassLoader(), new Class[] { interfaceClass }, this);
   }

   @Override
   public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
      RestClient restClient = interfaceClass.getAnnotation(RestClient.class);
      String applicationName = restClient.value(); // 服务名
      RequestMapping controllerRequestMapping = interfaceClass.getAnnotation(RequestMapping.class);
      String[] controllerUrls = controllerRequestMapping.value();
      RequestMapping methodRequestMapping = method.getAnnotation(RequestMapping.class);
      String[] methodUrls = methodRequestMapping.value();
      String suffixUrl = controllerUrls.length < 1 ? "" : controllerUrls[0] == null ? "" : controllerUrls[0];
      suffixUrl += methodUrls.length < 1 ? "" : methodUrls[0] == null ? "" : methodUrls[0];
      if (suffixUrl == null || suffixUrl.length() < 1)
         throw new RestClientException("请求url填写为空(指定值为空)");
      RequestMethod[] requestMethods = methodRequestMapping.method();
      String requestMethod = requestMethods == null ? ""
            : (requestMethods.length < 1 ? ""
                  : (requestMethods[0] == null ? ""
                        : requestMethods[0].name() == null ? "" : requestMethods[0].name()));
      if (requestMethod == null || requestMethod.length() < 1)
         throw new RestClientException("请求访问方法未指定");
      Class<?> returnType = method.getReturnType();
      RestTemplate restTemplate = RestClientUtils.getBeanByClass(RestTemplate.class);
      String url = "http://" + applicationName + suffixUrl;
      return RestClientUtils.doRequest(restTemplate, requestMethod, url, args, returnType, method);
   }
}

RestClientFactoryBean:

package com.zteict.cbm.basicdata.yearCaryover.RestClient;

import org.springframework.beans.factory.FactoryBean;

public class RestClientFactoryBean<T> implements FactoryBean<T> {

   private Class<T> interfaceClass; /*接口类字节码*/

   /*提供get和set方法*/
   public Class<T> getInterfaceClass() {
      return interfaceClass;
   }

   public void setInterfaceClass(Class<T> interfaceClass) {
      this.interfaceClass = interfaceClass;
   }

   /**
    * @since : 获取对象
    * @author : tristan('3301015948@qq.com') nowDate: 2018/7/6 20:56
    */
   @Override
   public T getObject() throws Exception {
      return (T) new RestClientInvocationHandler().bind(interfaceClass);
   }

   /**
    * @since : 获取类的字节码
    * @author : tristan('3301015948@qq.com') nowDate: 2018/7/6 20:57
    */
   @Override
   public Class<?> getObjectType() {
      return interfaceClass;
   }

   /**
    * @since : 是否单例,即都是使用同一个对象
    * @author : tristan('3301015948@qq.com') nowDate: 2018/7/6 20:58
    */
   @Override
   public boolean isSingleton() {
      return true;
   }
}

Scanner:

package com.zteict.cbm.basicdata.yearCaryover.RestClient;

import java.io.File;
import java.io.FileFilter;
import java.io.IOException;
import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.net.JarURLConnection;
import java.net.URL;
import java.net.URLDecoder;
import java.util.*;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;

/**
 * @since : 扫描类
 * @author : tristan('[email protected]') nowDate: 2018/7/7 19:44
 */
public class Scanner {
   /**
    * @since : 从包package中获取所有的Class
    * @author : tristan('[email protected]') nowDate: 2018/7/7 19:46
    */
   public Set<Class<?>> getClasses(String packageName) throws Exception {
      Set<Class<?>> classes = new HashSet<>(); // 第一个class类的集合
      boolean recursive = true; // 是否循环迭代
      String packageDirName = packageName.replace('.', '/'); // 获取包的名字 并进行替换
      Enumeration<URL> dirs;// 定义一个枚举的集合 并进行循环来处理这个目录下的things
      try {
         dirs = Thread.currentThread().getContextClassLoader().getResources(packageDirName);
         while (dirs.hasMoreElements()) { // 循环迭代下去
            URL url = dirs.nextElement(); // 获取下一个元素
            String protocol = url.getProtocol(); // 得到协议的名称
            if ("file".equals(protocol)) { // 如果是以文件的形式保存在服务器上
               String filePath = URLDecoder.decode(url.getFile(), "UTF-8"); // 获取包的物理路径
               findAndAddClassesInPackageByFile(packageName, filePath, recursive, classes); // 以文件的方式扫描整个包下的文件
            } else if ("jar".equals(protocol)) { // 如果是jar包文件
               JarFile jar; // 定义一个JarFile
               try {
                  jar = ((JarURLConnection) url.openConnection()).getJarFile();// 获取jar
                  Enumeration<JarEntry> entries = jar.entries();// 从此jar包 得到一个枚举类
                  while (entries.hasMoreElements()) { // 同样的进行循环迭代
                     JarEntry entry = entries.nextElement(); // 获取jar里的一个实体 可以是目录 和一些jar包里的其他文件 如META-INF等文件
                     String name = entry.getName();
                     if (name.charAt(0) == '/') {// 如果是以/开头的
                        name = name.substring(1);// 获取后面的字符串
                     }
                     if (name.startsWith(packageDirName)) { // 如果前半部分和定义的包名相同
                        int idx = name.lastIndexOf('/');// 如果以"/"结尾 是一个包
                        if (idx != -1) {
                           packageName = name.substring(0, idx).replace('/', '.');// 获取包名 把"/"替换成"."
                        }
                        if ((idx != -1) || recursive) {// 如果可以迭代下去 并且是一个包
                           if (name.endsWith(".class") && !entry.isDirectory()) {// 如果是一个.class文件 而且不是目录
                              String className = name.substring(packageName.length() + 1, name.length() - 6);// 去掉后面的".class"
                                                                                          // 获取真正的类名
                              try {
                                 classes.add(Class.forName(packageName + '.' + className));// 添加到classes
                              } catch (ClassNotFoundException e) {
                                 e.printStackTrace();
                              }
                           }
                        }
                     }
                  }
               } catch (IOException e) {
                  e.printStackTrace();
               }
            }
         }
      } catch (IOException e) {
         e.printStackTrace();
      }

      return classes;
   }
   /**
     *@since : 得到指定包中指定注解的所有class
     *@author : tristan('[email protected]') nowDate: 2018/7/7 19:51
     */
   public <T> List<Class> getClassByAnnotationAndPackage(String packageName, Class annotation) throws Exception {
      ArrayList<Class> classes = new ArrayList<>();
      Set<Class<?>> clsList = getClasses(packageName);
      if (clsList != null && clsList.size() > 0) {
         for (Class cls : clsList) {
            Annotation requestMapping = cls.getAnnotation(annotation);
            if (requestMapping != null) {
               classes.add(cls);
            }
         }
      }
      return classes;
   }
   /**
    * @since : 以文件的形式来获取包下的所有Class
    * @author : tristan('[email protected]') nowDate: 2018/7/7 19:49
    */
   public static void findAndAddClassesInPackageByFile(String packageName, String packagePath, final boolean recursive,
         Set<Class<?>> classes) {
      File dir = new File(packagePath);// 获取此包的目录 建立一个File
      if (!dir.exists() || !dir.isDirectory()) { // 如果不存在或者 也不是目录就直接返回
         return;
      }
      File[] dirfiles = dir.listFiles(new FileFilter() {// 如果存在 就获取包下的所有文件 包括目录
         public boolean accept(File file) {// 自定义过滤规则 如果可以循环(包含子目录) 或则是以.class结尾的文件(编译好的java类文件)
            return (recursive && file.isDirectory()) || (file.getName().endsWith(".class"));
         }
      });
      for (File file : dirfiles) {// 循环所有文件
         if (file.isDirectory()) { // 如果是目录 则继续扫描
            findAndAddClassesInPackageByFile(packageName + "." + file.getName(), file.getAbsolutePath(), recursive,
                  classes);
         } else {
            String className = file.getName().substring(0, file.getName().length() - 6);// 如果是java类文件 去掉后面的.class 只留下类名
            try {
               classes.add(Thread.currentThread().getContextClassLoader().loadClass(packageName + '.' + className));// 添加到集合中去
            } catch (ClassNotFoundException e) {
               e.printStackTrace();
            }
         }
      }
   }
}

RestClientUtils:

package com.zteict.cbm.basicdata.yearCaryover.RestClient;

import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.HashMap;

import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpMethod;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Component;
import org.springframework.web.client.RestTemplate;

@Component
public class RestClientUtils implements ApplicationContextAware {

   private static ApplicationContext applicationContext = null;

   @Override
   public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
      if (RestClientUtils.applicationContext == null)
         RestClientUtils.applicationContext = applicationContext;
   }

   public static ApplicationContext getApplicationContext() {
      return RestClientUtils.applicationContext;
   }

   /**
    * @since : 发起请求
    * @author : tristan('[email protected]') nowDate: 2018/7/6 20:14
    */
   public static <T> T doRequest(RestTemplate restTemplate, String requestMethod, String url, Object[] args,
         Class<T> responseType, Method method) throws RestClientException{
      // 创建一个特殊的bean,将所有的参数当做属性放到该bean中
      HttpEntity httpEntity = new HttpEntity<>(createCommonBeanDto(args, method));
      if (httpEntity == null)
         httpEntity = HttpEntity.EMPTY;
      ResponseEntity<T> exchange = restTemplate.exchange(url, HttpMethod.valueOf(requestMethod), httpEntity,responseType);
      if (exchange != null)
         return exchange.getBody();
      return null;
   }

   /**
    * @since : 为合并请求对象封装一个公共bean dto
    * @author : tristan('[email protected]') nowDate: 2018/7/7 11:16
    */
   private static Object createCommonBeanDto(Object[] args, Method method)throws RestClientException {
      if (args == null || args.length < 1)
         return null;
      HashMap<String, Object> propertyMap = new HashMap<>(); // 参数配置map
      String[] parameterNames = getParameterNamesByMethod(method);
      // 封装到参数配置map中
      for (int i = 0; i < args.length; i++) {
         Object arg = args[i];
         String parameterName = parameterNames[i];
         propertyMap.put(parameterName, arg.getClass());
      }
      // 创建公共beanDto
      CommonBeanDto bean = new CommonBeanDto(propertyMap);
      // 将数据放到公共beanDto中
      for (int i = 0; i < args.length; i++) {
         String parameterName = parameterNames[i];
         Object arg = args[i];
         bean.setValue(parameterName, arg);
      }
      return bean.getObject();
   }

   /**
    * @since : 获取bean的名称
    * @author : tristan('[email protected]') nowDate: 2018/7/6 21:12
    */
   public static String getBeanNameByClassName(String className) {
      className = className.substring(className.lastIndexOf(".") + 1);
      String subName = className.substring(0, 1);
      String sufName = className.substring(1, className.length());
      return subName.toLowerCase() + sufName;
   }

   /**
    * @since : 通过class去获取对应bean
    * @author : tristan('[email protected]') nowDate: 2018/7/7 9:22
    */
   public static <T> T getBeanByClass(Class<T> tClass) {
      return getApplicationContext().getBean(tClass);
   }

   /**
    * @since : 得到注解到参数上的名称
    * @author : tristan('[email protected]') nowDate: 2018/7/7 14:52
    */
   public static <T> String[] getParameterNamesByMethod(Method method) throws RestClientException{
      ArrayList<String> resultList = new ArrayList<>();
      Class<?>[] parameterTypes = method.getParameterTypes();
      Annotation[][] parameterAnnotations = method.getParameterAnnotations();
      for (int i1 = 0; i1 < parameterAnnotations.length; i1++) { //多个参数
         Annotation[] parameterAnnotation = parameterAnnotations[i1];
         for (int i = 0; i < parameterAnnotation.length; i++) {
            Annotation annotation = parameterAnnotation[i];
            if (annotation instanceof Param) {
               Param param = (Param) annotation;
               resultList.add(param.value());
               break;
            }else{
               if (i==parameterAnnotation.length-1){ //到了最后一个还没有设置值,使用默认值
                  Class<?> returnType = parameterTypes[i1];
                  String name = returnType.getName();
                  if (!name.startsWith("java.lang")){
                     resultList.add(getBeanNameByClassName(name));
                  }else{
                     throw new RestClientException("基础数据类型无法设置默认bean名称");
                  }
               }
            }
         }
      }
      return resultList.toArray(new String[resultList.size()]);
   }
}

CommonBeanDto:

package com.zteict.cbm.basicdata.yearCaryover.RestClient;

import java.util.Iterator;
import java.util.Map;
import java.util.Set;

import org.springframework.cglib.beans.BeanGenerator;
import org.springframework.cglib.beans.BeanMap;

/**
 * @since : 公共beanDto
 * @author : tristan('[email protected]') nowDate: 2018/7/7 10:15
 */
public class CommonBeanDto {
    public Object object = null;

    public BeanMap beanMap = null;

    public CommonBeanDto() {
        super();
    }

    public CommonBeanDto(Map propertyMap) {
        this.object = createDtoBean(propertyMap);
        this.beanMap = BeanMap.create(this.object);
    }

    /**
     * set方法
     * 
     * @param property
     *            属性名
     * @param value
     *            值
     */
    public void setValue(String property, Object value) {
        beanMap.put(property, value);
    }

    /**
     * get方法
     * 
     * @param property
     *            属性名
     * @return 值
     */
    public Object getValue(String property) {
        return beanMap.get(property);
    }

    /**
     * 获取Dto
     * 
     * @return
     */
    public Object getObject() {
        return this.object;
    }

    private Object createDtoBean(Map propertyMap) {
        BeanGenerator generator = new BeanGenerator();
        Set keySet = propertyMap.keySet();
        for (Iterator i = keySet.iterator(); i.hasNext();) {
            String key = (String) i.next();
            generator.addProperty(key, (Class) propertyMap.get(key));
        }
        return generator.create();
    }
}

测试

TestRestClient:

package com.zteict.cbm.basicdata.yearCaryover;

import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;

import com.zteict.cbm.basicdata.yearCaryover.RestClient.RestClient;

@RestClient("base-service")
@RequestMapping("/posStruElementsService")
public interface TestRestClient {
   @RequestMapping(value = "/testFeign", method = RequestMethod.POST)
   YearCaryover testFeignClient(@RequestBody YearCaryover yearCaryover);
}

PosStruElementsController:

@RestController
@RequestMapping(value = "/posStruElementsService")
public class PosStruElementsController {
    @RequestMapping(value = "/testFeign", method = RequestMethod.POST)
    public YearCaryover testFeignClient(@RequestBody YearCaryover yearCaryover , HttpServletRequest httpServletRequest) throws Exception {
        System.out.println("yearCaryover = " + yearCaryover);
        yearCaryover.setItemName(yearCaryover.getItemCode() + "---");
        Enumeration<String> parameterNames = httpServletRequest.getParameterNames();
        while (parameterNames.hasMoreElements()) {
            String s = parameterNames.nextElement();
            String parameter = httpServletRequest.getParameter(s);
            System.out.println("parameter = " + parameter);
        }
        return yearCaryover;
    }
}

YearCaryoverController:

@RestController
@RequestMapping("/yearCaryover")
public class YearCaryoverController {

    @Autowired
    private TestRestClient testRestClient;

    @RequestMapping("/testFeign")
    public String testFeign()throws Exception{
        YearCaryover yearCaryover = new YearCaryover();
        yearCaryover.setItemCode("ic123");
        testRestClient.testFeignClient(yearCaryover);
        return LocalDateTime.now().toString();
    }
}

猜你喜欢

转载自blog.csdn.net/qq_35559756/article/details/80945805
今日推荐