首先应了解Feign远程调用的原理, 参考文章 Feign远程调用过程 初始化/动态代理/负载均衡
基本原理:在Application启动类中注入Enable,注解中Import导入ImportBeanDefinitionRegistrar实现类,在实现类中即可完成对BeanDefinition的注入,然后BeanDefinition的BeanClass设置成Feign的代理类,当调用方法时,调用代理对象的方法,完成http远程调用。MyBatis的原理也是如此,只不过需要解析接口上@Select等注解而已。
本文直接用了POST发起远程请求,并且服务发起方和提供方在同一个服务中,只做示例,其他类型请求可以参考扩展实现。
创建注解
- 开启注解,导入RmiScannerRegistrar类
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Import(RmiScannerRegistrar.class)
public @interface RmiEnable {
String basePackage();
}
- 类注解 用于扫描过滤
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface RmiClient {
}
- 方法注解
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface RmiMethod {
String path();
HttpMethod method() default HttpMethod.POST;
}
动态代理
JDK动态代理,相关知识,请跳转到 Spring AOP动态代理(JDK与CGLib)及类内部方法事务不生效原因解析
import com.alibaba.fastjson.JSON;
import com.cloud.demo.annotation.RmiMethod;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.RequestBody;
import okhttp3.Response;
import org.springframework.beans.factory.FactoryBean;
import java.io.IOException;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.Objects;
import java.util.concurrent.TimeUnit;
public class RmiFactoryBean<T> implements FactoryBean<T>, InvocationHandler {
// 对象存储目标类型
private Class<T> rmi;
// 构造方法传入目标类型
public RmiFactoryBean(Class<T> rmi) {
this.rmi = rmi;
}
@Override
public T getObject() throws Exception {
// 生成代理对象
return (T) Proxy.newProxyInstance(rmi.getClassLoader(), new Class[]{rmi}, this);
}
@Override
public Class<?> getObjectType() {
// 返回目标类型
return rmi;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
RmiMethod rmiMethod = method.getAnnotation(RmiMethod.class);
if (rmiMethod != null) {
System.out.println("rmi request: " + rmiMethod.path() + " " + rmiMethod.method());
String requestJson = JSON.toJSONString(args[0]);
System.out.println("rmi params: " + requestJson);
String result = post(rmiMethod.path(), requestJson.getBytes()); // 示例只发送了POST请求
System.out.println("rmi response: " + result);
return JSON.parseObject(result, method.getReturnType());
}
return null;
}
private static OkHttpClient okHttpClient = new OkHttpClient().newBuilder().build();
// POST请求示例方法
private static String post(String url, byte[] body) {
Request.Builder builder = new Request.Builder();
builder.addHeader("content-Type", "application/json");
builder.url(url).post(RequestBody.create(body));
try {
Response response = okHttpClient.newCall(builder.build()).execute();
return Objects.requireNonNull(response.body()).string();
} catch (IOException e) {
e.printStackTrace();
}
return null;
}
}
RmiScannerRegistrar
借助Spring的ImportBeanDefinitionRegistrar利器,在Spring初始化时注入相关BeanDefinition,必须为FactoryBean,以便于生成动态代理完成远程调用。
import com.cloud.demo.annotation.RmiClient;
import com.cloud.demo.annotation.RmiEnable;
import org.springframework.beans.factory.annotation.AnnotatedBeanDefinition;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.beans.factory.support.GenericBeanDefinition;
import org.springframework.context.annotation.ClassPathScanningCandidateComponentProvider;
import org.springframework.context.annotation.ImportBeanDefinitionRegistrar;
import org.springframework.core.type.AnnotationMetadata;
import org.springframework.core.type.filter.AnnotationTypeFilter;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
public class RmiScannerRegistrar implements ImportBeanDefinitionRegistrar {
@Override
public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
// 判断注解是否有RmiScan注解 如果有获取路径
Map<String, Object> attrs = metadata.getAnnotationAttributes(RmiEnable.class.getName());
if (attrs != null) {
String basePackage = String.valueOf(attrs.get("basePackage"));
// 根据包获取下面的类
ClassPathScanningCandidateComponentProvider scanner = new ClassPathScanningCandidateComponentProvider(false) {
@Override
protected boolean isCandidateComponent(AnnotatedBeanDefinition beanDefinition) {
return true;
}
};
scanner.addIncludeFilter(new AnnotationTypeFilter(RmiClient.class));
Set<BeanDefinition> bdSet = scanner.findCandidateComponents(basePackage);
bdSet.forEach(e -> {
GenericBeanDefinition beanDefinition = (GenericBeanDefinition) e;
// 向构造方法中添加参数,值为目标bean的全路径名,Spring会自动转换成Class对象
beanDefinition.getConstructorArgumentValues().addGenericArgumentValue(Objects.requireNonNull(e.getBeanClassName()));
beanDefinition.setBeanClass(RmiFactoryBean.class);
registry.registerBeanDefinition(e.getBeanClassName(), e);
});
}
}
}
SpringWebApplication
// 注解添加扫描包
@RmiEnable(basePackage = "com.cloud.demo.rmi")
@SpringBootApplication(exclude= {DataSourceAutoConfiguration.class})
public class SpringWebApplication {
public static void main(String[] args) {
SpringApplication.run(SpringWebApplication.class, args);
}
}
pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.cloud.demo</groupId>
<artifactId>parent</artifactId>
<packaging>pom</packaging>
<version>1.0-SNAPSHOT</version>
<modules>
<module>provider</module>
<module>core-service</module>
<module>eureka</module>
<module>sharding_jdbc</module>
<module>spring</module>
</modules>
<properties>
<springframework.boot.version>2.1.2.RELEASE</springframework.boot.version>
<springframework.cloud.version>Greenwich.RELEASE</springframework.cloud.version>
</properties>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>${springframework.cloud.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-dependencies</artifactId>
<version>${springframework.boot.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>1.3.2</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.23</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.60</version>
</dependency>
</dependencies>
</dependencyManagement>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<source>1.8</source>
<target>1.8</target>
</configuration>
</plugin>
</plugins>
</build>
</project>
示例
LoginRmi
@RmiClient
public interface LoginRmi {
@RmiMethod(path = "http://127.0.0.1:8080/login", method = HttpMethod.POST)
Result login(@RequestBody Login login);
}
public class Login {
private String username;
private String password;
// getter setter
}
public class Result {
private String code;
private String msg;
// getter setter
}
@RestController
public class IndexController {
@Autowired
private LoginRmi loginRmi;
@GetMapping("/index")
public Result index() {
Login login = new Login();
login.setUsername("GuanDS");
login.setPassword("PWD");
return loginRmi.login(login);
}
}
@RestController
public class LoginController {
@PostMapping("/login")
public Result login(@RequestBody Login login) {
System.out.println("login request: " + login.getUsername() + " " + login.getPassword());
Result result = new Result();
result.setCode("000000");
result.setMsg("success");
return result;
}
}
运行结果
请求链接 http://127.0.0.1:8080/index
rmi request: http://127.0.0.1:8080/login POST
rmi params: {"password":"PWD","username":"GuanDS"}
login request: GuanDS PWD
rmi response: {"code":"000000","msg":"success"}