一文带你了解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容器的一些入门知识点:

  1. 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的简单过程

  1. 首先扫描指定路径下的.class结尾的文件,然后用类加载器加载成class对象

  2. 判断这些class对象是否需要创建Bean,通过注解过滤器来实现,如有没@Component注解(第一轮过滤方法)

  3. 之后对找到的class对象,再加一次判断,如判断是否接口,抽象类等,(会忽略接口和抽象类)(第二轮过滤方法)

  4. 将这些需要创建Bean的class对象封装成一个个 BeanDefinition对象,并且用一个Map<String, BeanDefinition>保存起来

  5. 上一步的map,key为Bean的名称,spring还使用一个List集合保存了所有BeanName,然后遍历list集合,通过map找到
    一个个的BeanDefinition对象,然后创建出对应的Bean,如果是单例Bean,最终会保存到一个Map<String,Object> singleObjects这个Map集合中,key为Bean的名字

  6. 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,默认的扫描器是不支持接口或者抽象类的,因此,对于接口,需要覆盖对应的方法

  1. 扫描器的顶级父类: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等注解,因此,对于接口,需要重新写个扫描器,重写第二轮过滤方法

  1. 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);
		}
	}
    
}
  1. 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类型,需要作为构造器参数传入

  1. 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接口的方式,你会了吗?

posted @   yangxiaohui227  阅读(182)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· 阿里巴巴 QwQ-32B真的超越了 DeepSeek R-1吗?
· 【译】Visual Studio 中新的强大生产力特性
· 10年+ .NET Coder 心语 ── 封装的思维:从隐藏、稳定开始理解其本质意义
· 【设计模式】告别冗长if-else语句:使用策略模式优化代码结构
历史上的今天:
2021-09-11 带你理解springboot中的endpoint
点击右上角即可分享
微信分享提示