Spring:FactoryBean预加载逻辑以及自定义实现Mybatis的接口扫描
1 前言
参考Mybatis框架的@Mapper注解扫描Mapper接口的业务逻辑,其中集成Spring的逻辑里使用到了Spring框架的FactoryBean拓展点,本文针对Spring FactoryBean的加载流程进行分析和理解。
本文参考源码依赖:
<parent><artifactId>spring-boot-starter-parent</artifactId><groupId>org.springframework.boot</groupId><version>2.5.4</version>
</parent><dependencies><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency><dependency><groupId>org.mybatis.spring.boot</groupId><artifactId>mybatis-spring-boot-starter</artifactId><version>2.3.0</version></dependency><dependency><groupId>mysql</groupId><artifactId>mysql-connector-java</artifactId>
<!-- <version>3.1.14</version>--></dependency></dependencies>
tips: idea中,如果Download Source只是下载了Spring-beans依赖,没有下载Spring-boot依赖的source,那么ctrl+鼠标左键点击方法时,不会提示该方法在依赖Spring-boot中的使用地方,只会提示Spring-beans依赖中的提示地方。
比如:Spring-beans(5.3.9)中的DefaultListableBeanFactory,即Bean工厂类下的findAnnotationOnBean方法:
@Override
@Nullable
public <A extends Annotation> A findAnnotationOnBean(String beanName, Class<A> annotationType)throws NoSuchBeanDefinitionException {return findMergedAnnotationOnBean(beanName, annotationType).synthesize(MergedAnnotation::isPresent).orElse(null);
}
该方法在下载了Spring-boot(2.5.4)依赖的source后,ctrl+鼠标左键可以提示并找到该方法所使用的地方,即AnnotationDependsOnDatabaseInitializationDetector类:
class AnnotationDependsOnDatabaseInitializationDetector implements DependsOnDatabaseInitializationDetector {@Overridepublic Set<String> detect(ConfigurableListableBeanFactory beanFactory) {Set<String> dependentBeans = new HashSet<>();for (String beanName : beanFactory.getBeanDefinitionNames()) {if (beanFactory.findAnnotationOnBean(beanName, DependsOnDatabaseInitialization.class) != null) {dependentBeans.add(beanName);}}return dependentBeans;}}
上述代码片段也是本文的切入点,即Spring框架针对FactoryBean的加载,是有初始化(以及缓存)的设计的,并非是实际调用时才会去生成对应的FactoryBean对象。
2 Spring之FactoryBean加载逻辑
上述代码片段,beanFactory.findAnnotationOnBean(beanName, DependsOnDatabaseInitialization.class) != null),调用的是DefaultListableBeanFactory的findAnnotationOnBean方法,如下:
private <A extends Annotation> MergedAnnotation<A> findMergedAnnotationOnBean(String beanName, Class<A> annotationType)
核心FactoryBean的init逻辑,需要看getType(beanName):
Class<?> beanType = getType(beanName);
getType(beanName)方法中,如下的getTypeForFactoryBean(beanName, mbd, allowFactoryBeanInit)是处理FactoryBean的逻辑:
if (beanClass != null && FactoryBean.class.isAssignableFrom(beanClass)) {if (!BeanFactoryUtils.isFactoryDereference(name)) {// If it's a FactoryBean, we want to look at what it creates, not at the factory class.return getTypeForFactoryBean(beanName, mbd, allowFactoryBeanInit).resolve();}else {return beanClass;}
}
else {return (!BeanFactoryUtils.isFactoryDereference(name) ? beanClass : null);
}
getTypeForFactoryBean(String beanName, RootBeanDefinition mbd, boolean allowInit)方法源码片段:
protected ResolvableType getTypeForFactoryBean(String beanName, RootBeanDefinition mbd, boolean allowInit) {ResolvableType result = getTypeForFactoryBeanFromAttributes(mbd);if (result != ResolvableType.NONE) {return result;}if (allowInit && mbd.isSingleton()) {try {FactoryBean<?> factoryBean = doGetBean(FACTORY_BEAN_PREFIX + beanName, FactoryBean.class, null, true);Class<?> objectType = getTypeForFactoryBean(factoryBean);return (objectType != null ? ResolvableType.forClass(objectType) : ResolvableType.NONE);}catch (BeanCreationException ex) {if (ex.contains(BeanCurrentlyInCreationException.class)) {logger.trace(LogMessage.format("Bean currently in creation on FactoryBean type check: %s", ex));}else if (mbd.isLazyInit()) {logger.trace(LogMessage.format("Bean creation exception on lazy FactoryBean type check: %s", ex));}else {logger.debug(LogMessage.format("Bean creation exception on eager FactoryBean type check: %s", ex));}onSuppressedException(ex);}}return ResolvableType.NONE;
}
getTypeForFactoryBean方法会调用如下逻辑,即doGetBean(FACTORY_BEAN_PREFIX + beanName, FactoryBean.class, null, true),这里就会init初始化FactoryBean:
if (allowInit && mbd.isSingleton()) {try {FactoryBean<?> factoryBean = doGetBean(FACTORY_BEAN_PREFIX + beanName, FactoryBean.class, null, true);Class<?> objectType = getTypeForFactoryBean(factoryBean);return (objectType != null ? ResolvableType.forClass(objectType) : ResolvableType.NONE);}catch (BeanCreationException ex) {if (ex.contains(BeanCurrentlyInCreationException.class)) {logger.trace(LogMessage.format("Bean currently in creation on FactoryBean type check: %s", ex));}else if (mbd.isLazyInit()) {logger.trace(LogMessage.format("Bean creation exception on lazy FactoryBean type check: %s", ex));}else {logger.debug(LogMessage.format("Bean creation exception on eager FactoryBean type check: %s", ex));}onSuppressedException(ex);}
}
实际是DatabaseInitializationDependencyConfigurer的内部类:DependsOnDatabaseInitializationPostProcessor,调用postProcessBeanFactory来调用的FactoryBean的初始加载进入this.singletonObjects中的(this.singletonObjects归属于DefaultSingletonBeanRegistry类,doGetBean方法中可见)。
详细参考:DatabaseInitializationDependencyConfigurer.DependsOnDatabaseInitializationPostProcessor:
private Collection<String> detectDependsOnInitializationBeanNames(ConfigurableListableBeanFactory beanFactory)
doGetBean方法中部分代码片段如下,getSingleton方法会创建Bean并放入this.singletonObjects中:
protected <T> T doGetBean(String name, @Nullable Class<T> requiredType, @Nullable Object[] args, boolean typeCheckOnly)throws BeansException
// Create bean instance.
if (mbd.isSingleton()) {sharedInstance = getSingleton(beanName, () -> {try {return createBean(beanName, mbd, args);}catch (BeansException ex) {// Explicitly remove instance from singleton cache: It might have been put there// eagerly by the creation process, to allow for circular reference resolution.// Also remove any beans that received a temporary reference to the bean.destroySingleton(beanName);throw ex;}});beanInstance = getObjectForBeanInstance(sharedInstance, name, beanName, mbd);
}
上面的getSingleton方法中,有如下的处理逻辑:
if (newSingleton) {addSingleton(beanName, singletonObject);
}
addSingleton即注册singletonObject,即单例Bean对象到this.singletonObjects,后续获取Bean时,如果这里存在则直接从其中获取即可(后续获取bean时,this.singletonObjects含有值,则直接返回该bean):
protected void addSingleton(String beanName, Object singletonObject) {synchronized (this.singletonObjects) {this.singletonObjects.put(beanName, singletonObject);this.singletonFactories.remove(beanName);this.earlySingletonObjects.remove(beanName);this.registeredSingletons.add(beanName);}
}
getSingleton完整方法如下:
public Object getSingleton(String beanName, ObjectFactory<?> singletonFactory) {Assert.notNull(beanName, "Bean name must not be null");synchronized (this.singletonObjects) {Object singletonObject = this.singletonObjects.get(beanName);if (singletonObject == null) {if (this.singletonsCurrentlyInDestruction) {throw new BeanCreationNotAllowedException(beanName,"Singleton bean creation not allowed while singletons of this factory are in destruction " +"(Do not request a bean from a BeanFactory in a destroy method implementation!)");}if (logger.isDebugEnabled()) {logger.debug("Creating shared instance of singleton bean '" + beanName + "'");}beforeSingletonCreation(beanName);boolean newSingleton = false;boolean recordSuppressedExceptions = (this.suppressedExceptions == null);if (recordSuppressedExceptions) {this.suppressedExceptions = new LinkedHashSet<>();}try {singletonObject = singletonFactory.getObject();newSingleton = true;}catch (IllegalStateException ex) {// Has the singleton object implicitly appeared in the meantime ->// if yes, proceed with it since the exception indicates that state.singletonObject = this.singletonObjects.get(beanName);if (singletonObject == null) {throw ex;}}catch (BeanCreationException ex) {if (recordSuppressedExceptions) {for (Exception suppressedException : this.suppressedExceptions) {ex.addRelatedCause(suppressedException);}}throw ex;}finally {if (recordSuppressedExceptions) {this.suppressedExceptions = null;}afterSingletonCreation(beanName);}if (newSingleton) {addSingleton(beanName, singletonObject);}}return singletonObject;}
}
调用createBean(beanName, mbd, args)方法是创建FactoryBean的实际方法,核心方法逻辑看doCreateBean(beanName, mbdToUse, args):
try {Object beanInstance = doCreateBean(beanName, mbdToUse, args);if (logger.isTraceEnabled()) {logger.trace("Finished creating instance of bean '" + beanName + "'");}return beanInstance;
}
catch (BeanCreationException | ImplicitlyAppearedSingletonException ex) {// A previously detected exception with proper bean creation context already,// or illegal singleton state to be communicated up to DefaultSingletonBeanRegistry.throw ex;
}
catch (Throwable ex) {throw new BeanCreationException(mbdToUse.getResourceDescription(), beanName, "Unexpected exception during bean creation", ex);
}
可以看到doCreateBean(beanName, mbdToUse, args)方法中,实际的FactoryBean对象,是从this.factoryBeanInstanceCache中获取的(同时清除该FactoryBean对象缓存):
然后接下来,直接从BeanWrapperImpl对象中获取到该FactoryBean:
另外,this.factoryBeanInstanceCache的值的获取,其实在Spring-beans包下的AbstractAutowireCapableBeanFactory类的方法getSingletonFactoryBeanForTypeCheck(String beanName, RootBeanDefinition mbd)中有实现:
@Nullable
private FactoryBean<?> getSingletonFactoryBeanForTypeCheck(String beanName, RootBeanDefinition mbd) {synchronized (getSingletonMutex()) {BeanWrapper bw = this.factoryBeanInstanceCache.get(beanName);if (bw != null) {return (FactoryBean<?>) bw.getWrappedInstance();}Object beanInstance = getSingleton(beanName, false);if (beanInstance instanceof FactoryBean) {return (FactoryBean<?>) beanInstance;}if (isSingletonCurrentlyInCreation(beanName) ||(mbd.getFactoryBeanName() != null && isSingletonCurrentlyInCreation(mbd.getFactoryBeanName()))) {return null;}Object instance;try {// Mark this bean as currently in creation, even if just partially.beforeSingletonCreation(beanName);// Give BeanPostProcessors a chance to return a proxy instead of the target bean instance.instance = resolveBeforeInstantiation(beanName, mbd);if (instance == null) {bw = createBeanInstance(beanName, mbd, null);instance = bw.getWrappedInstance();}}catch (UnsatisfiedDependencyException ex) {// Don't swallow, probably misconfiguration...throw ex;}catch (BeanCreationException ex) {// Don't swallow a linkage error since it contains a full stacktrace on// first occurrence... and just a plain NoClassDefFoundError afterwards.if (ex.contains(LinkageError.class)) {throw ex;}// Instantiation failure, maybe too early...if (logger.isDebugEnabled()) {logger.debug("Bean creation exception on singleton FactoryBean type check: " + ex);}onSuppressedException(ex);return null;}finally {// Finished partial creation of this bean.afterSingletonCreation(beanName);}FactoryBean<?> fb = getFactoryBean(beanName, instance);if (bw != null) {this.factoryBeanInstanceCache.put(beanName, bw);}return fb;}
}
执行getSingletonFactoryBeanForTypeCheck(String beanName, RootBeanDefinition mbd)方法时,可见初始在FactoryBean的缓存factoryBeanInstanceCache中没有获取到,且getSingleton(beanName, false)方法也没有获取到该bean,因此接着往下执行:
执行到核心生成FactoryBean的逻辑,在下面的bw = createBeanInstance(beanName, mbd, null)方法中,生成的对象为BeanWrapperImpl:
若对上述代码有印象的话,其实这个方法,就是刚才doCreateBean方法中调用过的,判断FactoryBean的缓存中不存在该Bean时,即instanceWrapper == null时,再次调用createBeanInstance(beanName, mbd, args)方法:
BeanWrapper instanceWrapper = null;
if (mbd.isSingleton()) {instanceWrapper = this.factoryBeanInstanceCache.remove(beanName);
}
if (instanceWrapper == null) {instanceWrapper = createBeanInstance(beanName, mbd, args);
}
那么核心分析FactoryBean的创建逻辑,就应该是createBeanInstance方法了,Spring-beans依赖中的AbstractAutowireCapableBeanFactory类的createBeanInstance(String beanName, RootBeanDefinition mbd, @Nullable Object[] args)方法如下:
protected BeanWrapper createBeanInstance(String beanName, RootBeanDefinition mbd, @Nullable Object[] args) {// Make sure bean class is actually resolved at this point.Class<?> beanClass = resolveBeanClass(mbd, beanName);if (beanClass != null && !Modifier.isPublic(beanClass.getModifiers()) && !mbd.isNonPublicAccessAllowed()) {throw new BeanCreationException(mbd.getResourceDescription(), beanName,"Bean class isn't public, and non-public access not allowed: " + beanClass.getName());}Supplier<?> instanceSupplier = mbd.getInstanceSupplier();if (instanceSupplier != null) {return obtainFromSupplier(instanceSupplier, beanName);}if (mbd.getFactoryMethodName() != null) {return instantiateUsingFactoryMethod(beanName, mbd, args);}// Shortcut when re-creating the same bean...boolean resolved = false;boolean autowireNecessary = false;if (args == null) {synchronized (mbd.constructorArgumentLock) {if (mbd.resolvedConstructorOrFactoryMethod != null) {resolved = true;autowireNecessary = mbd.constructorArgumentsResolved;}}}if (resolved) {if (autowireNecessary) {return autowireConstructor(beanName, mbd, null, null);}else {return instantiateBean(beanName, mbd);}}// Candidate constructors for autowiring?Constructor<?>[] ctors = determineConstructorsFromBeanPostProcessors(beanClass, beanName);if (ctors != null || mbd.getResolvedAutowireMode() == AUTOWIRE_CONSTRUCTOR ||mbd.hasConstructorArgumentValues() || !ObjectUtils.isEmpty(args)) {return autowireConstructor(beanName, mbd, ctors, args);}// Preferred constructors for default construction?ctors = mbd.getPreferredConstructors();if (ctors != null) {return autowireConstructor(beanName, mbd, ctors, null);}// No special handling: simply use no-arg constructor.return instantiateBean(beanName, mbd);
}
首先从RootBeanDefinition中获取到beanClass,在自定义BeanDefinition时,比如我们自己写一个ImportBeanDefinitionRegistrar的实现类,重写registerBeanDefinitions方法注册BeanDefinition时,也需要设置该BeanDefinition的beanClass,作为这个Bean的Class对象(因为Spring底层框架,需要根据Class对象获取对应的生成Bean的Constructor方法来反射生成Bean):
继续执行到方法determineConstructorsFromBeanPostProcessors(beanClass, beanName),但是没有决策到Constructor:
最后执行instantiateBean(beanName, mbd)方法,生成FactoryBean对象:
AbstractAutowireCapableBeanFactory的instantiateBean(String beanName, RootBeanDefinition mbd)方法如下:
protected BeanWrapper instantiateBean(String beanName, RootBeanDefinition mbd) {try {Object beanInstance;if (System.getSecurityManager() != null) {beanInstance = AccessController.doPrivileged((PrivilegedAction<Object>) () -> getInstantiationStrategy().instantiate(mbd, beanName, this),getAccessControlContext());}else {beanInstance = getInstantiationStrategy().instantiate(mbd, beanName, this);}BeanWrapper bw = new BeanWrapperImpl(beanInstance);initBeanWrapper(bw);return bw;}catch (Throwable ex) {throw new BeanCreationException(mbd.getResourceDescription(), beanName, "Instantiation of bean failed", ex);}
}
这里的初始化策略,是使用的Spring-beans包下的org.springframework.beans.factory.support.CglibSubclassingInstantiationStrategy(继承了org.springframework.beans.factory.support.SimpleInstantiationStrategy):
SimpleInstantiationStrategy的Object instantiate(RootBeanDefinition bd, @Nullable String beanName, BeanFactory owner)方法如下:
@Override
public Object instantiate(RootBeanDefinition bd, @Nullable String beanName, BeanFactory owner) {// Don't override the class with CGLIB if no overrides.if (!bd.hasMethodOverrides()) {Constructor<?> constructorToUse;synchronized (bd.constructorArgumentLock) {constructorToUse = (Constructor<?>) bd.resolvedConstructorOrFactoryMethod;if (constructorToUse == null) {final Class<?> clazz = bd.getBeanClass();if (clazz.isInterface()) {throw new BeanInstantiationException(clazz, "Specified class is an interface");}try {if (System.getSecurityManager() != null) {constructorToUse = AccessController.doPrivileged((PrivilegedExceptionAction<Constructor<?>>) clazz::getDeclaredConstructor);}else {constructorToUse = clazz.getDeclaredConstructor();}bd.resolvedConstructorOrFactoryMethod = constructorToUse;}catch (Throwable ex) {throw new BeanInstantiationException(clazz, "No default constructor found", ex);}}}return BeanUtils.instantiateClass(constructorToUse);}else {// Must generate CGLIB subclass.return instantiateWithMethodInjection(bd, beanName, owner);}
}
下面可以看到,初始化前,依然获取到RootBeanDefinition的beanClass,判断如果是interface,则bean的初始化直接抛出异常Specified class is an interface,从这里可知,Mybatis的@Mapper接口扫描的逻辑,虽然使用的interface,但是Mybatis在将其注册为BeanDefinition时,Mybatis底层实际还是将其包装为FactoryBean的形式(并通过JDK动态代理的方式为接口方法实现增删改查的逻辑),因为beanClass为interface类型的bean,在Spring框架中是无法被初始化的:
然后根据Class的getDeclaredConstructor()方法,通过beanClass获取到该Class声明的无参Constructor方法(即前面提到的,Spring框架中的BeanDefinition必须声明beanClass,用于反射获取bean实例的构造方法等等,这里Spring是取的Class的无参构造方法,故而一般我们在Spring框架中声明bean时,该Class需要具有默认的无参构造方法):
同时将该无参构造方法,设置为RootBeanDefinition的resolvedConstructorOrFactoryMethod,最后调用BeanUtils.instantiateClass(constructorToUse)即可生成该对象,instantiateClass(Constructor<T> ctor, Object… args)方法本质即调用constructor.newInstance(args)来反射生成实例对象:
public static <T> T instantiateClass(Constructor<T> ctor, Object... args) throws BeanInstantiationException {Assert.notNull(ctor, "Constructor must not be null");try {ReflectionUtils.makeAccessible(ctor);if (KotlinDetector.isKotlinReflectPresent() && KotlinDetector.isKotlinType(ctor.getDeclaringClass())) {return KotlinDelegate.instantiateClass(ctor, args);}else {Class<?>[] parameterTypes = ctor.getParameterTypes();Assert.isTrue(args.length <= parameterTypes.length, "Can't specify more arguments than constructor parameters");Object[] argsWithDefaultValues = new Object[args.length];for (int i = 0 ; i < args.length; i++) {if (args[i] == null) {Class<?> parameterType = parameterTypes[i];argsWithDefaultValues[i] = (parameterType.isPrimitive() ? DEFAULT_TYPE_VALUES.get(parameterType) : null);}else {argsWithDefaultValues[i] = args[i];}}return ctor.newInstance(argsWithDefaultValues);}}catch (InstantiationException ex) {throw new BeanInstantiationException(ctor, "Is it an abstract class?", ex);}catch (IllegalAccessException ex) {throw new BeanInstantiationException(ctor, "Is the constructor accessible?", ex);}catch (IllegalArgumentException ex) {throw new BeanInstantiationException(ctor, "Illegal arguments for constructor", ex);}catch (InvocationTargetException ex) {throw new BeanInstantiationException(ctor, "Constructor threw exception", ex.getTargetException());}
}
最后实例化完成,将其包裹为BeanWrapperImpl对象,如下,然后instantiateBean(beanName, mbd)方法返回该BeanWrapperImpl对象:
BeanWrapper bw = new BeanWrapperImpl(beanInstance);
最后关键步骤,将该包裹了初始化的FactoryBean对象的BeanWrapperImpl对象,置入this.factoryBeanInstanceCache,即FactoryBean缓存中:
上述逻辑,调用为AbstractAutowireCapableBeanFactory类下的protected ResolvableType getTypeForFactoryBean(String beanName, RootBeanDefinition mbd, boolean allowInit)方法,如下:
protected ResolvableType getTypeForFactoryBean(String beanName, RootBeanDefinition mbd, boolean allowInit) {// Check if the bean definition itself has defined the type with an attributeResolvableType result = getTypeForFactoryBeanFromAttributes(mbd);if (result != ResolvableType.NONE) {return result;}ResolvableType beanType =(mbd.hasBeanClass() ? ResolvableType.forClass(mbd.getBeanClass()) : ResolvableType.NONE);// For instance supplied beans try the target type and bean classif (mbd.getInstanceSupplier() != null) {result = getFactoryBeanGeneric(mbd.targetType);if (result.resolve() != null) {return result;}result = getFactoryBeanGeneric(beanType);if (result.resolve() != null) {return result;}}// Consider factory methodsString factoryBeanName = mbd.getFactoryBeanName();String factoryMethodName = mbd.getFactoryMethodName();// Scan the factory bean methodsif (factoryBeanName != null) {if (factoryMethodName != null) {// Try to obtain the FactoryBean's object type from its factory method// declaration without instantiating the containing bean at all.BeanDefinition factoryBeanDefinition = getBeanDefinition(factoryBeanName);Class<?> factoryBeanClass;if (factoryBeanDefinition instanceof AbstractBeanDefinition &&((AbstractBeanDefinition) factoryBeanDefinition).hasBeanClass()) {factoryBeanClass = ((AbstractBeanDefinition) factoryBeanDefinition).getBeanClass();}else {RootBeanDefinition fbmbd = getMergedBeanDefinition(factoryBeanName, factoryBeanDefinition);factoryBeanClass = determineTargetType(factoryBeanName, fbmbd);}if (factoryBeanClass != null) {result = getTypeForFactoryBeanFromMethod(factoryBeanClass, factoryMethodName);if (result.resolve() != null) {return result;}}}// If not resolvable above and the referenced factory bean doesn't exist yet,// exit here - we don't want to force the creation of another bean just to// obtain a FactoryBean's object type...if (!isBeanEligibleForMetadataCaching(factoryBeanName)) {return ResolvableType.NONE;}}// If we're allowed, we can create the factory bean and call getObjectType() earlyif (allowInit) {FactoryBean<?> factoryBean = (mbd.isSingleton() ?getSingletonFactoryBeanForTypeCheck(beanName, mbd) :getNonSingletonFactoryBeanForTypeCheck(beanName, mbd));if (factoryBean != null) {// Try to obtain the FactoryBean's object type from this early stage of the instance.Class<?> type = getTypeForFactoryBean(factoryBean);if (type != null) {return ResolvableType.forClass(type);}// No type found for shortcut FactoryBean instance:// fall back to full creation of the FactoryBean instance.return super.getTypeForFactoryBean(beanName, mbd, true);}}if (factoryBeanName == null && mbd.hasBeanClass() && factoryMethodName != null) {// No early bean instantiation possible: determine FactoryBean's type from// static factory method signature or from class inheritance hierarchy...return getTypeForFactoryBeanFromMethod(mbd.getBeanClass(), factoryMethodName);}result = getFactoryBeanGeneric(beanType);if (result.resolve() != null) {return result;}return ResolvableType.NONE;
}
即getSingletonFactoryBeanForTypeCheck(beanName, mbd):
if (allowInit) {FactoryBean<?> factoryBean = (mbd.isSingleton() ?getSingletonFactoryBeanForTypeCheck(beanName, mbd) :getNonSingletonFactoryBeanForTypeCheck(beanName, mbd));
上述的分析可知,在Spring框架设计中,allowInit的前提下,在通过getSingletonFactoryBeanForTypeCheck方法(可以理解为FactoryBean类型预检的方法)获取FactoryBean的type时,就已经做了FactoryBean的初始化,同时置入缓存中,减少了下次初始化FactoryBean时还需重新初始化的额外开销。同时,getSingletonFactoryBeanForTypeCheck(String beanName, RootBeanDefinition mbd)方法没有清除缓存的处理逻辑,无则创建并置入缓存,有则返回,只有后续调用doGetBean方法时,才会清除该FactoryBean缓存,并将其重新置入this.singletonObjects中。
上述调用getTypeForFactoryBean(beanName, mbd, allowFactoryBeanInit),发生在org.springframework.beans.factory.support.AbstractBeanFactory类中的isTypeMatch方法中
protected boolean isTypeMatch(String name, ResolvableType typeToMatch, boolean allowFactoryBeanInit)throws NoSuchBeanDefinitionException {String beanName = transformedBeanName(name);boolean isFactoryDereference = BeanFactoryUtils.isFactoryDereference(name);// Check manually registered singletons.Object beanInstance = getSingleton(beanName, false);if (beanInstance != null && beanInstance.getClass() != NullBean.class) {if (beanInstance instanceof FactoryBean) {if (!isFactoryDereference) {Class<?> type = getTypeForFactoryBean((FactoryBean<?>) beanInstance);return (type != null && typeToMatch.isAssignableFrom(type));}else {return typeToMatch.isInstance(beanInstance);}}else if (!isFactoryDereference) {if (typeToMatch.isInstance(beanInstance)) {// Direct match for exposed instance?return true;}else if (typeToMatch.hasGenerics() && containsBeanDefinition(beanName)) {// Generics potentially only match on the target class, not on the proxy...RootBeanDefinition mbd = getMergedLocalBeanDefinition(beanName);Class<?> targetType = mbd.getTargetType();if (targetType != null && targetType != ClassUtils.getUserClass(beanInstance)) {// Check raw class match as well, making sure it's exposed on the proxy.Class<?> classToMatch = typeToMatch.resolve();if (classToMatch != null && !classToMatch.isInstance(beanInstance)) {return false;}if (typeToMatch.isAssignableFrom(targetType)) {return true;}}ResolvableType resolvableType = mbd.targetType;if (resolvableType == null) {resolvableType = mbd.factoryMethodReturnType;}return (resolvableType != null && typeToMatch.isAssignableFrom(resolvableType));}}return false;}else if (containsSingleton(beanName) && !containsBeanDefinition(beanName)) {// null instance registeredreturn false;}// No singleton instance found -> check bean definition.BeanFactory parentBeanFactory = getParentBeanFactory();if (parentBeanFactory != null && !containsBeanDefinition(beanName)) {// No bean definition found in this factory -> delegate to parent.return parentBeanFactory.isTypeMatch(originalBeanName(name), typeToMatch);}// Retrieve corresponding bean definition.RootBeanDefinition mbd = getMergedLocalBeanDefinition(beanName);BeanDefinitionHolder dbd = mbd.getDecoratedDefinition();// Setup the types that we want to match againstClass<?> classToMatch = typeToMatch.resolve();if (classToMatch == null) {classToMatch = FactoryBean.class;}Class<?>[] typesToMatch = (FactoryBean.class == classToMatch ?new Class<?>[] {classToMatch} : new Class<?>[] {FactoryBean.class, classToMatch});// Attempt to predict the bean typeClass<?> predictedType = null;// We're looking for a regular reference but we're a factory bean that has// a decorated bean definition. The target bean should be the same type// as FactoryBean would ultimately return.if (!isFactoryDereference && dbd != null && isFactoryBean(beanName, mbd)) {// We should only attempt if the user explicitly set lazy-init to true// and we know the merged bean definition is for a factory bean.if (!mbd.isLazyInit() || allowFactoryBeanInit) {RootBeanDefinition tbd = getMergedBeanDefinition(dbd.getBeanName(), dbd.getBeanDefinition(), mbd);Class<?> targetType = predictBeanType(dbd.getBeanName(), tbd, typesToMatch);if (targetType != null && !FactoryBean.class.isAssignableFrom(targetType)) {predictedType = targetType;}}}// If we couldn't use the target type, try regular prediction.if (predictedType == null) {predictedType = predictBeanType(beanName, mbd, typesToMatch);if (predictedType == null) {return false;}}// Attempt to get the actual ResolvableType for the bean.ResolvableType beanType = null;// If it's a FactoryBean, we want to look at what it creates, not the factory class.if (FactoryBean.class.isAssignableFrom(predictedType)) {if (beanInstance == null && !isFactoryDereference) {beanType = getTypeForFactoryBean(beanName, mbd, allowFactoryBeanInit);predictedType = beanType.resolve();if (predictedType == null) {return false;}}}else if (isFactoryDereference) {// Special case: A SmartInstantiationAwareBeanPostProcessor returned a non-FactoryBean// type but we nevertheless are being asked to dereference a FactoryBean...// Let's check the original bean class and proceed with it if it is a FactoryBean.predictedType = predictBeanType(beanName, mbd, FactoryBean.class);if (predictedType == null || !FactoryBean.class.isAssignableFrom(predictedType)) {return false;}}// We don't have an exact type but if bean definition target type or the factory// method return type matches the predicted type then we can use that.if (beanType == null) {ResolvableType definedType = mbd.targetType;if (definedType == null) {definedType = mbd.factoryMethodReturnType;}if (definedType != null && definedType.resolve() == predictedType) {beanType = definedType;}}// If we have a bean type use it so that generics are consideredif (beanType != null) {return typeToMatch.isAssignableFrom(beanType);}// If we don't have a bean type, fallback to the predicted typereturn typeToMatch.isAssignableFrom(predictedType);
}
调用getTypeForFactoryBean(beanName, mbd, allowFactoryBeanInit)的方法:
// If it's a FactoryBean, we want to look at what it creates, not the factory class.
if (FactoryBean.class.isAssignableFrom(predictedType)) {if (beanInstance == null && !isFactoryDereference) {beanType = getTypeForFactoryBean(beanName, mbd, allowFactoryBeanInit);predictedType = beanType.resolve();if (predictedType == null) {return false;}}
}
上述的FactoryBean demo参考类:
/*** @author xiaoxu* @date 2024-01-02* java_demo:com.xiaoxu.test.impo.SelectorFactoryBean*/
@Component
public class SelectorFactoryBean implements FactoryBean<SelectorProxy> {@Overridepublic SelectorProxy getObject() throws Exception {SelectorProxy proxy = new SelectorProxy();System.out.println("获取FactoryBean SelectorProxy.");return proxy;}@Overridepublic Class<?> getObjectType() {return SelectorProxy.class;}@Overridepublic boolean isSingleton() {return FactoryBean.super.isSingleton();}
}
上述调用的isTypeMatch(beanName, type, allowFactoryBeanInit)方法,实际是调用的org.springframework.beans.factory.support.DefaultListableBeanFactory类的private String[] doGetBeanNamesForType(ResolvableType type, boolean includeNonSingletons, boolean allowEagerInit)方法:
这里是mybatis框架中,根据type(此处为mybatis的SqlSessionTemplate类)来获取所有匹配该类型的beanDefinitionNames,可见doGetBeanNamesForType方法,直接对FactoryBean做了bean实例缓存的初始化了:
如果上述没有匹配到,那么给FactoryBean的beanName前缀加上"&"再次匹配,如果还是没有匹配到,那么就不添加该beanName(当然此处主要关心FactoryBean的初始化逻辑):
实际调用方法为org.springframework.beans.factory.support.DefaultListableBeanFactory#getBeanNamesForType(@Nullable Class<?> type, boolean includeNonSingletons, boolean allowEagerInit):
结论:由此可知getBeanNamesForType(@Nullable Class<?> type)方法,默认就会执行FactoryBean的init操作,将其创建的FactoryBean对象置入缓存中(注意这里创建的是FactoryBean本身,非FactoryBean调用getObject()方法获取的对象)。
该方法由Mybatis的Spring自动配置类MybatisAutoConfiguration$AutoConfiguredMapperScannerRegistrar声明并调用的:
AutoConfiguredMapperScannerRegistrar类,即Mybatis注册MapperScannerConfigurer的BeanDefinition的ImportBeanDefinitionRegistrar实现类:
如下可见,这里处理的逻辑,就是Spring针对ImportBeanDefinitionRegistrar的拓展点处理逻辑:
小结:
(1) 通过调用getBeanNamesForType(@Nullable Class<?> type)方法,对于FactoryBean处理类型type是否匹配时(参考内部方法:getTypeForFactoryBean(beanName, mbd, allowFactoryBeanInit)),Spring心想,这里已经处理过FactoryBean(并且都初始化过实例对象了),那么下次处理不就可以方便些了么?于是将其置入缓存this.factoryBeanInstanceCache中,减少反射调用的开销。
上述是Mybatis框架在注册扫描BeanDefinition,即MapperScannerConfigurer时,调用了factory.getBeanNamesForType(type)方法时处理的逻辑,此时Spring框架还在处理各个自动配置类,还处于执行的初期。
AutoConfiguredMapperScannerRegistrar代码片段:
if (injectSqlSession && this.beanFactory instanceof ListableBeanFactory) {ListableBeanFactory listableBeanFactory = (ListableBeanFactory)this.beanFactory;Optional<String> sqlSessionTemplateBeanName = Optional.ofNullable(this.getBeanNameForType(SqlSessionTemplate.class, listableBeanFactory));Optional<String> sqlSessionFactoryBeanName = Optional.ofNullable(this.getBeanNameForType(SqlSessionFactory.class, listableBeanFactory));if (!sqlSessionTemplateBeanName.isPresent() && sqlSessionFactoryBeanName.isPresent()) {builder.addPropertyValue("sqlSessionFactoryBeanName", sqlSessionFactoryBeanName.get());} else {builder.addPropertyValue("sqlSessionTemplateBeanName", sqlSessionTemplateBeanName.orElse("sqlSessionTemplate"));}}
(2)后续AnnotationDependsOnDatabaseInitializationDetector类执行detect方法时,需要判断bean的Class上,是否具有@DependsOnDatabaseInitialization注解。
通过调用findAnnotationOnBean(String beanName, Class<A> annotationType)方法,即内部调用的Class<?> getType(String name)方法,getType(String name)方法其中又会调用到getTypeForFactoryBean(String beanName, RootBeanDefinition mbd, boolean allowInit)方法,用于获取bean的Class,此时bean也做了初始化,那么Spring又想,这里也需要处理FactoryBean(并且初始化实例对象),如果缓存this.factoryBeanInstanceCache中存在该FactoryBean的对象,那我直接用不就好了?于是就清除this.factoryBeanInstanceCache缓存中该对象(remove方法,同时返回清除的对象),如果返回结果不为null,那么我就直接置入this.singletonObjects中,如果为null,那我就手动调用下createBeanInstance(beanName, mbd, args)创建就好了。
这里会调用doGetBean(FACTORY_BEAN_PREFIX + beanName, FactoryBean.class, null, true)方法。
这里的调用发生在DependsOnDatabaseInitializationPostProcessor的postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory)方法处,该类实现了BeanFactoryPostProcessor接口,亦是Spring的拓展点之一,调用的时间节点晚于上述(1)点中的自动配置类注册BeanDefinition的时间节点,故而是后发生的。
逻辑参考invokeBeanFactoryPostProcessors(nonOrderedPostProcessors, beanFactory),如下:
3 结合FactoryBean自定义注解实现Bean扫描
下面以我自定义实现扫描的CustomerMapper为例,根据自定义的SqlMapper注解,将该CustomerMapper接口,注册为FactoryBean的BeanDefinition:
test:
package com.xiaoxu.test.impo.test;import com.xiaoxu.test.impo.ifc.SqlMapper;/*** @author xiaoxu* @date 2024-02-23*/
@SqlMapper
public interface CustomerMapper {void queryCustomerById(String id);}
package com.xiaoxu.test.impo.test;import com.xiaoxu.test.impo.ifc.SqlMapper;/*** @author xiaoxu* @date 2024-02-20*/
@SqlMapper
public interface FruitSqlMapper {void queryFruitById();}
autoconfigure:
package com.xiaoxu.test.impo.autoconfigure;import com.xiaoxu.test.impo.core.XImportRegistrar;
import com.xiaoxu.test.impo.ifc.EnableSqlMapperProxy;
import com.xiaoxu.test.impo.ifc.RegistrarImport;
import com.xiaoxu.test.impo.ifc.XImport;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.context.annotation.Configuration;/*** @author xiaoxu* @date 2023-12-26* java_demo:com.xiaoxu.test.impo.autoconfigure.XImportAutoConfiguration*/
@RegistrarImport
public class XImportAutoConfiguration {@Configuration@XImport@EnableSqlMapperProxy@ConditionalOnMissingBean(XImportRegistrar.class)public static class XImportAutoSelector implements InitializingBean {public XImportAutoSelector() {}@Overridepublic void afterPropertiesSet() throws Exception {System.out.println("Not found registrar for registering sqlMapper.");}}}
core:
ClassPathSqlMapperScanner :
package com.xiaoxu.test.impo.core;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.BeanDefinitionRegistry;
import org.springframework.context.annotation.ClassPathBeanDefinitionScanner;
import org.springframework.core.type.AnnotationMetadata;
import org.springframework.core.type.classreading.MetadataReader;
import org.springframework.core.type.classreading.MetadataReaderFactory;
import org.springframework.core.type.filter.AnnotationTypeFilter;
import org.springframework.core.type.filter.AssignableTypeFilter;
import org.springframework.core.type.filter.TypeFilter;import java.io.IOException;
import java.lang.annotation.Annotation;
import java.util.Arrays;
import java.util.Set;/*** @author xiaoxu* @date 2024-01-22* java_demo:com.xiaoxu.test.impo.core.ClassPathSqlMapperScanner*/
public class ClassPathSqlMapperScanner extends ClassPathBeanDefinitionScanner {private Class<? extends SqlMapperFactoryBean> sqlMapperFactoryBeanClass;static final String FACTORY_BEAN_OBJECT_TYPE = "factoryBeanObjectType";private Class<?> markerSqlMapperClazz;private Class<? extends Annotation> detectClass;public ClassPathSqlMapperScanner(BeanDefinitionRegistry registry) {super(registry, false);}@Overrideprotected Set<BeanDefinitionHolder> doScan(String... basePackages) {Set<BeanDefinitionHolder> beanDefinitions = super.doScan(basePackages);if (beanDefinitions.isEmpty()) {System.out.println("No Sql Mapper was found in '" + Arrays.toString(basePackages) + "' package. Please check your configuration.");} else {this.processBeanDefinitions(beanDefinitions);}return beanDefinitions;}private void processBeanDefinitions(Set<BeanDefinitionHolder> beanDefinitions) {for (BeanDefinitionHolder beanDefinitionHolder : beanDefinitions) {System.out.println("【SqlMapper】开始处理beandefinition'" + beanDefinitionHolder.getBeanName());BeanDefinition beanDefinition = beanDefinitionHolder.getBeanDefinition();String beanClassName = beanDefinition.getBeanClassName();beanDefinition.setBeanClassName(SqlMapperFactoryBean.class.getName());beanDefinition.getConstructorArgumentValues().addGenericArgumentValue(beanClassName);}}public void setSqlMapperFactoryBeanClass(Class<? extends SqlMapperFactoryBean> sqlMapperFactoryBeanClass) {this.sqlMapperFactoryBeanClass = sqlMapperFactoryBeanClass;}public void setMarkerSqlMapperClazz(Class<?> markerSqlMapperClazz) {this.markerSqlMapperClazz = markerSqlMapperClazz;}public void setDetectClass(Class<? extends Annotation> detectClass) {this.detectClass = detectClass;}public void registerFilters() {boolean acceptAllClazz = true;if (this.detectClass != null) {this.addIncludeFilter(new AnnotationTypeFilter(this.detectClass));acceptAllClazz = false;}if (this.markerSqlMapperClazz != null) {this.addIncludeFilter(new AssignableTypeFilter(this.markerSqlMapperClazz));}if (acceptAllClazz) {this.addIncludeFilter(new TypeFilter() {@Overridepublic boolean match(MetadataReader metadataReader, MetadataReaderFactory metadataReaderFactory) throws IOException {return true;}});}this.addExcludeFilter(((metadataReader, metadataReaderFactory) -> {String className = metadataReader.getClassMetadata().getClassName();return className.endsWith("package-info");}));}@Overrideprotected boolean isCandidateComponent(AnnotatedBeanDefinition beanDefinition) {AnnotationMetadata metadata = beanDefinition.getMetadata();return (metadata.isInterface() || metadata.isConcrete()) && metadata.isIndependent();}
}
DetectorScanner:
package com.xiaoxu.test.impo.core;import com.google.common.collect.Lists;
import com.xiaoxu.test.impo.ifc.EnableSqlMapperProxy;
import com.xiaoxu.test.impo.infrastructure.AttributeUtil;
import org.apache.commons.lang3.ArrayUtils;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.BeanFactoryAware;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.beans.factory.support.BeanDefinitionRegistryPostProcessor;
import org.springframework.beans.factory.support.BeanNameGenerator;
import org.springframework.boot.autoconfigure.AutoConfigurationPackages;
import org.springframework.context.annotation.ScannedGenericBeanDefinition;
import org.springframework.core.annotation.AnnotationAttributes;
import org.springframework.core.io.Resource;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import org.springframework.core.io.support.ResourcePatternResolver;
import org.springframework.core.type.AnnotationMetadata;
import org.springframework.core.type.ClassMetadata;
import org.springframework.core.type.classreading.CachingMetadataReaderFactory;
import org.springframework.core.type.classreading.MetadataReader;
import org.springframework.core.type.classreading.MetadataReaderFactory;
import org.springframework.core.type.filter.TypeFilter;
import org.springframework.lang.NonNull;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
import org.springframework.util.ClassUtils;
import org.springframework.util.StringUtils;import java.io.IOException;
import java.lang.annotation.Annotation;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import java.util.Optional;/*** @author xiaoxu* @date 2024-01-18* java_demo:com.xiaoxu.test.impo.core.DetectorScanner*/
public class DetectorScanner implements BeanDefinitionRegistryPostProcessor, BeanFactoryAware {static final String DEFAULT_RESOURCE_PATTERN = "**/*.class";private String resourcePattern = DEFAULT_RESOURCE_PATTERN;private static final String classConcat = ".";private ResourcePatternResolver resourcePatternResolver;private MetadataReaderFactory metadataReaderFactory;private Class<? extends SqlMapperFactoryBean> sqlMapperFactoryBeanClass;private Class<?> markerClazz;private BeanNameGenerator nameGenerator;private final List<TypeFilter> includeFilters = new ArrayList<>();private final boolean useDefaultFilters;private AnnotationMetadata metadata;private AnnotationAttributes attributes;private BeanFactory beanFactory;private Object detectObject;private String basePackage;public DetectorScanner(boolean useDefaultFilters, @Nullable List<TypeFilter> detectProxyFilters) {this.useDefaultFilters = useDefaultFilters;this.nameGenerator = null;if (this.useDefaultFilters) {registerDefaultFilters();} else {this.includeFilters.addAll(Optional.ofNullable(detectProxyFilters).orElse(Lists.newArrayList()));}}@Overridepublic void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException {System.out.println("开始注册DetectorScanner");if (AutoConfigurationPackages.has(beanFactory)) {List<String> packages = AutoConfigurationPackages.get(this.beanFactory);System.out.println("packages is :{" + packages + "}.");System.out.println("先打印原有属性值:" + this.attributes);ConfigurableListableBeanFactory beanFactory = (ConfigurableListableBeanFactory) this.beanFactory;String scannedPath = getScannedPath();System.out.println("扫描:" + scannedPath);try {String packageSearchPath = ResourcePatternResolver.CLASSPATH_ALL_URL_PREFIX +resolveBasePackage(scannedPath) + "/" + resourcePattern;loadDetectObject(beanFactory, getResources(packageSearchPath));System.out.println("detect:" + this.detectObject);if (getDetectObject() != null) {ClassPathSqlMapperScanner scanner = new ClassPathSqlMapperScanner(registry);scanner.setSqlMapperFactoryBeanClass(this.sqlMapperFactoryBeanClass);scanner.setBeanNameGenerator(this.nameGenerator);scanner.setMarkerSqlMapperClazz(this.markerClazz);scanner.setDetectClass(((Class<? extends Annotation>) getDetectObject()));scanner.registerFilters();scanner.scan(StringUtils.tokenizeToStringArray(this.basePackage, " ,;\n\t"));}} catch (IOException ex) {throw new RuntimeException("I/O failure during classpath scanning", ex);}}}@SuppressWarnings("rawtypes")private String getScannedPath() {String scannedPath = null;Object value = this.attributes.get("value");String path = (String) this.attributes.get("path");if (StringUtils.hasText(path)) {scannedPath = path;}if (!StringUtils.hasText(scannedPath) && Object.class != value) {String name = ((Class) value).getName();scannedPath = name.substring(0, name.lastIndexOf((char) 46));}if (!StringUtils.hasText(scannedPath)) {String metadataClassName;scannedPath = this.metadata != null ?((metadataClassName = this.metadata.getClassName()).substring(0, metadataClassName.lastIndexOf((char) 46))): "";}if (!StringUtils.hasText(scannedPath)) {throw new RuntimeException("DetectorScanner needs Value Or Path to transfer Path, now detect stopped.");}return scannedPath;}private Resource[] getResources(String packageSearchPath) throws IOException {return getResourcePatternResolver().getResources(packageSearchPath);}private void loadDetectObject(ConfigurableListableBeanFactory beanFactory, Resource[] resources) throws IOException {ResourceDetectHolder[] resourceDetectHolders = new ResourceDetectHolder[0];try {for (Resource resource : resources) {if (resource.isReadable()) {MetadataReader metadataReader = getMetadataReaderFactory().getMetadataReader(resource);resourceDetectHolders = ArrayUtils.addAll(resourceDetectHolders, loadResourceHolder(beanFactory, resource, metadataReader));}}} catch (IOException e) {throw new RuntimeException("IO error:" + e.getMessage());}if (resourceDetectHolders.length <= 0) {throw new RuntimeException("No detect proxy resource holder found.");}if (resourceDetectHolders.length > 1) {throw new RuntimeException("More than one detect proxy resource holder found.");}if (ArrayUtils.isEmpty(resourceDetectHolders))throw new RuntimeException("Could not found detect proxy object.");this.detectObject = resourceDetectHolders[0].detectProxy;}private ResourceDetectHolder[] loadResourceHolder(ConfigurableListableBeanFactory beanFactory, Resource resource, MetadataReader metadataReader) throws IOException {List<ResourceDetectHolder> resourceDetectHolders = Lists.newArrayList();for (TypeFilter tf : this.includeFilters) {if (tf.match(metadataReader, getMetadataReaderFactory())) {Object detectProxy = findDetectProxy(beanFactory, metadataReader);resourceDetectHolders.add(new ResourceDetectHolder(resource, detectProxy));}}return resourceDetectHolders.toArray(new ResourceDetectHolder[0]);}private Object findDetectProxy(ConfigurableListableBeanFactory beanFactory, MetadataReader metadataReader) {String beanWrapName = getBeanWrapName(metadataReader);BeanDefinition beanDefinition = beanFactory.getBeanDefinition(beanWrapName);AnnotationAttributes attributes = new AnnotationAttributes();if (beanDefinition instanceof ScannedGenericBeanDefinition) {attributes = AttributeUtil.getAttributes(((ScannedGenericBeanDefinition) beanDefinition).getMetadata(), getSelectorDetectType(), false);}Assert.notNull(attributes.get("value"), () -> {return "No Proxy Object found. Is " + beanWrapName + "has config " + getSelectorDetectType().getName() + "it ?";});return Optional.ofNullable(attributes.get("value")).orElseThrow(() -> new RuntimeException("non null detect proxy value allowed."));}private boolean isCandidateCondition(AnnotationMetadata metadata) {return metadata.isConcrete() && metadata.isIndependent() && metadata.getEnclosingClassName() != null;}protected String resolveBasePackage(String basePackage) {return ClassUtils.convertClassNameToResourcePath(basePackage);}@Overridepublic void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {System.out.println("调用【postProcessBeanFactory】");}private String getBeanWrapName(MetadataReader metadataReader) {ClassMetadata classMetadata = metadataReader.getClassMetadata();String enclosingName = classMetadata.getEnclosingClassName();enclosingName = enclosingName == null ? "" : enclosingName;String className = classMetadata.getClassName();return enclosingName.substring(enclosingName.lastIndexOf((char) 46) + 1)+ classConcat + className.substring(className.lastIndexOf((char) 36) + 1);}@Overridepublic void setBeanFactory(@NonNull BeanFactory beanFactory) throws BeansException {this.beanFactory = beanFactory;}private ResourcePatternResolver getResourcePatternResolver() {if (this.resourcePatternResolver == null) {this.resourcePatternResolver = new PathMatchingResourcePatternResolver();}return this.resourcePatternResolver;}public MetadataReaderFactory getMetadataReaderFactory() {if (this.metadataReaderFactory == null) {this.metadataReaderFactory = new CachingMetadataReaderFactory();}return metadataReaderFactory;}@SuppressWarnings("all")private void registerDefaultFilters() {this.includeFilters.add(new TypeFilter() {@Overridepublic boolean match(@NonNull MetadataReader metadataReader, @NonNull MetadataReaderFactory metadataReaderFactory) throws IOException {AnnotationMetadata metadata = metadataReader.getAnnotationMetadata();if (isCandidateCondition(metadata)) {return metadata.hasAnnotation(getSelectorDetectType().getName());}return false;}});}public void setSqlMapperFactoryBeanClass(Class<? extends SqlMapperFactoryBean> sqlMapperFactoryBeanClass) {this.sqlMapperFactoryBeanClass = sqlMapperFactoryBeanClass;}public void setAttributes(AnnotationAttributes attributes) {this.attributes = attributes;}public void setMetadata(AnnotationMetadata metadata) {this.metadata = metadata;}public void setBasePackage(String basePackage) {this.basePackage = basePackage;}private Class<?> getSelectorDetectType() {return EnableSqlMapperProxy.class;}public Object getDetectObject() {return detectObject;}static class ResourceDetectHolder {final Resource resource;final Object detectProxy;public ResourceDetectHolder(Resource resource, Object detectProxy) {this.resource = resource;this.detectProxy = detectProxy;}public Resource getResource() {return resource;}public Object getDetectProxy() {return detectProxy;}@Overridepublic boolean equals(Object o) {if (this == o) return true;if (o == null || getClass() != o.getClass()) return false;ResourceDetectHolder that = (ResourceDetectHolder) o;return Objects.equals(resource, that.resource) && Objects.equals(detectProxy, that.detectProxy);}@Overridepublic int hashCode() {return Objects.hash(resource, detectProxy);}}}
SqlMapperBeanNameGenerator:
package com.xiaoxu.test.impo.core;import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.beans.factory.support.BeanNameGenerator;
import org.springframework.util.Assert;/*** @author xiaoxu* @date 2024-02-02* java_demo:com.xiaoxu.test.impo.core.SqlMapperBeanNameGenerator*/
public class SqlMapperBeanNameGenerator implements BeanNameGenerator {private static final String TAG = "ByXiaoxu";private static final String CON = "AutoMapper$";private Class<?> sqlMapperInterface;private Predicate names;public static SqlMapperBeanNameGenerator getSqlMapperBeanNameGenerator(Class<?> sqlMapperInterface) {return new SqlMapperBeanNameGenerator(sqlMapperInterface);}private SqlMapperBeanNameGenerator(Class<?> sqlMapperInterface) {Assert.notNull(sqlMapperInterface, () -> "sqlMapperInterface is null.");this.sqlMapperInterface = sqlMapperInterface;this.names = new Predicate() {@Overridepublic boolean evaluate(String name) {return false;}};}@Overridepublic String generateBeanName(BeanDefinition definition, BeanDefinitionRegistry registry) {String beanClassName = definition.getBeanClassName();Assert.state(beanClassName != null, "No bean class name set");String base = beanClassName + CON + this.getTag() + Integer.toHexString(this.sqlMapperInterface.hashCode());String attempt = base;for (int var9 = 2; names.evaluate(attempt); attempt = attempt + "_" + var9++) {}return attempt;}private String getTag() {return TAG;}public void setNames(Predicate names) {Assert.state(names != null, () -> "names could not be null.");this.names = names;}public interface Predicate {boolean evaluate(String name);}}
SqlMapperFactoryBean<T>:
package com.xiaoxu.test.impo.core;import org.springframework.beans.factory.FactoryBean;/*** @author xiaoxu* @date 2024-01-22* java_demo:com.xiaoxu.test.impo.core.SqlMapperFactoryBean*/
public class SqlMapperFactoryBean<T> implements FactoryBean<T> {private Class<T> sqlMapperClazz;public SqlMapperFactoryBean() {}private SqlMapperFactoryBean(Class<T> sqlMapperClazz) {this.sqlMapperClazz = sqlMapperClazz;}@Overridepublic T getObject() throws Exception {return null;}@Overridepublic Class<?> getObjectType() {return null;}@Overridepublic boolean isSingleton() {return FactoryBean.super.isSingleton();}
}
WrapBeanNameGenerator:
package com.xiaoxu.test.impo.core;import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.beans.factory.support.BeanNameGenerator;
import org.springframework.util.Assert;/*** @author xiaoxu* @date 2023-12-21* java_demo:com.xiaoxu.test.impo.core.WrapBeanNameGenerator*/
public class WrapBeanNameGenerator implements BeanNameGenerator {public static final WrapBeanNameGenerator INSTANCE = new WrapBeanNameGenerator();private static final String SUFFIX = "ByXiaoxu";private static final String CON = "Auto$";@Overridepublic String generateBeanName(BeanDefinition definition, BeanDefinitionRegistry registry) {String beanClassName = definition.getBeanClassName();Assert.state(beanClassName != null, "No bean class name set");return beanClassName + CON + SUFFIX;}
}
XImportRegistrar:
package com.xiaoxu.test.impo.core;import com.google.common.collect.Lists;
import com.xiaoxu.test.impo.ifc.RegistrarImport;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.BeanClassLoaderAware;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.BeanFactoryAware;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.support.AbstractBeanDefinition;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.boot.autoconfigure.AutoConfigurationPackages;
import org.springframework.context.EnvironmentAware;
import org.springframework.context.annotation.ImportBeanDefinitionRegistrar;
import org.springframework.core.annotation.AnnotationAttributes;
import org.springframework.core.env.Environment;
import org.springframework.core.type.AnnotationMetadata;
import org.springframework.util.StringUtils;import java.util.List;/*** @author xiaoxu* @date 2023-12-21* java_demo:com.xiaoxu.test.impo.core.XImportRegistrar*/
public class XImportRegistrar implements ImportBeanDefinitionRegistrar, BeanFactoryAware, BeanClassLoaderAware, EnvironmentAware {private static final String DEFAULT_FILTER_CONFIG = "useDefaultFilter";private ClassLoader classLoader;private BeanFactory beanFactory;private Environment environment;@Overridepublic void setBeanClassLoader(ClassLoader classLoader) {this.classLoader = classLoader;}@Overridepublic void setBeanFactory(BeanFactory beanFactory) throws BeansException {this.beanFactory = beanFactory;}@Overridepublic void setEnvironment(Environment environment) {this.environment = environment;}@Overridepublic void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {if (!AutoConfigurationPackages.has(beanFactory)) {return;}System.out.println("路径如果@AutoConfigurationPackage注解不配置basePackages以及Class路径,那么默认取启动类@SpringBootApplication的所在包");List<String> packages = AutoConfigurationPackages.get(this.beanFactory);System.out.println("packages is :{" + packages + "}.");System.out.println("annotated is:" + importingClassMetadata);System.out.println(importingClassMetadata.getClassName());System.out.println("Registrar开始注册DetectorScanner(有路径)");BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(DetectorScanner.class);// role 2 means internal working.builder.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);AnnotationAttributes annotationAttributes = null;if (importingClassMetadata.hasAnnotation(RegistrarImport.class.getName())) {System.out.println("我有注解RegistrarImport:");annotationAttributes = AnnotationAttributes.fromMap(importingClassMetadata.getAnnotationAttributes(RegistrarImport.class.getName(), false));System.out.println(annotationAttributes);builder.addPropertyValue("attributes", annotationAttributes);builder.addPropertyValue("metadata", importingClassMetadata);}builder.addPropertyValue("sqlMapperFactoryBeanClass", SqlMapperFactoryBean.class);builder.addPropertyValue("basePackage", StringUtils.collectionToCommaDelimitedString(packages));builder.addConstructorArgValue(Boolean.parseBoolean(System.getProperty(DEFAULT_FILTER_CONFIG, "true")));builder.addConstructorArgValue(Lists.newArrayList());AbstractBeanDefinition beanDefinition = builder.getBeanDefinition();String detectorBeanName = WrapBeanNameGenerator.INSTANCE.generateBeanName(beanDefinition, registry);System.out.println("开始注册DetectorScanner name is(有路径):" + detectorBeanName);registry.registerBeanDefinition(detectorBeanName, beanDefinition);}}
XImportSelector:
package com.xiaoxu.test.impo.core;import com.xiaoxu.test.impo.ifc.EnableSqlMapperProxy;
import org.springframework.context.annotation.DeferredImportSelector;
import org.springframework.core.annotation.AnnotationAttributes;
import org.springframework.core.type.AnnotationMetadata;
import org.springframework.util.Assert;
import org.springframework.util.ClassUtils;import java.util.Arrays;
import java.util.stream.Collectors;/*** @author xiaoxu* @date 2023-12-22* java_demo:com.xiaoxu.test.impo.core.XImportSelector*/
public class XImportSelector implements DeferredImportSelector {private AnnotationAttributes detectAttributes;@Overridepublic String[] selectImports(AnnotationMetadata annotationMetadata) {System.out.println("3. 获取外部父类");return new String[]{annotationMetadata.getEnclosingClassName()};}public void setDetectAttributes(AnnotationAttributes annotationAttributes) {this.detectAttributes = annotationAttributes;}public AnnotationAttributes getDetectAttributes() {return detectAttributes;}@Overridepublic Class<? extends Group> getImportGroup() {return XImportSelector.BundleGroup.class;}private static class BundleGroup implements DeferredImportSelector.Group {private AnnotationMetadata metadata;private AnnotationAttributes attributes;@Overridepublic void process(AnnotationMetadata metadata, DeferredImportSelector selector) {this.metadata = metadata;System.out.println("1. 处理bundle");System.out.println(metadata);System.out.println(selector);AnnotationAttributes attributes = ((XImportSelector) selector).getAttributes(metadata);this.attributes = attributes;
// factory处理不同类型的分别处理 filter 实际需要操作的配置类 为XImportSelector(DeferredImportSelector selector)
// 填充参数等等}@Overridepublic Iterable<Entry> selectImports() {System.out.println("2. 执行innnerselectImports");XImportSelector xImportSelector = new XImportSelector();String[] importNames = xImportSelector.selectImports(this.metadata);xImportSelector.setDetectAttributes(this.attributes);return Arrays.stream(importNames).map(name -> new Group.Entry(this.metadata, name)).collect(Collectors.toList());}}protected AnnotationAttributes getAttributes(AnnotationMetadata annotationMetadata) {String name = this.getSelectorDetectType().getName();AnnotationAttributes attributes = AnnotationAttributes.fromMap(annotationMetadata.getAnnotationAttributes(name, true));Assert.notNull(attributes, () -> {return "No auto-configuration attributes found. Is " + annotationMetadata.getClassName() + " annotated with " + ClassUtils.getShortName(name) + "?";});return attributes;}private Class<?> getSelectorDetectType() {return EnableSqlMapperProxy.class;}}
ifc:
package com.xiaoxu.test.impo.ifc;import java.lang.annotation.*;@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
public @interface EnableSqlMapperProxy {Class<?> value() default SqlMapper.class;
}
package com.xiaoxu.test.impo.ifc;import com.xiaoxu.test.impo.core.XImportRegistrar;
import org.springframework.context.annotation.Import;import java.lang.annotation.*;@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@Import({XImportRegistrar.class})
public @interface RegistrarImport {Class<?> value() default Object.class;String path() default "";Class<?> type() default SqlDetector.class;}
package com.xiaoxu.test.impo.ifc;import java.lang.annotation.*;@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
public @interface SqlDetector {/*** @return sql*/String sql();/*** @return 类型*/String type();}
package com.xiaoxu.test.impo.ifc;import java.lang.annotation.*;@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
public @interface SqlMapper {/*** @return 唯一id*/String uniqueId() default "";}
package com.xiaoxu.test.impo.ifc;import com.xiaoxu.test.impo.core.XImportBeanPostProcessor;
import com.xiaoxu.test.impo.core.XImportSelector;
import org.springframework.context.annotation.Import;import java.lang.annotation.*;@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE, ElementType.METHOD})
@Documented
@Inherited
@Import({XImportSelector.class, XImportBeanPostProcessor.class})
public @interface XImport {
}
infrastructure:
package com.xiaoxu.test.impo.infrastructure;import org.springframework.core.annotation.AnnotationAttributes;
import org.springframework.core.type.AnnotationMetadata;
import org.springframework.util.Assert;
import org.springframework.util.ClassUtils;/*** @author xiaoxu* @date 2024-01-18* java_demo:com.xiaoxu.test.impo.infrastructure.AttributeUtil*/
public class AttributeUtil {public static AnnotationAttributes getAttributes(AnnotationMetadata annotationMetadata, Class<?> type, boolean classValueAsString) {String name = type.getName();AnnotationAttributes attributes = AnnotationAttributes.fromMap(annotationMetadata.getAnnotationAttributes(name, classValueAsString));Assert.notNull(attributes, () -> {return "No annotated attributes found. Is " + annotationMetadata.getClassName() + " annotated with " + ClassUtils.getShortName(name) + "?";});return attributes;}}
上述代码可示,Spring扫描到的customerMapper的FactoryBean(以及fruitSqlMapper),即如上自定义注解@SqlMapper扫描生成的。
SpringBoot启动类如下:
启动SpringBoot项目,效果如下:
上述的代码实现参考自Mybatis的@Mapper接口扫描逻辑,同时也可以看出,Mybatis扫描Mapper接口所在包路径时,是按照SpringBoot项目的bean扫描路径来实现的,所以也会加入到Spring的Bean管理中,实现的形式采用的Spring的FactoryBean,故而我们平时开发中,可以直接使用@Autowired或@Resource等注解进行Mybatis的Mapper接口方法调用。
另外上述的FactoryBean的实现需要注意,比如如下的调用:
启动时报错:Bean named ‘customerMapper’ is expected to be of type ‘com.xiaoxu.test.impo.test.CustomerMapper’ but was actually of type ‘org.springframework.beans.factory.support.NullBean’
这是因为SqlMapperFactoryBean<T>的getObject()方法返回值为null导致的,如下修改即可:
测试接口增加原已定义的注解:SqlDetector
package com.xiaoxu.test.impo.test;import com.xiaoxu.test.impo.ifc.SqlDetector;
import com.xiaoxu.test.impo.ifc.SqlMapper;/*** @author xiaoxu* @date 2024-02-23*/
@SqlMapper
public interface CustomerMapper {@SqlDetector(type = "select", sql = "select * from customer")void queryCustomerById(String id);}
package com.xiaoxu.test.impo.test;import com.xiaoxu.test.impo.ifc.SqlDetector;
import com.xiaoxu.test.impo.ifc.SqlMapper;/*** @author xiaoxu* @date 2024-02-20*/
@SqlMapper
public interface FruitSqlMapper {@SqlDetector(type = "select", sql = "select * from fruit")void queryFruitById();}
新增代理类SqlMapperProxy<T>:
SqlMapperProxy<T>:
package com.xiaoxu.test.impo.core;import com.xiaoxu.test.impo.ifc.SqlDetector;import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.Map;
import java.util.function.Function;
import java.util.stream.Collectors;/*** @author xiaoxu* @date 2024-03-01* java_demo:com.xiaoxu.test.impo.core.SqlMapperProxy*/
public class SqlMapperProxy<T> implements InvocationHandler {Class<T> mapper;Map<String, Method> cache;public SqlMapperProxy(Class<T> mapper) {this.mapper = mapper;this.cache = Arrays.stream(this.mapper.getDeclaredMethods()).filter(m -> m.isAnnotationPresent(SqlDetector.class)).collect(Collectors.toMap(Method::getName, Function.identity(), (k1, k2) -> k1));}@Overridepublic Object invoke(Object proxy, Method method, Object[] args) throws Throwable {Method cachedMethod = this.cache.get(method.getName());if (cachedMethod != null) {SqlDetector sqlDetector = cachedMethod.getAnnotation(SqlDetector.class);System.out.println("执行" + sqlDetector.type() + "语句:" + sqlDetector.sql());} else {//否则执行Object的toString\hashCode\equals方法// 但是因为这里是接口,没有toString,简单自定义下if (method.getName().equalsIgnoreCase("toString")) {return "@SqlDetector{" + method.getName() + "} " + this.mapper.getCanonicalName();}}return null;}
}
同时修改SqlMapperFactoryBean<T>:
package com.xiaoxu.test.impo.core;import org.springframework.beans.factory.FactoryBean;import java.lang.reflect.Proxy;/*** @author xiaoxu* @date 2024-01-22* java_demo:com.xiaoxu.test.impo.core.SqlMapperFactoryBean*/
public class SqlMapperFactoryBean<T> implements FactoryBean<T> {private Class<T> sqlMapperClazz;public SqlMapperFactoryBean() {}private SqlMapperFactoryBean(Class<T> sqlMapperClazz) {this.sqlMapperClazz = sqlMapperClazz;}@Overridepublic T getObject() throws Exception {return (T) Proxy.newProxyInstance(SqlMapperFactoryBean.class.getClassLoader(),new Class[]{this.sqlMapperClazz}, new SqlMapperProxy(this.sqlMapperClazz));}@Overridepublic Class<?> getObjectType() {return this.sqlMapperClazz;}@Overridepublic boolean isSingleton() {return FactoryBean.super.isSingleton();}
}
重新执行结果如下:
单测亦执行成功:
最后的思考:
参考Mybatis框架的Mapper接口扫描逻辑可知,每个接口生成的FactoryBean的构造方法参数是Class,但是我们赋值时设置为全限定类名的String字符串,而Spring可以正常生成bean而没有报错。
同样场景,上述我的逻辑中,构造方法参数设置为类名字符串,实际定义的SqlMapperFactoryBean类中只有含有Class参数的构造方法(另一个是无参构造方法),为什么没有问题呢?因为Spring底层支持使用全限定类名转换成Class对象的功能,如此便可以匹配到对应的Constructor并生成bean。
参考如下org.springframework.beans.factory.support.ConstructorResolver类,用于处理无参Constructor以及含参Constructor,且通过Mybatis框架可得,注册BeanDefinition时,Mybatis框架添加的Constructor的参数为全限定类名的字符串String,依赖Spring框架可以自动将其转换成Class,即可以适配mybatis的public MapperFactoryBean(Class<T> mapperInterface)构造方法,如下可示:
private ArgumentsHolder createArgumentArray(String beanName, RootBeanDefinition mbd, @Nullable ConstructorArgumentValues resolvedValues,BeanWrapper bw, Class<?>[] paramTypes, @Nullable String[] paramNames, Executable executable,boolean autowiring, boolean fallback) throws UnsatisfiedDependencyException {
org.mybatis.spring.mapper.MapperFactoryBean如下:
public class MapperFactoryBean<T> extends SqlSessionDaoSupport implements FactoryBean<T> {private Class<T> mapperInterface;private boolean addToConfig = true;public MapperFactoryBean() {}public MapperFactoryBean(Class<T> mapperInterface) {this.mapperInterface = mapperInterface;}
如下可见,originalValue为类的全限定名称String,但是构造方法的参数为Class,
判断是否支持转换,无法转换,则继续往下执行:
往下执行时,在org.springframework.beans.TypeConverterDelegate中,最终将全限定类名字符串,转换成为Class对象:
@Nullable
private Object doConvertValue(@Nullable Object oldValue, @Nullable Object newValue,@Nullable Class<?> requiredType, @Nullable PropertyEditor editor) {Object convertedValue = newValue;if (editor != null && !(convertedValue instanceof String)) {// Not a String -> use PropertyEditor's setValue.// With standard PropertyEditors, this will return the very same object;// we just want to allow special PropertyEditors to override setValue// for type conversion from non-String values to the required type.try {editor.setValue(convertedValue);Object newConvertedValue = editor.getValue();if (newConvertedValue != convertedValue) {convertedValue = newConvertedValue;// Reset PropertyEditor: It already did a proper conversion.// Don't use it again for a setAsText call.editor = null;}}catch (Exception ex) {if (logger.isDebugEnabled()) {logger.debug("PropertyEditor [" + editor.getClass().getName() + "] does not support setValue call", ex);}// Swallow and proceed.}}Object returnValue = convertedValue;if (requiredType != null && !requiredType.isArray() && convertedValue instanceof String[]) {// Convert String array to a comma-separated String.// Only applies if no PropertyEditor converted the String array before.// The CSV String will be passed into a PropertyEditor's setAsText method, if any.if (logger.isTraceEnabled()) {logger.trace("Converting String array to comma-delimited String [" + convertedValue + "]");}convertedValue = StringUtils.arrayToCommaDelimitedString((String[]) convertedValue);}if (convertedValue instanceof String) {if (editor != null) {// Use PropertyEditor's setAsText in case of a String value.if (logger.isTraceEnabled()) {logger.trace("Converting String to [" + requiredType + "] using property editor [" + editor + "]");}String newTextValue = (String) convertedValue;return doConvertTextValue(oldValue, newTextValue, editor);}else if (String.class == requiredType) {returnValue = convertedValue;}}return returnValue;
}
转换成功:
并返回Constructor方法的参数args:
通过ctor.newInstance(argsWithDefaultValues)方法,将该Bean实例初始化成功:
核心调用如下: