背景:
我们在使用openFeign时只定义了相关的接口,并没有定义对应的实现类,但是最终实现了服务的远程调用。
最终实现经过了以下几个步骤:
1.Spring在启动的时候对接口特定的注解进行了扫描@FeignClient(value = "provider"),之后生成了该接口的代理类。
2.在proxy代理类中,获取到接口上注解FeignClient注解的value值,之后去注册中心(如eureka、nacos、zk)获取对应服务的ip及端口。
3.在代理类中获取当前调用的method方法上的注解@RequestMapping("/open")的value值,将第二步中的ip与value拼接成一个完整的url
4.使用okhttp或者httpclient调用目标服务的接口
但是这个只适用于公司内部系统之间的调用,因为使用openFeign不适合通过网关的场景,如果需要給外部公司或者系统需要调用,就得通过网关掉用,但是网关通常会有很多的鉴权参数之类的信息,所以需要额外的定制业务。或者各个外部系统自行实现其客户端的初始化。非常麻烦。
所以本文主要是参考openFeign,实现一个对外的接口的sdk,让客户只需要像openFeign一样实现一个接口,并且打上对应注解就行,不需要其关心其他的信息
实现方式:
1. 定义扫描包注解
该注解为了提示springboot在启动时,发现注解里面Import导入TestRegisterScan类,之后会自动解析此类
@Import(TestRegisterScan.class)
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE})
@Documented
public @interface TestScan {
String back() default "";
}
@SpringBootApplication
back为扫描的包路径
@TestScan(back = "com.example.test1.bean")
public class Test1Application{
public static void main(String[] args) {
SpringApplication spring = new SpringApplication(Test1Application.class);
ConfigurableApplicationContext run = spring.run(args);
}
}
2.TestRegisterScan类实现
spring解析该类时发现该注册类实现了ImportBeanDefinitionRegistrar接口,并重写了registerBeanDefinitions方法
public class TestRegisterScan implements ImportBeanDefinitionRegistrar {
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
//取到springApplication上的注解对象
AnnotationAttributes annotationAttributes =
AnnotationAttributes.fromMap(importingClassMetadata.getAnnotationAttributes(TestScan.class.getName()));
if(annotationAttributes != null){
List<String> basePackages = new ArrayList<>();
basePackages.addAll(Arrays.stream(annotationAttributes.getStringArray("back")).filter(StringUtils::hasText).collect(Collectors.toList()));
//TestClassPathScan为自定义扫描类
TestClassPathScan scan = new TestClassPathScan(registry);
scan.doScan(StringUtils.collectionToCommaDelimitedString(basePackages));
}
}
}
3.实现自定义扫描类
该类继承ClassPathBeanDefinitionScanner扫描类,在实例化时将BeanDefinitionRegistry传入,并将其放入父类的构造方法。重写里面的doScan方法,在调用时使用扫描包路径调用父类的doScan对该包路径进行扫描,扫描出符合isCandidateComponent的接口或者类,并返回其beanDefinitionHolders(bean定义的持有者),该持有者中有其BeanDefinition(bean的定义)。之后对其BeanDefinition进行处理(参考processBeanDefinitions方法),在这里将BeanDefinition中的实例化工厂bean替换成自定义的工厂bean(非常关键)
public class TestClassPathScan extends ClassPathBeanDefinitionScanner {
public TestClassPathScan(BeanDefinitionRegistry registry) {
super(registry);
}
//不加重写这个方法就无法扫描出接口
@Override
protected boolean isCandidateComponent(AnnotatedBeanDefinition beanDefinition) {
// 增加过滤,为接口类,并且接口上包含TestAnnotation注解
return beanDefinition.getMetadata().isInterface() &&
beanDefinition.getMetadata().isIndependent() &&
beanDefinition.getMetadata().hasAnnotation(TestAnnotation.class.getName()) || !beanDefinition.getMetadata().isInterface();
}
protected Set<BeanDefinitionHolder> doScan(String... basePackages) {
// spring默认不会扫描接口,此处设置为true,不做过滤
this.addIncludeFilter((metadataReader, metadataReaderFactory) -> true);
Set<BeanDefinitionHolder> beanDefinitionHolders = super.doScan(basePackages);
if(beanDefinitionHolders.isEmpty()){
System.out.println("没有扫描到包" + basePackages[0]);
}else{
processBeanDefinitions(beanDefinitionHolders);
}
return beanDefinitionHolders;
}
public void processBeanDefinitions(Set<BeanDefinitionHolder> definitionHolders){
for(BeanDefinitionHolder beanDefinitionHolder : definitionHolders){
GenericBeanDefinition beanDefinition = (GenericBeanDefinition) beanDefinitionHolder.getBeanDefinition();
String beanClassName = beanDefinition.getBeanClassName();
//偷天换日,将其换成自定义的
beanDefinition.setBeanClass(TestFactory.class);
//实例化工厂bean时构造方法参数入参新增为bean的名称,这部也必须有
beanDefinition.getConstructorArgumentValues().addGenericArgumentValue(beanClassName);
}
}
}
4.工厂bean的定义
在构造方法可以获取到bean的class对象,之后spring在用到该bean时,会根据bean的名称或者类型找到该工厂bean,最后调用getObject方法获取bean
public class TestFactory<T> implements FactoryBean<T> {
private Class<T> beanClass;
public TestFactory(Class<T> clazz){
this.beanClass = clazz;
}
@Override
public T getObject() throws Exception {
System.out.println("-----类名------" + beanClass.getName() + "-------" + Arrays.toString(beanClass.getInterfaces()));
//为接口用上面的代理 这个参数要是接口,若代理类本身就是接口直接使用new Class[]{beanClass},不为接口才getInterfaces
return (T) Proxy.newProxyInstance(beanClass.getClassLoader(), new Class[]{beanClass},
new TestProxy(beanClass));
//非接口代理
//T bean = (T) beanClass.getConstructor().newInstance();
//return (T) Proxy.newProxyInstance(beanClass.getClassLoader(), bean.getClass().getInterfaces(),new TestProxy(bean));
}
@Override
public Class<?> getObjectType() {
return null;
}
}
5.代理类定义
最后在代理类的invoke方法中,实现各自接口的代理实现,当代理对象调用方法时会调用其invoke方法,这时可通过method获取代理接口的注解,之后解析类注解和自定义注解获取完整路径,之后组装相关的参数(鉴权或者其他前置的自定义的业务),最后使用httpclient或者okhttp进行远程调用并返回
public class TestProxy<T> implements InvocationHandler {
private Class<T> beanClass;
private T bean;
//代理非接口使用
public TestProxy(T bean) {
this.bean = bean;
}
//代理接口使用
public TestProxy(Class<T> beanClass) {
this.beanClass = beanClass;
}
@Override
public Object invoke(Object o, Method method, Object[] objects) throws Throwable {
System.out.println("--x--"+beanClass.getName());
//TestAnnotation注解只是用于标识代理的是否为接口,仅测试中用
TestAnnotation annotation = beanClass.getAnnotation(TestAnnotation.class);
System.out.println(bean.getClass().getName());
if(ObjectUtils.isEmpty(annotation)){
System.out.println("------非注解接口代理------" + bean);
return method.invoke(bean, objects);
}else{
//代理类业务实现后使用httpclient或okhttp进行远程调用
System.out.println("------注解接口代理-----");
return (Integer)objects[0] + 100;
}
}
}
最终效果
测试类:
//spring中获取接口代理类
ConfigurableApplicationContext run = spring.run(args);
Object zzz = run.getBean("testzzzInterface");
System.out.println("---返回值----" + ((testzzzInterface)zzz).getTest1(1));
//被代理类定义
@TestAnnotation
public interface testzzzInterface {
int getTest1(int k);
}