Remoting组件实现
- 1. 前言
- 2. 原理说明
- 3. 远程调用组件实现---自定义注解
- 3.1 添加Spring依赖
- 3.2 编写@EnableRemoting注解
- 3.3 编写@RemoteClient注解
- 3.4 编写@GetMapping注解
- 4. 远程调用组件实现---生成代理类
- 4.1 编写自定义BeanDefinition注册器
- 4.2 编写自定义包扫描器
- 4.3 编写FactoryBean
- 4.4 编写远程调用接口的默认实现
- 4.5 http调用工具类
- 4.6 启动类上添加@EnableRemoting注解
- 5. 成果展现
1. 前言
今日有一个需求中需要调用其他服务的接口,由于该项目并非SpringCloud项目,所以先使用OkHttp实现远程调用,但是总觉得有点low,于是想手写一个远程调用组件,当然其实你不自己写,使用OpenFeign也是可以的。
关于以下示例代码,若是复制过去有爆红的可以自行修改,比较简单的代码了。
2. 原理说明
项目启动时,扫描带有自定义注解的接口,创建动态代理,并注册到IOC容器。
其实OpenFeign的原理也是如此,只不过OpenFeign中逻辑复杂,但最核心不外乎动态代理、注册BeanDefinition
3. 远程调用组件实现—自定义注解
3.1 添加Spring依赖
新建Module:Remoting,添加以下依赖:
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
<!-- okhttp -->
<dependency>
<groupId>com.squareup.okhttp3</groupId>
<artifactId>okhttp</artifactId>
</dependency>
3.2 编写@EnableRemoting注解
此注解作用与OpenFeign的@EnableFeignClients作用一致,执行一些初始化操作,例如扫描我们自定义@RemoteClient注解。
com.xczs.remoting.register.RemoteClientsRegistrar;
import org.springframework.context.annotation.Import;
import java.lang.annotation.*;
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Import(RemoteClientsRegistrar.class)
public @interface EnableRemoting {
/**
* Alias for the {@link #basePackages()} attribute. Allows for more concise annotation
* declarations e.g.: {@code @ComponentScan("org.my.pkg")} instead of
* {@code @ComponentScan(basePackages="org.my.pkg")}.
* @return the array of 'basePackages'.
*/
String[] value() default {};
/**
* Base packages to scan for annotated components.
* <p>
* {@link #value()} is an alias for (and mutually exclusive with) this attribute.
* <p>
* @return the array of 'basePackages'.
*/
String[] basePackages() default {};
}
3.3 编写@RemoteClient注解
此注解作用与OpenFeign的@FeignClient作用一致,用于标识我们需要生成代理的接口,创建代理类并注册到Spring的IOC容器。
import java.lang.annotation.*;
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface RemoteClient {
String value() default "";
/**
* @return 绝对 URL 或可解析的主机名(协议是可选的)。
*/
String url() default "";
}
3.4 编写@GetMapping注解
用于标识该方法是get方法。
import java.lang.annotation.*;
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface GetMapping {
String value() default "";
}
4. 远程调用组件实现—生成代理类
4.1 编写自定义BeanDefinition注册器
RemoteClientsRegistrar.java
import com.xczs.remoting.annotation.EnableRemoting;
import com.xczs.remoting.scanner.RemoteClientClassPathBeanDefinitionScanner;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.context.annotation.ImportBeanDefinitionRegistrar;
import org.springframework.core.annotation.AnnotationAttributes;
import org.springframework.core.type.AnnotationMetadata;
import java.util.Map;
import java.util.Objects;
public class RemoteClientsRegistrar implements ImportBeanDefinitionRegistrar {
@Override
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
Map<String, Object> attributes = importingClassMetadata.getAnnotationAttributes(EnableRemoting.class.getName());
AnnotationAttributes annotationAttributes = AnnotationAttributes.fromMap(attributes);
String[] basePackages = null;
if (Objects.nonNull(annotationAttributes.getStringArray("value"))) {
basePackages = annotationAttributes.getStringArray("value");
}
if (Objects.isNull(basePackages) || basePackages.length == 0) {
basePackages = (String[])attributes.get("basePackages");
}
if (Objects.isNull(basePackages) || basePackages.length == 0) {
throw new IllegalArgumentException("@EnableRemoting注解value或basePackages属性不可同时为空.");
}
RemoteClientClassPathBeanDefinitionScanner scanner = new RemoteClientClassPathBeanDefinitionScanner(registry, false);
scanner.doScan(basePackages);
}
}
4.2 编写自定义包扫描器
RemoteClientClassPathBeanDefinitionScanner.java
import com.xczs.remoting.annotation.RemoteClient;
import com.xczs.remoting.bean.RemoteClientFactoryBean;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.AnnotatedBeanDefinition;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.config.BeanDefinitionHolder;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.beans.factory.support.GenericBeanDefinition;
import org.springframework.context.annotation.ClassPathBeanDefinitionScanner;
import org.springframework.core.type.AnnotationMetadata;
import org.springframework.core.type.filter.AnnotationTypeFilter;
import org.springframework.util.Assert;
import java.util.Set;
@Slf4j
public class RemoteClientClassPathBeanDefinitionScanner extends ClassPathBeanDefinitionScanner {
public RemoteClientClassPathBeanDefinitionScanner(BeanDefinitionRegistry registry, boolean useDefaultFilters) {
super(registry, useDefaultFilters);
}
@Override
public Set<BeanDefinitionHolder> doScan(String... basePackages) {
// 添加过滤器, 只扫描添加了 RemoteClient 注解的类
addIncludeFilter(new AnnotationTypeFilter(RemoteClient.class));
Set<BeanDefinitionHolder> beanDefinitionHolderSet = super.doScan(basePackages);
// 对扫描到的数据进行代理处理
processBeanDefinitions(beanDefinitionHolderSet);
return beanDefinitionHolderSet;
}
@Override
protected boolean isCandidateComponent(AnnotatedBeanDefinition beanDefinition) {
return beanDefinition.getMetadata().isInterface() && beanDefinition.getMetadata().isIndependent();
}
private void processBeanDefinitions(Set<BeanDefinitionHolder> beanDefinitionHolderSet) {
beanDefinitionHolderSet.forEach(beanDefinitionHolder -> {
BeanDefinition beanDefinition = beanDefinitionHolder.getBeanDefinition();
if (beanDefinition instanceof AnnotatedBeanDefinition) {
AnnotationMetadata annotationMetadata = ((AnnotatedBeanDefinition) beanDefinition).getMetadata();
Assert.isTrue(annotationMetadata.isInterface(), "@RemoteClient只能使用在接口上");
}
// 设置工厂等操作需要基于GenericBeanDefinition, BeanDefinitionHolder是其子类
GenericBeanDefinition definition = (GenericBeanDefinition) beanDefinition;
// 获取接口的全路径名称
String beanClassName = definition.getBeanClassName();
// 设置构造函数参数
definition.getConstructorArgumentValues().addGenericArgumentValue(beanClassName);
// 设置工厂
definition.setBeanClass(RemoteClientFactoryBean.class);
definition.setAutowireMode(GenericBeanDefinition.AUTOWIRE_BY_TYPE);
});
}
}
4.3 编写FactoryBean
RemoteClientFactoryBean.java
import org.springframework.beans.factory.FactoryBean;
import java.lang.reflect.Proxy;
public class RemoteClientFactoryBean<T> implements FactoryBean<T> {
private Class<T> type;
public RemoteClientFactoryBean(Class<T> type) {
this.type = type;
}
@Override
public T getObject() throws Exception {
// 因为 DefaultRemoteClient 需要Class<T>作为参数, 所以该类包含一个Class<T>的成员, 通过构造函数初始化
return (T) Proxy.newProxyInstance(type.getClassLoader(), new Class[]{type},
new DefaultRemoteClient<>(type));
}
@Override
public Class<?> getObjectType() {
// 该方法返回的getObject()方法返回对象的类型,这里是基于type生成的代理对象, 所以类型就是上面定义的type
return type;
}
}
4.4 编写远程调用接口的默认实现
DefaultRemoteClient.java
import com.xczs.core.utils.OkHttpUtils;
import com.xczs.remoting.annotation.GetMapping;
import com.xczs.remoting.annotation.RemoteClient;
import lombok.extern.slf4j.Slf4j;
import okhttp3.Response;
import java.lang.annotation.Annotation;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
@Slf4j
public class DefaultRemoteClient<T> implements InvocationHandler {
/**
* 这里声明一个Class, 用来接收接口声明的泛型实际类型的class, T是声明的实体类类型
*/
private Class<T> type;
public DefaultRemoteClient(Class<T> type) {
this.type = type;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// Object 方法,走原生方法, 比如 hashCode()
if (Object.class.equals(method.getDeclaringClass())) {
return method.invoke(this, args);
}
// 其它走动态代理
Class<?> declaringClass = method.getDeclaringClass();
RemoteClient remoteClient = declaringClass.getAnnotation(RemoteClient.class);
String domain = remoteClient.url();
Annotation[] annotations = method.getAnnotations();
for (Annotation annotation : annotations) {
if (annotation instanceof GetMapping) {
GetMapping getAnno = (GetMapping) annotation;
String uri = getAnno.value();
String url = domain + uri;
Response response = OkHttpUtils.doExecuteGet(url);
String resp = response.body().string();
return resp;
}
}
return null;
}
}
4.5 http调用工具类
OkHttpUtils工具类
import lombok.extern.slf4j.Slf4j;
import okhttp3.*;
import java.io.IOException;
import java.util.Objects;
import java.util.concurrent.TimeUnit;
@Slf4j
public class OkHttpUtils {
private static OkHttpClient okHttpClient = null;
private static final MediaType mediaType = MediaType.parse(org.springframework.http.MediaType.APPLICATION_JSON_VALUE);
public static Response doExecuteGet(String url) throws IOException {
return doExecuteGet(url, null);
}
public static Response doExecuteGet(String url, Headers headers) throws IOException {
init();
// 创建request对象
Request.Builder builder = new Request.Builder().url(url);
if (Objects.nonNull(headers)) {
builder.headers(headers);
}
Request request = builder.build();
Response response = okHttpClient.newCall(request).execute();
if (!response.isSuccessful()) {
log.error("OkHttpUtils.doExecuteGet:{}", response.body().string());
throw new RPCException(response.code() + "", response.message());
}
return response;
}
public static Response doExecutePost(String url, Object body) throws IOException {
return doExecutePost(url, null, body);
}
public static Response doExecutePost(String url, Headers headers, Object body) throws IOException {
init();
RequestBody requestBody = RequestBody.create(mediaType, JSONUtil.writeValueAsString(body));
// 创建request对象
Request.Builder builder = new Request.Builder().url(url).post(requestBody);
if (Objects.nonNull(headers)) {
builder.headers(headers);
}
Request request = builder.build();
Response response = okHttpClient.newCall(request).execute();
if (!response.isSuccessful()) {
log.error("OkHttpUtils.doExecutePost:{}", response.body().string());
throw new RPCException(response.message());
}
return response;
}
private static synchronized void init() {
if (Objects.isNull(okHttpClient)) {
okHttpClient = new OkHttpClient.Builder()
.connectTimeout(15, TimeUnit.SECONDS)
.readTimeout(60, TimeUnit.SECONDS)
.writeTimeout(60, TimeUnit.SECONDS)
.build();
}
}
}
4.6 启动类上添加@EnableRemoting注解
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
@EnableRemoting(basePackages = {"com"})
public class TestApplication {
public static void main(String[] args) {
SpringApplication.run(TestApplication.class, args);
}
}
5. 成果展现
服务提供方:
远程调用接口:
测试类:
调用结果: