今日有一个需求中需要调用其他服务的接口,由于该项目并非SpringCloud项目,所以先使用OkHttp实现远程调用,但是总觉得有点low,于是想手写一个远程调用组件,当然其实你不自己写,使用OpenFeign也是可以的。
项目启动时,扫描带有自定义注解的接口,创建动态代理,并注册到IOC容器。
其实OpenFeign的原理也是如此,只不过OpenFeign中逻辑复杂,但最核心不外乎动态代理、注册BeanDefinition
新建Module:Remoting,添加以下依赖:
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
此注解作用与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 {};
}
此注解作用与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 "";
}
用于标识该方法是get方法。
import java.lang.annotation.*;
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface GetMapping {
String value() default "";
}
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);
}
}
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);
});
}
}
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;
}
}
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;
}
}
服务提供方:
远程调用接口:
测试类:
调用结果: