Forest 是一个开源的 Java HTTP 客户端框架,它能够将 HTTP 的所有请求信息(包括 URL、Header 以及 Body 等信息)绑定到您自定义的 Interface 方法上,能够通过调用本地接口方法的方式发送 HTTP 请求。
https://gitee.com/dromara/forest
<dependency>
<groupId>com.dtflys.forestgroupId>
<artifactId>forest-spring-boot-starterartifactId>
<version>1.5.28version>
dependency>
@RestController
public class IndexController {
@Value(value = "${spring.application.name}")
private String applicationName; //demo
@GetMapping("/index")
public String index() {
return "您好,欢迎访问【" + applicationName + "】";
}
@GetMapping("/hello")
public String hello(@RequestParam(required = false) String msg) throws InterruptedException {
// 模拟业务耗时处理流程
// Thread.sleep(2 * 1000L);
return "hello: " + msg;
}
}
@Address(host = "127.0.0.1", port = "9098")
public interface MyForestClient {
/**
* 本地测试接口
*/
@Get(url = "/index")
String index();
@Get(url = "/hello?msg=${msg}")
String hello(@DataVariable("msg") String msg);
}
@SpringBootTest
public class ForestTest {
@Autowired
private MyForestClient myClient;
@Test
public void testForest() throws Exception {
// 调用接口
String index = myClient.index();
System.out.println(index);
String hello = myClient.hello("测试...");
System.out.println(hello);
}
}
resources/META-INF/spring.factories
将加载对应的文件中写的bean,ForestAutoConfiguration
。该类会引入ForestBeanRegister
。@Configuration
@EnableConfigurationProperties({ForestConfigurationProperties.class})
@Import({ForestScannerRegister.class})
public class ForestAutoConfiguration {
//...
@Bean
@DependsOn("forestBeanProcessor")
@ConditionalOnMissingBean
public ForestBeanRegister forestBeanRegister(SpringForestProperties properties,
SpringForestObjectFactory forestObjectFactory,
SpringInterceptorFactory forestInterceptorFactory,
ForestConfigurationProperties forestConfigurationProperties) {
ForestBeanRegister register = new ForestBeanRegister(
applicationContext,
forestConfigurationProperties,
properties,
forestObjectFactory,
forestInterceptorFactory);
register.registerForestConfiguration();
register.registerScanner();
return register;
}
@Import({ForestScannerRegister.class})
,会找到所有匹配的包,若 @ForestScan
注解未定义扫描包名,则扫描整个项目。ForestBeanRegister#registerScanner
会进行扫描注册,使用了自定义的扫描器ClassPathClientScanner
,扫描匹配的方法是ClassPathClientScanner#interfaceFilter
,将扫描好的匹配类生成对应的实体Bean,注册到容器中。ClassPathClientScanner#processBeanDefinitions
。往容器中注入Class为ClientFactoryBean
的Bean。 private void processBeanDefinitions(Set<BeanDefinitionHolder> beanDefinitions) {
GenericBeanDefinition definition;
for (BeanDefinitionHolder holder : beanDefinitions) {
definition = (GenericBeanDefinition) holder.getBeanDefinition();
String beanClassName = definition.getBeanClassName();
ClientFactoryBeanUtils.setupClientFactoryBean(definition, configurationId, beanClassName);
logger.info("[Forest] Created Forest Client Bean with name '" + holder.getBeanName()
+ "' and Proxy of '" + beanClassName + "' client interface");
}
}
ClientFactoryBean#getObject
的实体类。返回的是ForestConfiguration#createInstance
。生成代理类,使用的拦截拦截类是InterfaceProxyHandler。 public <T> T createInstance(Class<T> clazz) {
ProxyFactory<T> proxyFactory = this.getProxyFactory(clazz);
return proxyFactory.createInstance();
}
//ProxyFactory#createInstance
public T createInstance() {
T instance = this.configuration.getInstanceCache().get(this.interfaceClass);
boolean cacheEnabled = this.configuration.isCacheEnabled();
if (cacheEnabled && instance != null) {
return instance;
} else {
synchronized(this.configuration.getInstanceCache()) {
instance = this.configuration.getInstanceCache().get(this.interfaceClass);
if (cacheEnabled && instance != null) {
return instance;
} else {
InterfaceProxyHandler<T> interfaceProxyHandler = new InterfaceProxyHandler(this.configuration, this, this.interfaceClass);
instance = Proxy.newProxyInstance(this.interfaceClass.getClassLoader(), new Class[]{this.interfaceClass, ForestClientProxy.class}, interfaceProxyHandler);
if (cacheEnabled) {
this.configuration.getInstanceCache().put(this.interfaceClass, instance);
}
return instance;
}
}
}
}
InterfaceProxyHandler
该拦截类主要根据拦截方法,解析对应的注解信息,进行http请求,最核心的就是调用okhttp3的请求。 public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
String methodName = method.getName();
if (method.isDefault()) {
return this.invokeDefaultMethod(proxy, method, args);
} else {
ForestMethod forestMethod = (ForestMethod)this.forestMethodMap.get(method);
if (forestMethod != null) {
return forestMethod.invoke(args);
} else {
if (args == null || args.length == 0) {
InterfaceProxyHandler.NonParamsInvocation invocation = getNonParamsInvocation(methodName);
if (invocation != null) {
return invocation.invoke(this, proxy);
}
}
if (args != null && args.length == 1) {
if ("equals".equals(methodName)) {
Object obj = args[0];
if (Proxy.isProxyClass(obj.getClass())) {
InvocationHandler h1 = Proxy.getInvocationHandler(proxy);
InvocationHandler h2 = Proxy.getInvocationHandler(obj);
return h1.equals(h2);
}
return false;
}
if ("wait".equals(methodName) && args[0] instanceof Long) {
proxy.wait((Long)args[0]);
}
}
if (args != null && args.length == 2 && args[0] instanceof Long && args[1] instanceof Integer && "wait".equals(methodName)) {
proxy.wait((Long)args[0], (Integer)args[1]);
}
throw new NoSuchMethodError(method.getName());
}
}
}