版权声明:本文为博主原创文章,未经博主允许不得转载。 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();
}
}