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调用工具类
- 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 {@Overridepublic 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);}@Overridepublic Set<BeanDefinitionHolder> doScan(String... basePackages) {// 添加过滤器, 只扫描添加了 RemoteClient 注解的类addIncludeFilter(new AnnotationTypeFilter(RemoteClient.class));Set<BeanDefinitionHolder> beanDefinitionHolderSet = super.doScan(basePackages);// 对扫描到的数据进行代理处理processBeanDefinitions(beanDefinitionHolderSet);return beanDefinitionHolderSet;}@Overrideprotected 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;}@Overridepublic T getObject() throws Exception {// 因为 DefaultRemoteClient 需要Class<T>作为参数, 所以该类包含一个Class<T>的成员, 通过构造函数初始化return (T) Proxy.newProxyInstance(type.getClassLoader(), new Class[]{type},new DefaultRemoteClient<>(type));}@Overridepublic Class<?> getObjectType() {// 该方法返回的getObject()方法返回对象的类型,这里是基于type生成的代理对象, 所以类型就是上面定义的typereturn 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;}@Overridepublic 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 com.xczs.core.exception.RPCException;
import com.xczs.core.json.JSONUtil;
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();}}}
5. 成果展现
服务提供方:
远程调用接口:
测试类:
调用结果: