一文带你了解Spring是如何整合mybatis、feign等接口的
spring 整合mybatis和OpenFeign时,都是通过接口来实现的,如:
@Repository
public interface OrderMapper {
int deleteByPrimaryKey(Long codId);
}
@FeignClient("coupon")
public interface MmsService {
@RequestMapping("/coupon/spubounds/save")
R saveSpuBounds(@RequestBody SpuBoundsDTO spuBounds);
}
spring容器的bean默认是不支持接口或者抽象类的,那上面的接口是如何整合到spring中的呢?
在回答上述问题前,我们需要了解spring容器的一些入门知识点:
- BeanDefinition (创建bean的模板)
我们知道,一个class对象可以通过new的方式创建一个对象,但在spring中,这样简单的通过Class对象去创建Bean是远远不够的,例如一个Bean需要有名称BeanName,需要有属性判断是否是单例,需要有属性判断是否懒加载,需要在对象创建完成后,执行初始化方法 等,简单的用一个class对象来描述,不足以实现这个功能,因此spring 使用 BeanDefinition来描述这些特征
public abstract class AbstractBeanDefinition extends BeanMetadataAttributeAccessor implements BeanDefinition {
@Nullable
private volatile Object beanClass; // 原始的class对象
@Nullable
private String scope;
private boolean abstractFlag;
private boolean lazyInit; //是否懒加载
private int autowireMode;
private int dependencyCheck;
@Nullable
private String[] dependsOn;
private boolean autowireCandidate;
private boolean primary; //如果容器中有多个相同类型的bean,当其他bean注入该类型的Bean时,以哪个为准
private final Map<String, AutowireCandidateQualifier> qualifiers;
@Nullable
private String factoryBeanName; //如果是@Bean方式创建,这里指的是工厂Bean的名称
@Nullable
private String factoryMethodName; //如果是@Bean方式创建,这里指的是工厂Bean对应的方法
@Nullable
private String initMethodName; //初始化方法
@Nullable
private String destroyMethodName; //销毁方法
... ...
}
所以,spring创建Bean需要先创建BeanDefitition对象
模板 | 对象 | 说明 |
---|---|---|
Class对象 | Object对象 | Object对象通过Class对象创建 |
BeanDefinition对象 | bean | bean是通过 BeanDefinition对象创建 |
spring 创建Bean的简单过程
-
首先扫描指定路径下的.class结尾的文件,然后用类加载器加载成class对象
-
判断这些class对象是否需要创建Bean,通过注解过滤器来实现,如有没@Component注解(第一轮过滤方法)
-
之后对找到的class对象,再加一次判断,如判断是否接口,抽象类等,(会忽略接口和抽象类)(第二轮过滤方法)
-
将这些需要创建Bean的class对象封装成一个个 BeanDefinition对象,并且用一个Map<String, BeanDefinition>保存起来
-
上一步的map,key为Bean的名称,spring还使用一个List
集合保存了所有BeanName,然后遍历list集合,通过map找到
一个个的BeanDefinition对象,然后创建出对应的Bean,如果是单例Bean,最终会保存到一个Map<String,Object> singleObjects这个Map集合中,key为Bean的名字 -
FactoryBean
public interface FactoryBean<T> {
@Nullable
T getObject() throws Exception;
@Nullable
Class<?> getObjectType();
default boolean isSingleton() {
return true;
}
}
spring专门提供了FactoryBean
当你通过BeanName获取对应的Bean时,会返回getObject()的执行结果,当你通过类型T去获取Bean时,
spring 会遍历 Map<String, BeanDefinition> map,然后判断 BeanDefinition中的BeanClass是不是FactoryBean,
如果是的话,就会调用 getObjectType()方法返回的类似跟类型T比较,相等就返回getObject()对应的对象; 下面通过一个Mapper接口简单讲解其实现:
@Repository
public interface OrderMapper {
int deleteByPrimaryKey(Long codId);
}
public class MapperFactoryBean implements FactoryBean<OrderMapper> {
private Class<OrderMapper> aClass;
public MapperFactoryBean(Class<OrderMapper> aClass) {
this.aClass = aClass;
}
@Override
public OrderMapper getObject() throws Exception {
Object proxyInstance = Proxy.newProxyInstance(this.getClass().getClassLoader(), new Class[]{aClass}, new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
return null;
}
});
return (OrderMapper) proxyInstance;
}
@Override
public Class<?> getObjectType() {
return aClass;
}
}
public class AService
{
@Autowired
private OrderMapper orderMapper;
}
上述,MapperFactoryBean通过构造器传入参数OrderMapper.class,让它们产生了关联,在spring的单例池Map<String,Object> singleObjects中,beanName为orderMapper,Object对象为MapperFactoryBean的实列,当AService中注入OrderMapper接口时,
spring会遍历singleObjects,然后判断对应的Object对象是不是FactoryBean,是的话,就会调用 getObjectType()获得对应的类型,然后跟OrderMapper.class比较,如果相等,就会调用 getObject()方法对应的对象,该方法最终返回了OrderMapper接口的代理类,从而实现了接口在spring中的管理
spring 是如何查找哪些类需要创建Bean的呢?
通过前面知道,接口是通过FactoryBean来注入spring容器的,在bean创建的过程中,需要判断哪些Class对象需要注入spring容器,spring先通过扫描器,扫描所有的class对象,然后将它们封装成BeanDefinition,最后再创建对应的Bean,默认的扫描器是不支持接口或者抽象类的,因此,对于接口,需要覆盖对应的方法
- 扫描器的顶级父类:ClassPathScanningCandidateComponentProvider
public class ClassPathScanningCandidateComponentProvider implements EnvironmentCapable, ResourceLoaderAware {
static final String DEFAULT_RESOURCE_PATTERN = "**/*.class";
protected final Log logger;
private String resourcePattern;
private final List<TypeFilter> includeFilters;
private final List<TypeFilter> excludeFilters;
... ...
//扫描给定的包名下的Class对象,如@MapperScan和 @ComponentScan中指定的包名
private Set<BeanDefinition> scanCandidateComponents(String basePackage) {
Set<BeanDefinition> candidates = new LinkedHashSet<>();
try {
//将包名转成文件路径,并且通过流的方式读取所有.class文件
String packageSearchPath = ResourcePatternResolver.CLASSPATH_ALL_URL_PREFIX +
resolveBasePackage(basePackage) + '/' + this.resourcePattern;
Resource[] resources = getResourcePatternResolver().getResources(packageSearchPath);
for (Resource resource : resources) {
if (resource.isReadable()) {
try {
MetadataReader metadataReader = getMetadataReaderFactory().getMetadataReader(resource);
//第一轮过滤方法,通过过滤器判断 如:
@MapperScan(basePackages = { "com.yang" },annotationClass = Repository.class),可以指定只有带@Repository注解的类或者接口
if (isCandidateComponent(metadataReader)) {
ScannedGenericBeanDefinition sbd = new ScannedGenericBeanDefinition(metadataReader);
sbd.setResource(resource);
sbd.setSource(resource);
//第二轮过滤方法,判断上一轮过滤后的class对象是否接口或者抽象类
if (isCandidateComponent(sbd)) {
candidates.add(sbd);
}
}
catch (IOException ex) {
throw new BeanDefinitionStoreException("I/O failure during classpath scanning", ex);
}
return candidates;
}
//这是上面的第一轮过滤方法,排除指定路径下的class对象,哪些需要包含,哪些需要排除,
这里一般是指带某种注解的,如@Component注解
protected boolean isCandidateComponent(MetadataReader metadataReader) throws IOException {
for (TypeFilter tf : this.excludeFilters) {
if (tf.match(metadataReader, getMetadataReaderFactory())) {
return false;
}
}
for (TypeFilter tf : this.includeFilters) {
if (tf.match(metadataReader, getMetadataReaderFactory())) {
return isConditionMatch(metadataReader);
}
}
return false;
}
//第二轮过滤器,默认class对象不能是抽象的,如果是抽象的,必须方法含有@Lookup注解
protected boolean isCandidateComponent(AnnotatedBeanDefinition beanDefinition) {
AnnotationMetadata metadata = beanDefinition.getMetadata();
return (metadata.isIndependent() && (metadata.isConcrete() ||
(metadata.isAbstract() && metadata.hasAnnotatedMethods(Lookup.class.getName()))));
}
... ...
}
小结:由上面的扫描器知道,spring默认会将接口和抽象类过滤掉,即使加了这些接口或者抽象类加了@componet @Configuration等注解,因此,对于接口,需要重新写个扫描器,重写第二轮过滤方法
- spring 扫描器的默认实现 ClassPathBeanDefinitionScanner,它没改变父类的过滤逻辑
public class ClassPathBeanDefinitionScanner extends ClassPathScanningCandidateComponentProvider {
protected Set<BeanDefinitionHolder> doScan(String... basePackages) {
Set<BeanDefinitionHolder> beanDefinitions = new LinkedHashSet<>();
for (String basePackage : basePackages) {
//调用父类的扫描方法,获得BeanDefinition对象
Set<BeanDefinition> candidates = findCandidateComponents(basePackage);
... ...
}
return beanDefinitions;
}
//这是父类的方法
public Set<BeanDefinition> findCandidateComponents(String basePackage) {
if (this.componentsIndex != null && indexSupportsIncludeFilters()) {
return addCandidateComponentsFromIndex(this.componentsIndex, basePackage);
}
else {
//这里调用的是父类的那个扫描方法
return scanCandidateComponents(basePackage);
}
}
}
- Mybatis中Mapper接口的扫描器:ClassPathMapperScanner
//继承spring默认的扫描器
public class ClassPathMapperScanner extends ClassPathBeanDefinitionScanner {
public Set<BeanDefinitionHolder> doScan(String... basePackages) {
//调用的是父类的扫描方法
Set<BeanDefinitionHolder> beanDefinitions = super.doScan(basePackages);
if (beanDefinitions.isEmpty()) {
... ...
} else {
//扫描到的BeanDefinition是接口,需要转成FactoryBean
this.processBeanDefinitions(beanDefinitions);
}
return beanDefinitions;
}
//重点在这里,重写了父类的第二轮过滤器,判断只有接口才是我们要的
protected boolean isCandidateComponent(AnnotatedBeanDefinition beanDefinition) {
return beanDefinition.getMetadata().isInterface() && beanDefinition.getMetadata().isIndependent();
}
//重点:需要将BeanDefinion中的类型替换成FactoryBean
private void processBeanDefinitions(Set<BeanDefinitionHolder> beanDefinitions) {
GenericBeanDefinition definition;
for (BeanDefinitionHolder holder : beanDefinitions) {
definition = (GenericBeanDefinition) holder.getBeanDefinition();
//旧的接口,作为构造器的参数
definition.getConstructorArgumentValues().addGenericArgumentValue(definition.getBeanClassName());
//将类替换成FactoryBean
definition.setBeanClass(this.mapperFactoryBean.getClass());
definition.getPropertyValues().add("addToConfig", this.addToConfig);
}
}
}
}
//小结:spring整合mybatis,第一步需要重写扫描器的第二轮过滤方法,改成只有接口才能通过,其次,将符合要求的
BeanDefinition对象中的类型换成FactoryBean,之前的class类型,需要作为构造器参数传入
- spring整合Fegin的扫描器:通过匿名内部类实现:
在FeignClientsRegistrar这个类中:
protected ClassPathScanningCandidateComponentProvider getScanner() {
return new ClassPathScanningCandidateComponentProvider(false, this.environment) {
//核心点,放开了父类中接口和抽象类的限制条件
protected boolean isCandidateComponent(AnnotatedBeanDefinition beanDefinition) {
boolean isCandidate = false;
if (beanDefinition.getMetadata().isIndependent() && !beanDefinition.getMetadata().isAnnotation()) {
isCandidate = true;
}
return isCandidate;
}
};
}
//扫描器似乎没有指定Feign一定是接口,并且也没实现将BeanDefinition中的class转成FactoryBean的逻辑,那它们在哪里实现
,这一切都在FeignClientsRegistrar中实现
public void registerFeignClients(AnnotationMetadata metadata,
BeanDefinitionRegistry registry) {
ClassPathScanningCandidateComponentProvider scanner = getScanner();
Set<String> basePackages;
Map<String, Object> attrs = metadata.getAnnotationAttributes(EnableFeignClients.class.getName());
//扫描器第一轮过滤方法指定的过滤器,只包含@FeignClient注解的类
AnnotationTypeFilter annotationTypeFilter = new AnnotationTypeFilter(FeignClient.class);
final Class<?>[] clients = attrs == null ? null
: (Class<?>[]) attrs.get("clients");
if (clients == null || clients.length == 0) {
scanner.addIncludeFilter(annotationTypeFilter);
basePackages = getBasePackages(metadata);
}
else {
final Set<String> clientClasses = new HashSet<>();
basePackages = new HashSet<>();
for (Class<?> clazz : clients) {
basePackages.add(ClassUtils.getPackageName(clazz));
clientClasses.add(clazz.getCanonicalName());
}
AbstractClassTestingTypeFilter filter = new AbstractClassTestingTypeFilter() {
@Override
protected boolean match(ClassMetadata metadata) {
String cleaned = metadata.getClassName().replaceAll("\\$", ".");
return clientClasses.contains(cleaned);
}
};
//给扫描器第一轮过滤方法指定过滤器
scanner.addIncludeFilter(
new AllTypeFilter(Arrays.asList(filter, annotationTypeFilter)));
}
for (String basePackage : basePackages) {
//调用父类的扫描方法,得到BeanDefinition对象
Set<BeanDefinition> candidateComponents = scanner
.findCandidateComponents(basePackage);
for (BeanDefinition candidateComponent : candidateComponents) {
if (candidateComponent instanceof AnnotatedBeanDefinition) {
// verify annotated class is an interface
AnnotatedBeanDefinition beanDefinition = (AnnotatedBeanDefinition) candidateComponent;
AnnotationMetadata annotationMetadata = beanDefinition.getMetadata();
//重点,前面说过,扫描器第二轮过滤方法,没有指定一定是接口,所以这里用了断言来判断
Assert.isTrue(annotationMetadata.isInterface(),
"@FeignClient can only be specified on an interface");
Map<String, Object> attributes = annotationMetadata
.getAnnotationAttributes(
FeignClient.class.getCanonicalName());
String name = getClientName(attributes);
registerClientConfiguration(registry, name,
attributes.get("configuration"));
//这里是将其转成FactoryBean
registerFeignClient(registry, annotationMetadata, attributes);
}
}
}
}
//这里是将其转成FactoryBean
private void registerFeignClient(BeanDefinitionRegistry registry,
AnnotationMetadata annotationMetadata, Map<String, Object> attributes) {
String className = annotationMetadata.getClassName();
//直接创建一个FactoryBean
BeanDefinitionBuilder definition = BeanDefinitionBuilder
.genericBeanDefinition(FeignClientFactoryBean.class);
definition.addPropertyValue("url", getUrl(attributes));
definition.addPropertyValue("path", getPath(attributes));
String name = getName(attributes);
definition.addPropertyValue("name", name);
//通过属性,将原来接口的类型注入到FactoryBean中,后续其getObject方法就可以返回代理类了
definition.addPropertyValue("type", className);
... ...
BeanDefinitionHolder holder = new BeanDefinitionHolder(beanDefinition, className,
new String[] { alias });
BeanDefinitionReaderUtils.registerBeanDefinition(holder, registry);
}
总结: spring整合Fegin接口,意义上通过改变扫描器的第二轮过滤方法来实现接口的扫描,之后也会转成对应的FactoryBean
思考:如果我们想实现类似Mapper接口,Fegin接口的方式,你会了吗?
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· 阿里巴巴 QwQ-32B真的超越了 DeepSeek R-1吗?
· 【译】Visual Studio 中新的强大生产力特性
· 10年+ .NET Coder 心语 ── 封装的思维:从隐藏、稳定开始理解其本质意义
· 【设计模式】告别冗长if-else语句:使用策略模式优化代码结构
2021-09-11 带你理解springboot中的endpoint