MyBatis是怎么整合到Spring的
最近我在做我自己负责的项目的时候有一个类似于Feign远程调用的场景:第三方需要通过Http的形式去调用我负责的中台。那么这样的场景大家思考一下怎么如果你作为一个中台这边的对接开发人,那么你会以什么样的技术方案来解决这个问题?
最开始我的方案很简单就是提供一个Http请求的工具类给调用者,同时将一些验签之类的内容封装到这个里面。调用者拿到这个工具类就可以实现中台的调用了,可以实现一行代码的调用。
但是我总觉得这样的实现不够优雅,对于使用者来说毕竟是硬编码这样对业务代码有比较大的侵入性。
之后我就想到了Feign的远程调用,足够优雅并且代码的耦合性很低,并且接口都是声明式的(这样感觉很高大上)。
那么我们来回忆一下再使用Feign进行远程调用的时候,我们都做了啥?
首先我们需要一个注解来开启Feign的代理功能 @EnableFeignClients 。然后我们需要通过@FeignClient这个注解来将我们定义的接口类注册到Spring的容器里面去,同时在这个注解的元数据里面加上我们要请求的服务的信息。最后定义一个方法来指定我们要调用到的方法名称。
根据Feign这样的调用方式就能够完全地符合我们的场景,我们只需要定义一个接口就能够去调用一个远程的服务。这样我们就能够像调用本地的方法一样去调用远程的方法了,使用者是完全对远程的服务无感知的。区别于传统的Http请求工具类的调用方式,使用者能够很明显地感觉到一个远程调用的过程。
那么问题来了,这么神奇的操作他的背后的原理是什么?
我们现在主要需要搞清楚两个问题:
- 这个接口的定义为什么会被注册到Spring容器?
- 这个接口没有实现类也没有具体的执行逻辑,那么他是怎么实现远程调用的。
我来解释一下这样个问题:
-
首先如果有一定的Spring基础的同学一定知道容器中所有的bean定义在默认的情况下是在容器启动时的扫描器Scanner在doScan方法里面去扫描进去的,具体的逻辑就是扫描指定basePackage路径下的所有class文件,找到这些类中用@Component注解注释过的类(@Service、@Controller也包含了@Component注解)。那么现在的问题是@FeignClient这个注解很明显不是Spring默认能够扫描的,所以Feign一定是做了这一步的处理来将其自定义的注解加入到Spring包扫描时候的includeFilter中去。
-
同样有一定Spring基础的同学肯定知道Spring默认是不会将一个抽象类或者是接口类的BeanDefinition注册到容器里去。在doScan时容器会过滤掉这些无法实例化的类,而是通过注册其子类的BeanDefinition之后实例化其子类bean时合并其父子类的bean定义来达到实现抽象类的功能。那么Feign一定是做了什么处理来达到这样的目的。
其实这样的场景(通过声明接口的形式来达到bean的自动注入和代理)在Spring提供的基础框架以及后续的一些优秀的开源项目中我们都能够看到这样的影子,无一例外的是哪些优秀的开源项目的作者也是使用的这样的方案去解决这样的问题。
比较经典的就是MyBatis。我们来回忆一下我们在使用MyBatis的时候我们都干了啥?
- 首先我们需要使用指定的数据库的url、username、password等等信息来创建一个SqlSession这样的一个bean到容器里面去,之后的sql的执行都是通过这个bean来提交到数据库。
- 之后我们需要@MapperScan注解去指定我们的mapper接口的包路径,同时这个类上还要加上@Configuration。
- 编写xml或者使用注解在mapper的方法上写上sql。
- 通过Service注入Mapper对象来调用具体的数据库操作方法。
那么我们接下来就仔细地研究一下MyBatis是怎么做到这些事情的。在我们的场景中我们只需要研究两个细节:
- 自定义的注解是怎么在启动的时候被注册的Spring容器中的?
- Spring是怎么去创建接口的代理对象的?
自定义的注解是怎么在启动的时候被注册的Spring容器中的?
有点Spring基础的同学都知道Spring的IOC是通过扫描指定的包路径下的类来获取bean的BeanDefinition,之后再通过这个BeanDefinition来创建bean。
那么我们现在首要需要弄清楚的就是@Mapper这个注解注释的bean是在什么时候,在什么地方被注册到容器中的。
一切要从容器的启动说起。
上面说过了在使用MyBatis的功能之前我们需要通过@MapperScan来指定我们要扫描的包路径,所以说这个注解可以理解为开启Mapper注册的开关。我们看一下MapperScan这个类上面的注释:
可以看到上说了这个注解必须要同时加在@Configuration这个注解上。那么这个BeanDefinition的注册过程就一定和@Configuration有关。
那么@Configuration这个注解是做什么用的呢?
当然@Configuration的用法很多,主要是将一些指定的BeanDefinition手动地注册到容器中,我们去看一眼这个注解上面的注释,这里就提供了一种Bean的注入方式,这种方式就是MyBatis在注册自己的Mapper对象所用到的。同时不光是MyBatis,很多其他的第三方的项目都是用这种方式来实现将自己自定义的注解来定义类,从而告诉Spring将这些Bean注入到容器里面去。
从下面的代码中可以看到演示的代码的注入方式是通过@Configuration和@Import注解组合使用,在@Import注解中指定这个配置要注入的类,实例代码给的是一个DatabaseConfig.class。这样可以实现的效果就是将一个和容器无关的类(默认不被注入)注入到容器。
当然MyBatis不仅仅是通过这样的一个配置类就能够实现Mapper自定注入的功能的。我们可以看一下@MapperScan这个注解用@Import了什么。
从上面的代码中可以看到果然MapperScan这个注解默认就通过@Import注入了一个类:MapperScannerRegistrar,从这个名称上来看这是一个注册器,那么显而易见,Mapper的自动注册的功能一定就和这个注册器有关。
注册器的逻辑我们待会再讲,我们先来研究研究@Import这个注解是怎么将指定的类注册到容器里的。
我们上面就说过了@Import这个注解需要跟@Configuration这个注解一起使用才能到达注入指定Bean的效果,那么@Configuration是在什么时候发挥作用的呢?
这里就要从容器启动开始说起了。
容器在启动的时候会通过Scanner来将beanPackage下的所有类全部扫描一遍,然后根据includeFilter将指定注解修饰的类添加到容器的BeanDefinitionMap里面去。在完成了上面的步骤之后就需要对容器进行初始化也就是refresh()。那么在refresh的逻辑里面就有一个对容器进行后置处理的步骤。而@Configuration的后置处理器就是在这里进行调用的。
下面的代码就是容器进行后置处理时候的代码入口,要注意的是容器启动到这里的状态是:BeanDefinition已经被加载(自定义注解的没加载),容器中的Bean还没有被初始化。
在这个方面里面就会调用到@Configuration注解的后置处理器ConfigurationClassPostProcessor,具体的调用方法是postProcessBeanFactory方法。我们忽略掉一些不感兴趣的逻辑,直接看这个方法里面调用的loadBeanDefinitions(),在这个方面里面会间接调用到loadBeanDefinitionsForConfigurationClass()这个方法,那么问题的关键点就在这里了。
我们看一下这个方法的最后一行,loadBeanDefinitionsFromRegistrars。这个方法的字面意思就是从注册器中加载BeanDefinition。这里的目的就和我们上面说到的@MapperScan的意图很吻合了。
我们继续点进去这个方法研究一下,从下图中的代码可以看到这里的逻辑就是调用注册去的registerBeanDefinitions方法来将我们自定义的注解修饰的Bean注册到容器中去。
好的,那么到这里前半部分的逻辑以及很清楚了,这里做一个总结:先声明一个配置类将@Configuration和@Import注解同时使用,同时在@Import注解中指定一个我们自定义注解的注册器(这个注册器的作用我们下面讲)。那么在容器启动的时候就会触发Bean工厂的后置处理,在Bean工厂的后置处理中会调用到@Configuration的后置处理,而@Configuration的后置处理逻辑中我们关心的这部分逻辑就是获取这个类的@Import注解注入的类,如果这个类是注册器的话就调用这个注册器的registerBeanDefinitions方法,将我们自定义的注册注释的类的定义扫描到容器里去。
那么我们接下来继续研究一下@MapperScan注入的注册器是怎么将自定义的@Mapper修饰的类注册到容器里面去的。
我们直接打开MapperScannerRegistrar这个类,这个类的方法入口就是我们上面说到的registerBeanDefinitions方法。在这个方法里面主要做的事情就是获取了@MapperScan注解所配置的元数据,这里面我们最关心的属性就是basePackage。这个basePackage属性就指定了我们定义的Mapper接口的包的路径。
那么我们继续从这个方法的最后一行继续点开方法往下看,可以看到这个方法里面就是构造了一个BeanDefinition,但是很遗憾这个Bean并不是我们的Mapper接口,而是一个Scanner的配置类。通过下面的方法截图可以看到这里通过一个BeanDefinition的构造器指定了MapperScannerConfigurer的类来构造了一个bean定义。
在这个方法的最后我们可以看到这个将之前从@MapperScan中定义的元数据basePackage也设置到这个Bean的定义中了,同时在最后面将这个BeanDefinition注册到容器中。
这里注入的MapperScannerConfigurer这个Bean很重要,里面包含了生成MapperBean的关键信息,比较重要的有basePackage、mapperFactoryBeanClass、sqlSessionFactoryBeanName等等。
那么到这里为止@Configuration的后置处理就结束了。
之后的是刚刚注册的这个MapperScannerConfigurer的处理逻辑。到目前为止处理逻辑中都还没有将我们需要的Mapper的Bean给注册到容器,做的都还是一些准备的工作,接下来才是和我们编写的Mapper直接相关的逻辑。
MapperScannerConfigurer这个类继承了BeanDefinitionRegistryPostProcessor,其实现了postProcessBeanDefinitionRegistry,这个类本身也是一个后置处理器,调用的入口和上面的注册器一样,但是在注册器的后置处理调用之后调用。
我们来看一下这个类的后置处理的逻辑。如下图所示,逻辑主要就是实例化了一个Scanner,并为这个scanner设置了一系列的属性,这些属性都是为之后的scanner扫描类所生成的BeanDefinition做一个属性的填充和完善,可以看到比较重要的几个有代理对象工厂类、sqlSession的工厂、是否懒加载等等。
这里还有一行比较重要的代码scanner.redisterFilters(),这行代码将我们的自定义注解@Mapper注册到scanner的includeFilter中,让我们之后的doScan操作可以扫描到这些接口。
所以到现在为止我们算是有点眉目了,@Mapper之所以能够被自动地扫描到容器中是因为在基础Bean的定义被注册到容器之后,MyBatis通过Bean工厂的后置处理器将文件又扫描了一遍从而找到了需要的@Mapper接口,并将其封装成BeanDefinition注册到容器里面去。
那么之后的逻辑就是doScan扫描指定包的逻辑了。
这部分的代码和Spring启动的时候扫描的代码差不多。但是不同的是MyBatis的扫描器再扫描获取了所有的@Mapper接口的BeanDefinition之后,还进行了一次自定义的后置处理。
这个后置处理里面的逻辑就是将扫描到的所有BeanDefinition全部设置代理工厂对象等等属性。到这里为止才是将我们定义的所有Mapper接口的定义扫描到容器里面去了!
以上的全部就是MyBatis为了实现Mapper接口的自动注入而做的工作。这段流程很经典,很多接入Spring的优秀开源项目都使用这种方式来实现的自动注入!框架源码中以@Enable开头的注解开启某某功能的注解全部都是这种方式来实现,所有很大程度上来说这种注入方式算是一种最佳实现了。
最后我们来总结一下这种自动注入的实现原理:
- 编写一个配置类,在类上加上@Configuration和@Import,用@Import去指定一个注册器。
- 编写一个注册器实现ImportBeanDefinitionRegistrar接口,在registerBeanDefinitions方法里面再向容器中注入一个Scanner扫描器。@Configuration的后置处理在容器刷新的时候调用,并且帮助我们将这个注册器注入到容器。
- 通过一个注解将我们自定义的接口类包地址指定,在Scanner初始化的时候将basePackage和代理工厂设置到scanner中,在完成了我们自定义的注解的扫描之后通过后置处理将代理对象工厂设置到BeanDefinition中去。
代理对象是怎么生成的?
上面的内容完整地介绍了怎么将一个我们自定义的注解修饰的类在容器启动的时候自动注入到容器中。那么接下来的内容就是研究我们注入到容器中的BeanDefinition是怎么生成代理对象的。
首先如果你有一定的SpringIOC的基础就能够知道:
- Spring的接口类就算类上加了@Component注解也是不能被注册到容器中的。
- Spring不可以直接初始化一个接口对象。
第一点我们上面已经研究过了,通过一些列的手段我们最终是将一个接口类的BeanDefinition注册到了容器中,但是接口注定是接口是没有办法直接通过反射或者是new来创建出对象的。Spring在处理普通的Bean的时候也是将其的父子类的信息合并成一个MergedBeanDefinition。
那么很明显要想通过一个接口来初始化成对象那么一定是使用的动态代理了!
还记得我们上面研究自动注入时候的一个小细节吗?Scanner在扫描到指定的BeanDefinition之后还做了一个postProcess也就是后置处理。里面有一个操作就是在获取到的BeanDefinition中设置一个FactoryBean的类型。那么有一定SpringIOC基础的同学就能够知道在doGetBean创建对象的流程中,在基于BeanDefinition去生成这个Bean的时候优先是去判断一下当前要生产的这个Bean的定义中是否被设置了FactoryBeanClass这个属性。如果被设置了这个属性的话那么就会优先地使用工厂的getObject方法去生成这个对象的实例,反之才会使用反射去newInstance。
那么现在问题就很明显了,MyBatis的MapperBean是通过MyBatis提供的FactoryBean来实现实例化的,那么按照这样的逻辑在这个工厂Bean里面一定有通过动态代理来生成代理对象的逻辑。
那么最初设置工厂Bean的地方在哪里呢?当然还是在@MapperScan这个注解里面,通过下面的截图可以看到这个注解默认去定义了一个factoryBean的元数据,默认就是MapperFactoryBean,所以这个类就是MyBatis提供的工厂Bean了。
我们直接点开这个类,直接定位到getObject方法,这里通过SqlSession去获取Mapper对象。
我们直接跟着这个getMapper方法去找到他的实际的实现逻辑,最后在MapperRegistry类中找到了。这里通过MapperProxyFactory的newInstance方法来创建了一个代理对象并返回。
那么这里的MapperProxyFactory就是实际生成代理对象的工厂。我们点开这个newInstance方法可以看到他创建代理对象的逻辑。下面这个方法去创建了一个实现了InvocationHandler接口的对象,之后再将这个对象交给Proxy去生成一个实际的代理对象。
那么到目前为止一个Mapper的代理对象就已经生成完成了,那么他是怎么实现自己的逻辑的呢(将xml或者写在注解中的sql解析,再交给sqlSession去执行)?
这部分的逻辑就被封装到MapperProxy里面了。
首先这个类是实现了InvocationHandler的,所以我们直接去看这个类的invoke方法。通过这个方法的最后两行我们可以看到,这个方法将要执行的method封装成了MapperMethod,之后再调用mapperMethod方法的execute去执行具体的sql执行逻辑。
所以真正的执行逻辑得看这个execute方法。第一眼看到这个方法就能知道MyBatis很明显是根据方法的执行类型(select、insert、update、delete)来将原始的数据解析成sql,看到convertArgsToSqlCommandParam这个方法名称就能够很容易理解,就是转换成sql嘛!之后就是直接将转换的sql交给sqlSession去执行了!逻辑清晰明了,简单易懂!
好了,到这里为止我们就将Spring生成MyBatis代理对象的逻辑给理清楚了,而且还大致了了解了一下MyBatis是怎么去转换并执行sql的(具体的转换规则就在上图的convert开头的方法里面,感兴趣的同学可以自己研究一下)。那我们总结一下这一部分的执行逻辑:
- Spring的IOC逻辑(doGetBean)通过Mapper的BeanDefinition中设置的FactoryBean生成Mapper的对象。
- 在FactoryBean生成对象的过程中使用的是MyBatis提供的一个代理工厂对象来生成。
- 代理工厂会将Mapper对象封装成一个MapperProxy
- 具体的代理方法的执行是将我们的接口类中的方法封装成一个MapperMethod,并调用其execute方法。
- 最后就是代理执行的逻辑,很简单就是根据方法的操作类型将原始信息转换成sql,并将这些sql提交给sqlSession去执行并获取返回的结果。
实战
接下来让我们回到这篇文章的开头,我们需要自己实现一个类似于Feign那样的远程调用代理。接下来我直接贴上我自己的代码,并对一些关键的地方加上说明。
使用demo实例
import cn.yueworld.eip.consumer.base.EipScan;
import cn.yueworld.eip.consumer.base.EnableEipRestAutoProxy;
import org.springframework.context.annotation.Configuration;
/**
* 交互中台的请求代理功能配置类。
*
* 注意:如果想要使用请求代理的功能那么就一定要配置这个类。以下三个注解都是必须的。
*
* 配置方法:通过{@link EnableEipRestAutoProxy}这个注解来开启代理功能,
* 通过{@link EipScan}这个注解来指定定义的代理接口的包路径。
* 通过{@link Configuration}这个注解来将当前配置注册到容器并调用
* Configuration的后置处理。
*
* @author Chenxi Du
*/
@Configuration
@EnableEipRestAutoProxy
@EipScan("cn.yueworld.eip.consumer.demo")
public class EipConfig {
}
import cn.yueworld.eip.consumer.base.EipResponseVO;
import cn.yueworld.eip.consumer.base.EipRest;
import cn.yueworld.eip.consumer.base.Target;
/**
* 交互中台的请求接口,可以直接将这个接口注入到需要使用的bean中
* 这种模式和Feign以及MyBatis的Mapper用法一样。
*
* 这里强烈建议将注解中的元数据如请求的url、接口的appCode、businessCode
* secretKey等信息放置到配置文件中。测试环境和正式环境的配置文件
* 隔离开,那么这个请求的url从配置文件中读取就能够很好地实现正式环
* 境和测试环境的切换。
*
* @author Chenxi Du
*/
@EipRest("https://eipdemo.powerlong.com/ys-eip-server/business/http/request")
public interface OrderApi {
/**
* 这里定义一个请求到交互中台的请求方法
* @param userVO
* @return
*/
@Target(appCode = "cwgx0033", businessCode = "cwgxcsg0004", secretKey = "9e8cdeff19c64ab49f1fa939f366e164")
EipResponseVO testPost(UserVO userVO);
}
/**
* 订单接口类(做测试用,没有实际的业务意义)
*
* @author Chenxi Du
*/
public interface OrderService {
void queryOrder(UserVO userVO) throws Exception;
}
import cn.yueworld.eip.consumer.base.EipResponseVO;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
/**
* 订单接口类(做测试用,没有实际的业务意义)
*
* @author Chenxi Du
*/
@Service
public class OrderServiceImpl implements OrderService {
// 在这里将我们定义的交互中台请求接口注入当前的bean
@Autowired
private OrderApi orderApi;
/**
* 这里实现一个获取订单的接口,这个方法中通过我们定义的请求接口对象
* 将请求发送到交互中台。(这里只是演示,该方法没有实际的业务意义)
* @param userVO
* @throws Exception
*/
@Override
public void queryOrder(UserVO userVO) throws Exception{
/**
* 业务逻辑处理
*/
// 这里通过接口的代理对象去请求到交互中台,并获取到该请求的返回对象
EipResponseVO eipResponseVO = orderApi.testPost(userVO);
/**
* 业务逻辑处理
*/
}
}
定义一个开启你要实现的功能的注解
import org.springframework.context.annotation.Import;
import java.lang.annotation.Target;
import java.lang.annotation.*;
/**
* 开启请求自动代理的功能。如果要使用代理请求的话务必要添加这个注解。
* 在这里会Import{@link EipRestRegistrar}
*
* @author Chenxi Du
*/
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Import(EipRestRegistrar.class)
public @interface EnableEipRestAutoProxy {
}
定义一个提供指定包扫描的注解
import org.springframework.context.annotation.Import;
import java.lang.annotation.Target;
import java.lang.annotation.*;
/**
* 请求代理对象的扫描配置注解,用于标注
* 对象所在的包路径或者直接指定类以及代理bean的工厂类
* @author Chenxi Du
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
@Import(EipRestRegistrar.class)
public @interface EipScan {
String[] value() default {};
String[] basePackages() default {};
Class<?>[] basePackageClasses() default {};
Class<? extends EipRestFactoryBean> factoryBean() default EipRestFactoryBean.class;
}
定义一个声明代理接口对象的注解
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
/**
* 请求对象的注解,通过这个可以将定义的请求接口注入到
* 容器,并且生成代理对象。这个注解需要标注上交互中台的请求
* url,建议url从配置中读取,测试环境和正式环境的配置文件隔离开。
* 这样就只需要编写一套代码
*
* @author Chenxi Du
*/
@java.lang.annotation.Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface EipRest {
String[] value();
}
代理接口方法的注解
这里的属性可以根据自己的业务需要来定义。
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
/**
* 请求代理接口的元数据注解,
* 在这里可以配置指定接口的请求的信息
*
* @author Chenxi Du
*/
@java.lang.annotation.Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Target {
// 应用编码
String appCode();
// 接口编码
String businessCode();
// 应用秘钥
String secretKey();
}
扫描器配置类的注册器
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.context.ResourceLoaderAware;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.ImportBeanDefinitionRegistrar;
import org.springframework.core.annotation.AnnotationAttributes;
import org.springframework.core.io.ResourceLoader;
import org.springframework.core.type.AnnotationMetadata;
import org.springframework.util.ClassUtils;
import org.springframework.util.StringUtils;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;
/**
* 请求代理对象的注册器。这个注册器的作用是将自定义的@EipRest注解注释的类
* 的定义加载到容器中。这个对象在{@link EnableEipRestAutoProxy}中被import到容器
* 同时在{@link Configuration}中被后置处理,调用这个对象的
* EipRestRegistrar.registerBeanDefinitions方法
*
* @author Chenxi Du
*/
public class EipRestRegistrar implements ImportBeanDefinitionRegistrar, ResourceLoaderAware {
private ResourceLoader resourceLoader;
@Override
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
/**
* 在这里对EipScannerConfigurer进行属性的初始化配置
*/
AnnotationAttributes eipScanAttrs = AnnotationAttributes
.fromMap(importingClassMetadata.getAnnotationAttributes(EipScan.class.getName()));
if (eipScanAttrs != null) {
registerBeanDefinitions(eipScanAttrs, registry, generateBaseBeanName(importingClassMetadata, 0));
}
}
void registerBeanDefinitions(AnnotationAttributes annoAttrs, BeanDefinitionRegistry registry, String beanName) {
BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(EipScannerConfigurer.class);
// 添加请求代理的包路径
List<String> basePackages = new ArrayList<>();
basePackages.addAll(
Arrays.stream(annoAttrs.getStringArray("value")).filter(StringUtils::hasText).collect(Collectors.toList()));
basePackages.addAll(Arrays.stream(annoAttrs.getStringArray("basePackages")).filter(StringUtils::hasText)
.collect(Collectors.toList()));
basePackages.addAll(Arrays.stream(annoAttrs.getClassArray("basePackageClasses")).map(ClassUtils::getPackageName)
.collect(Collectors.toList()));
builder.addPropertyValue("basePackage", StringUtils.collectionToCommaDelimitedString(basePackages));
registry.registerBeanDefinition(EipRest.class.getName(), builder.getBeanDefinition());
}
@Override
public void setResourceLoader(ResourceLoader resourceLoader) {
this.resourceLoader = resourceLoader;
}
private static String generateBaseBeanName(AnnotationMetadata importingClassMetadata, int index) {
return importingClassMetadata.getClassName() + "#" + EipRestRegistrar.class.getSimpleName() + "#" + index;
}
}
扫描器的配置类
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.beans.factory.support.BeanDefinitionRegistryPostProcessor;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.util.StringUtils;
import static org.springframework.util.Assert.notNull;
/**
* 代理扫描配置器,用于调用扫描器将指定的接口的定义扫描到容器
* 同时这个对象会在{@link EipRestRegistrar}类中被注册
* @author Chenxi Du
*/
public class EipScannerConfigurer implements BeanDefinitionRegistryPostProcessor {
// 请求代理对象的包路径
private String basePackage;
public void setBasePackage(String basePackage){
this.basePackage = basePackage;
}
public void afterPropertiesSet() throws Exception {
notNull(this.basePackage, "请求代理对象的包路径不能为空");
}
@Override
public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException {
ClassPathEipScanner scanner = new ClassPathEipScanner(registry);
scanner.registerFilters();
scanner.scan(
StringUtils.tokenizeToStringArray(this.basePackage, ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS));
}
@Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
}
}
扫描器
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.AbstractBeanDefinition;
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.filter.AnnotationTypeFilter;
import java.util.Arrays;
import java.util.Set;
/**
* 扫描器,用于扫描给定的包路径下的用@EipRest注释的对象,
* 这个扫描器在{@link EipScannerConfigurer}中被调用
*
* @author Chenxi Du
*/
@Slf4j
public class ClassPathEipScanner extends ClassPathBeanDefinitionScanner {
private Class<? extends EipRestFactoryBean> eipRestFactoryBeanClass = EipRestFactoryBean.class;
public void setEipRestFactoryBeanClass(Class<? extends EipRestFactoryBean> eipRestFactoryBeanClass) {
this.eipRestFactoryBeanClass = eipRestFactoryBeanClass == null ? EipRestFactoryBean.class : eipRestFactoryBeanClass;
}
public ClassPathEipScanner(BeanDefinitionRegistry registry) {
super(registry);
}
@Override
protected boolean isCandidateComponent(AnnotatedBeanDefinition beanDefinition) {
return beanDefinition.getMetadata().isInterface() && beanDefinition.getMetadata().isIndependent();
}
@Override
protected boolean checkCandidate(String beanName, BeanDefinition beanDefinition) {
if (super.checkCandidate(beanName, beanDefinition)) {
return true;
} else {
log.warn( "跳过当前bean '" + beanName + "' 和 '"
+ beanDefinition.getBeanClassName() + "' 代理接口" + ". Bean早已在容器中定义!");
return false;
}
}
public void registerFilters() {
addIncludeFilter(new AnnotationTypeFilter(EipRest.class));
}
@Override
public Set<BeanDefinitionHolder> doScan(String... basePackages) {
Set<BeanDefinitionHolder> beanDefinitions = super.doScan(basePackages);
if (beanDefinitions.isEmpty()) {
log.warn("在指定的包路径下没有找到请求bean'" + Arrays.toString(basePackages)
+ "' 请检查配置");
} else {
processBeanDefinitions(beanDefinitions);
}
return beanDefinitions;
}
private void processBeanDefinitions(Set<BeanDefinitionHolder> beanDefinitions) {
GenericBeanDefinition definition;
for (BeanDefinitionHolder holder : beanDefinitions) {
definition = (GenericBeanDefinition) holder.getBeanDefinition();
String beanClassName = definition.getBeanClassName();
definition.getConstructorArgumentValues().addGenericArgumentValue(beanClassName);
definition.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_BY_TYPE);
definition.setBeanClass(this.eipRestFactoryBeanClass);
}
}
}
工厂Bean
import org.springframework.beans.factory.FactoryBean;
/**
* EipRest代理对象的工厂bean,会在bean被初始化的时候调用其getObject()
* 方法来返回一个代理对象
*
* @author Chenxi Du
* @param <T>
*/
public class EipRestFactoryBean<T> extends EipRestSupport implements FactoryBean<T> {
private Class<T> eipRestInterface;
public EipRestFactoryBean(Class<T> eipRestInterface) {
this.eipRestInterface = eipRestInterface;
}
@Override
public T getObject() throws Exception {
return getProxy(this.eipRestInterface);
}
@Override
public Class<?> getObjectType() {
return this.eipRestInterface;
}
@Override
public boolean isSingleton() {
return true;
}
@Override
public <T> T getProxy(Class<T> type) {
EipRestProxyFactory<T> eipRestProxyFactory = addProxyFactory(type);
return eipRestProxyFactory.newInstance(type);
}
}
代理工厂Bean
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
public class EipRestProxyFactory<T> {
private final Class<T> eipRestInterface;
private final Map<Method, EipRestMethod> methodCache = new ConcurrentHashMap<>();
public EipRestProxyFactory(Class<T> eipRestInterface) {
this.eipRestInterface = eipRestInterface;
}
public Class<T> getMapperInterface() {
return eipRestInterface;
}
public Map<Method, EipRestMethod> getMethodCache() {
return methodCache;
}
@SuppressWarnings("unchecked")
protected T newInstance(Class<T> type) {
return (T) Proxy.newProxyInstance(eipRestInterface.getClassLoader(), new Class[] { eipRestInterface }, new EipRestProxy<>(type, methodCache));
}
}
代理方法的注册器
import java.util.concurrent.ConcurrentHashMap;
/**
* @author Chenxi Du
*/
public abstract class EipRestSupport {
private static ConcurrentHashMap<Class<?>, EipRestProxyFactory> proxyFactoryCache = new ConcurrentHashMap<>();
public EipRestProxyFactory addProxyFactory(Class<?> type){
if(!proxyFactoryCache.contains(type)){
proxyFactoryCache.put(type, new EipRestProxyFactory(type));
}
return proxyFactoryCache.get(type);
}
public abstract <T> T getProxy(Class<T> type);
}
代理对象
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.util.Map;
/**
* @author Chenxi Du
* @param <T>
*/
public class EipRestProxy<T> implements InvocationHandler {
private Class<T> eipRestInterface;
private final Map<Method, EipRestMethod> methodCache;
public EipRestProxy(Class<T> eipRestInterface, Map<Method, EipRestMethod> methodCache) {
this.eipRestInterface = eipRestInterface;
this.methodCache = methodCache;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
EipRestMethod eipRestMethod = cachedMapperMethod(method);
return eipRestMethod.execute(args);
}
private EipRestMethod cachedMapperMethod(Method method) {
return methodCache.computeIfAbsent(method, k -> new EipRestMethod(eipRestInterface, method));
}
}
代理方法的封装对象
import java.lang.reflect.Method;
/**
* 请求方法的封装,在代理对象的invoke方法里面调用封装后的方法对象的execute,
* 所以这个类是实际的方法调用的地方。
*
* @author Chenxi Du
*/
public class EipRestMethod {
private MethodSignature methodSignature;
public EipRestMethod(Class<?> eipRestInterface, Method method){
this.methodSignature = new MethodSignature(eipRestInterface, method);
}
public Object execute(Object[] args) throws Exception{
return methodSignature.send(args);
}
private static class MethodSignature{
private String url;
private String appCode;
private String businessCode;
private String secretKey;
private Class<?> eipRestInterface;
private Method method;
public Object send(Object[] args) throws Exception{
if(args[0] == null){
throw new Exception("请求到交互中台的参数为空!");
}
validate();
return EipUtil.send2Eip(url, appCode, businessCode, secretKey, args[0]);
}
public void validate() throws Exception{
if(!method.getReturnType().equals(EipResponseVO.class)) {
throw new Exception("交互中台的请求单例的返回类型要求是EipResponseVO类型,定义错误方法位置:" +
eipRestInterface.getName() + "." + method.getName());
}
}
public MethodSignature(Class<?> eipRestInterface, Method method){
Target target = method.getAnnotation(Target.class);
this.method = method;
this.appCode = target.appCode();
this.businessCode = target.businessCode();
this.secretKey = target.secretKey();
this.eipRestInterface = eipRestInterface;
this.url = eipRestInterface.getAnnotation(EipRest.class).value()[0];
}
}
}
Http请求的工具类、VO类
这里就不提供了。