目 录CONTENT

文章目录

【实践】Spring编码时的常见问题解析

FatFish1
2024-11-06 / 0 评论 / 0 点赞 / 72 阅读 / 0 字 / 正在检测是否收录...

问题1 - 给abstract类注解@Component会出现什么情况?

A:会将带有@Lookup注解方法的抽象类注册成bean

众所周知,abstract类是不能实例化的,但实际上spring注册bean与实例化是存在差异的。bean是被托管后的实例,尽管抽象类不能实例化,但是依旧还是可以有bean的。这部分可以参考代码ClassPathScanningCandidateComponentProvider#scanCandidateComponents

ScannedGenericBeanDefinition sbd = new ScannedGenericBeanDefinition(metadataReader);
sbd.setSource(resource);
if (isCandidateComponent(sbd)) {
    ……
}

使用方法isCandidateComponent(sbd)判断该类是否是需要被扫描到注册成bean的类

继续分析源码

protected boolean isCandidateComponent(AnnotatedBeanDefinition beanDefinition) {
    AnnotationMetadata metadata = beanDefinition.getMetadata();
    return (metadata.isIndependent() && (metadata.isConcrete() ||
          (metadata.isAbstract() && metadata.hasAnnotatedMethods(Lookup.class.getName()))));
}

可以看到这里判断时需同时满足以下两个条件

  • 条件1 - metadata.isIndependent():是独立类,即非内部类

  • 条件2 - 满足以下两种场景之一:

    • metadata.isConcrete():是具体类,即非抽象类或接口

    • 同时满足以下两种场景:

      • metadata.isAbstract():是抽象类

      • metadata.hasAnnotatedMethods(Lookup.class.getName())且有方法被@Lookup注解

因此可以得知,abstract类是可以被注册成bean的,但前提条件是其中有方法被@Lookup注解

@Lookup标签的作用是将注解的方法重写为调用BeanFactory#getBean()方法,返回一个对应的实例bean,它的作用是实现单例中使用非单例,从而解决多线程产生的并发问题

看下面例子:

假设有一个在多线程状态下启动的service

// 配置spring环境
@Configuration
@ComponentScan("project.myproject.test")
public class ApplicaitonConfig {
}

// 模拟一个多线程执行的service
@Service
@ExtendWith(SpringExtension.class)
@ContextConfiguration(classes = ApplicaitonConfig.class)
public class lookuptest {

    @Autowired
    private LookupComponent lookupComponent;

    @Test
    public void lookuptest() throws InterruptedException {
        new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println(lookupComponent.getTestClass().hashCode());
            }
        }).start();
        new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println(lookupComponent.getTestClass().hashCode());
            }
        }).start();
        TimeUnit.SECONDS.sleep(5);

    }
}

这个service在方法中起了两个线程,调用lookupComponent.getTestClass() 方法,看实现:

// 写法1
@Component
public class LookupComponent {
    
    @Autowired
    private TestClass testClass

    public TestClass getTestClass() {
        return testClass;
    }
}

// 写法2
@Component
public class LookupComponent {

    @Lookup
    public TestClass getTestClass() {
        return null;
    }
}

// 写法3
@Component
public abstract class LookupComponent {
    @Lookup
    public abstract TestClass getTestClass();
}

可以看到写法1是通过注入bean,返回bean,而写法2是通过注解@Lookup转换为调用BeanFactory#getBean() ,写法3是放弃了@Lookup方法中的空实现改完使用抽象类

再看TestClass的实现:

// 写法A
@Component
public class TestClass {
}

// 写法B
@Component
@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
public class TestClass {
}

写法B相比于写法A,增加了@Scope注解决定B为非单例模式

在这个测试场景下,可以实现非单例获取的为{写法2x写法B},{写法3x写法B},两个场景,原因如下:

  • 使用写法A,TestClass为非单例,每次使用BeanFactory#getBean()从三级缓存中获取,拿到都是同一个bean,因此不能获取非单例bean

  • 写法1,在bean加载阶段就已经使用BeanFactory#getBean()获取了bean并且完成了注入,并且BeanFactory#getBean()方法有且仅有1次调用,因此拿到的也是单例bean

而写法3相比于写法2,就是省略了return null;这种坏味道代码,也就是本问题之前的疑问,abstract+@Lookup可以被注册为bean的设计理念所在

问题2 - 对于由spring托管的子类,spring如何处理继承对象的注入?

A:对于spring托管的实现类,其父类在没有实例化需求时一般不使用@Service/@Component注解(例如抽象父类,用了也白用,除非是问题1的场景),但是其成员变量可以使用@Autowired注入,子类在完成populate工作时,会向上寻找其父类实现中具有@Autowired注解的成员变量进行注入

当一个子类的父类无需被spring托管,或父类为抽象类无需实例化时,子类未重写的方法调用到父类的成员变量时,可以借助父类的@Autowired注解进行注入

看一个案例:

// 父类实现
public class AbstractFather {

    @Autowired
    private EatService eatService;

    public void whoEat(String name) {
        System.out.println(String.format(eatService.eat(), name));
    }
}

// 子类实现
@Component
public class Son extends AbstractFather{

}

// service实现
@Service
public class EatService {
    public String eat() {
        return "%s is eating";
    }
}

// 配置spring环境
@Configuration
@ComponentScan("project.myproject.test")
public class ApplicaitonConfig {
}

@ExtendWith(SpringExtension.class)
@ContextConfiguration(classes = ApplicationConfig.class)
public class ExtendTest {
    @Autowired
    private Son son;
    @Test
    public void test() {
        son.whoEat("son");
    }
}

这里在调用son.whoEat() 时,可知调用到父类的方法,使用的是父类的eatService

如果子类重写了父类的whoEat()方法,就必须自己声明eatService变量,同时自行完成注入,看下案例:

@Component
public class Son extends AbstractFather{
    
    @Autowired
    private EatService eatService;

    @Override
    public void whoEat(String name) {
        System.out.println(String.format(eatService.eat(), "name no way"));
    }
}

对于此问题的源码在AbstractAutowireCapableBeanFactory#populateBean这个方法是创建bean实例三步中的第二步,作用的bean的属性注入

// AbstractAutowireCapableBeanFactory#populateBean
for (InstantiationAwareBeanPostProcessor bp : getBeanPostProcessorCache().instantiationAware) {
	PropertyValues pvsToUse = bp.postProcessProperties(pvs, bw.getWrappedInstance(), beanName);

这里是通过InstantiationAwareBeanPostProcessor#postProcessProperties的实现类找属性注入的入口,处理@Autowired注解的实现类和方法是是AutowiredAnnotationBeanPostProcessor#postProcessProperties

public PropertyValues postProcessProperties(PropertyValues pvs, Object bean, String beanName) {
    InjectionMetadata metadata = findAutowiringMetadata(beanName, bean.getClass(), pvs);

// AutowiredAnnotationBeanPostProcessor#findAutowiringMetadata
metadata = buildAutowiringMetadata(clazz);

继续跟进

// org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor#buildAutowiringMetadata
do {
    // 模块1、反射出所有field,找到带有@Autowired方法的
	final List<InjectionMetadata.InjectedElement> fieldElements = new ArrayList<>();
	ReflectionUtils.doWithLocalFields(targetClass, field -> {
		MergedAnnotation<?> ann = findAutowiredAnnotation(field);
		if (ann != null) {
			if (Modifier.isStatic(field.getModifiers())) {
				if (logger.isInfoEnabled()) {
					logger.info("Autowired annotation is not supported on static fields: " + field);
				}
				return;
			}
			boolean required = determineRequiredStatus(ann);
			fieldElements.add(new AutowiredFieldElement(field, required));
		}
	});

    // 模块2、反射出所有method,找到带有@Autowired方法的,例如set方法
	final List<InjectionMetadata.InjectedElement> methodElements = new ArrayList<>();
	ReflectionUtils.doWithLocalMethods(targetClass, method -> {
		Method bridgedMethod = BridgeMethodResolver.findBridgedMethod(method);
		if (!BridgeMethodResolver.isVisibilityBridgeMethodPair(method, bridgedMethod)) {
			return;
		}
		MergedAnnotation<?> ann = findAutowiredAnnotation(bridgedMethod);
		if (ann != null && method.equals(ClassUtils.getMostSpecificMethod(method, clazz))) {
			if (Modifier.isStatic(method.getModifiers())) {
				if (logger.isInfoEnabled()) {
					logger.info("Autowired annotation is not supported on static methods: " + method);
				}
				return;
			}
			if (method.getParameterCount() == 0) {
				if (logger.isInfoEnabled()) {
					logger.info("Autowired annotation should only be used on methods with parameters: " +
							method);
				}
			}
			boolean required = determineRequiredStatus(ann);
			PropertyDescriptor pd = BeanUtils.findPropertyForMethod(bridgedMethod, clazz);
			methodElements.add(new AutowiredMethodElement(method, required, pd));
		}
	});
	elements.addAll(0, sortMethodElements(methodElements, targetClass));
	elements.addAll(0, fieldElements);
	targetClass = targetClass.getSuperclass();
}
while (targetClass != null && targetClass != Object.class);

可以看到方法主体是一个do-while框架,每次循环分别找被@Autowired注解的field和method,但是找field的时候是找不到父类的,因为里面反射的时候使用的是clazz.getDeclaredFields() 方法

public static void doWithLocalFields(Class<?> clazz, FieldCallback fc) {
	for (Field field : getDeclaredFields(clazz)) {

// -----------------------------------------------------------------------------------------------------------

private static Field[] getDeclaredFields(Class<?> clazz) {
	Assert.notNull(clazz, "Class must not be null");
	Field[] result = declaredFieldsCache.get(clazz);
	if (result == null) {
		try {
			result = clazz.getDeclaredFields();
			declaredFieldsCache.put(clazz, (result.length == 0 ? EMPTY_FIELD_ARRAY : result));
		}
		catch (Throwable ex) {
			throw new IllegalStateException("Failed to introspect Class [" + clazz.getName() +
					"] from ClassLoader [" + clazz.getClassLoader() + "]", ex);
		}
	}
	return result;
}

真正能找到父类是因为最后一行逻辑

targetClass = targetClass.getSuperclass();

每次获取其父类,继续循环,指导父类为null,或父类为Object.class,停止循环

这部分可以配合spring-beans创建流程看:

http://www.chymfatfish.cn/archives/spring-beansgetfromfactory#postprocessmergedbeandefinition

问题3 - spring如何处理被@Component注解的内部类

A:内部类也使用@Component注解,外部类也必须被spring托管(例如使用@Component注解),这样内部类可以被spring托管成bean,且不关心内部类使用哪种关键词修饰

这个问题要从ApplicationContext的refresh流程开始看,在AbstractApplicationContext#refresh 方法中有一步调用:

// --- org.springframework.context.support.AbstractApplicationContext#refresh ---

// Invoke factory processors registered as beans in the context.
invokeBeanFactoryPostProcessors(beanFactory);

调用invokeBeanFactoryPostProcessors激活beanDefinition加载前的前置处理器

protected void invokeBeanFactoryPostProcessors(ConfigurableListableBeanFactory beanFactory) {
    PostProcessorRegistrationDelegate.invokeBeanFactoryPostProcessors(beanFactory, getBeanFactoryPostProcessors());
    ……
}

它的核心代码是两个部分:通过getBeanFactoryPostProcessors()方法获取到通过AbstractApplicationContext#addBeanFactoryPostProcessor 方法硬编码的前置处理器,如何调用PostProcessorRegistrationDelegate#invokeBeanFactoryPostProcessors 激活前置处理器

public static void invokeBeanFactoryPostProcessors(
			ConfigurableListableBeanFactory beanFactory, List<BeanFactoryPostProcessor> beanFactoryPostProcessors) {

		Set<String> processedBeans = new HashSet<>();
        
        // part1. if-else 部分 激活硬编码的前置处理器
		if (beanFactory instanceof BeanDefinitionRegistry) {
			……
		}

		else {
			// Invoke factory processors registered with the context instance.
			invokeBeanFactoryPostProcessors(beanFactoryPostProcessors, beanFactory);
		}

        // part2. 激活注册的前置处理器
		……

补充前置处理器的分类:

  • 硬编码的:通过AbstractApplicationContext#addBeanFactoryPostProcessor 方法添加进来的

  • 注册的:通过PriorityOrdered接口、Ordered接口、No-Ordered没有实现排序接口的处理器

  • BeanFactoryPostProcessor和BeanDefinitionRegistryPostProcessor两类,二者均可以采用硬编码的,也可以采用注册的

可以看的invokeBeanFactoryPostProcessors的主体框架:

  • part1部分是一个if-else,条件BeanFactory是否是BeanDefinitionRegistry的实现

    • 如果是,走if,处理硬编码进来的BeanFactoryPostProcessors处理器和硬编码的+注册进来的BeanDefinitionRegistryPostProcessor处理器

    • 如果否,走else,只处理硬编码进来的BeanFactoryPostProcessors处理器

  • part2部分省略掉了,是处理注册进来的BeanFactoryPostProcessors

因为默认情况下使用的BeanFactory是DefaultListableBeanFactory,它实现了BeanDefinitionRegistry,因此在part1判断的时候走if路线

public class DefaultListableBeanFactory extends AbstractAutowireCapableBeanFactory
       implements ConfigurableListableBeanFactory, BeanDefinitionRegistry, Serializable

看if中的逻辑

BeanDefinitionRegistry registry = (BeanDefinitionRegistry) beanFactory;
List<BeanFactoryPostProcessor> regularPostProcessors = new ArrayList<>();
List<BeanDefinitionRegistryPostProcessor> registryProcessors = new ArrayList<>();

首先预置了两个List,就是为了区分BeanFactoryPostProcessors和BeanDefinitionRegistryPostProcessor两类处理器

然后处理硬编码的部分,即方法入参进来的处理器,判断属于哪一类,分别存入对应的list

for (BeanFactoryPostProcessor postProcessor : beanFactoryPostProcessors) {
    if (postProcessor instanceof BeanDefinitionRegistryPostProcessor) {
       BeanDefinitionRegistryPostProcessor registryProcessor =
             (BeanDefinitionRegistryPostProcessor) postProcessor;
       registryProcessor.postProcessBeanDefinitionRegistry(registry);
       registryProcessors.add(registryProcessor);
    }
    else {
       regularPostProcessors.add(postProcessor);
    }
}

然后找注册进来的BeanDefinitionRegistryPostProcessor处理器

第一部分是实现了PriorityOrdered接口的BeanDefinitionRegistryPostProcessor处理器

// First, invoke the BeanDefinitionRegistryPostProcessors that implement PriorityOrdered.
String[] postProcessorNames =
       beanFactory.getBeanNamesForType(BeanDefinitionRegistryPostProcessor.class, true, false);
for (String ppName : postProcessorNames) {
    if (beanFactory.isTypeMatch(ppName, PriorityOrdered.class)) {
       currentRegistryProcessors.add(beanFactory.getBean(ppName, BeanDefinitionRegistryPostProcessor.class));
       processedBeans.add(ppName);
    }
}
sortPostProcessors(currentRegistryProcessors, beanFactory);
registryProcessors.addAll(currentRegistryProcessors);
invokeBeanDefinitionRegistryPostProcessors(currentRegistryProcessors, registry, beanFactory.getApplicationStartup());
currentRegistryProcessors.clear();

在这里会找到一个专门处理注解的处理器ConfigurationClassPostProcessor,然后激活它的postProcessBeanDefinitionRegistry方法

private static void invokeBeanDefinitionRegistryPostProcessors(
       Collection<? extends BeanDefinitionRegistryPostProcessor> postProcessors, BeanDefinitionRegistry registry, ApplicationStartup applicationStartup) {
    for (BeanDefinitionRegistryPostProcessor postProcessor : postProcessors) {
       StartupStep postProcessBeanDefRegistry = applicationStartup.start("spring.context.beandef-registry.post-process")
             .tag("postProcessor", postProcessor::toString);
       postProcessor.postProcessBeanDefinitionRegistry(registry);
       postProcessBeanDefRegistry.end();
    }
}

向下跟踪调用到ConfigurationClassPostProcessor#processConfigBeanDefinitions方法

public void processConfigBeanDefinitions(BeanDefinitionRegistry registry) {
	List<BeanDefinitionHolder> configCandidates = new ArrayList<>();
	String[] candidateNames = registry.getBeanDefinitionNames();
    // 依次遍历候选类,找到非@Configuration的类,存入configCandidates中等待实例化
	for (String beanName : candidateNames) {
		BeanDefinition beanDef = registry.getBeanDefinition(beanName);
		if (beanDef.getAttribute(ConfigurationClassUtils.CONFIGURATION_CLASS_ATTRIBUTE) != null) {
			if (logger.isDebugEnabled()) {
				logger.debug("Bean definition has already been processed as a configuration class: " + beanDef);
			}
		}
		else if (ConfigurationClassUtils.checkConfigurationClassCandidate(beanDef, this.metadataReaderFactory)) {
			configCandidates.add(new BeanDefinitionHolder(beanDef, beanName));
		}
	}

这里首先找到非@Configuration的类,存入configCandidates中等待实例化,后面又候选类的判断和排序,跳过,直接看实例化的部分

ConfigurationClassParser parser = new ConfigurationClassParser(
		this.metadataReaderFactory, this.problemReporter, this.environment,
		this.resourceLoader, this.componentScanBeanNameGenerator, registry);

Set<BeanDefinitionHolder> candidates = new LinkedHashSet<>(configCandidates);
Set<ConfigurationClass> alreadyParsed = new HashSet<>(configCandidates.size());
do {
    StartupStep processConfig = this.applicationStartup.start("spring.context.config-classes.parse");
    parser.parse(candidates);

调用ConfigurationClassParser#parse方法进行实例化,这里一路向下跟踪,调用到ConfigurationClassParser#doProcessConfigurationClass 方法,该方法处理了@Component、@PropertySource、@ComponentScan、@Import、@ImportResource、@Bean几种注解

if (configClass.getMetadata().isAnnotated(Component.class.getName())) {
	// Recursively process any member (nested) classes first
	processMemberClasses(configClass, sourceClass, filter);
}

对于@Component注解,首先要处理它的内部类,调用来到ConfigurationClassParser#processMemberClasses方法

private void processMemberClasses(ConfigurationClass configClass, SourceClass sourceClass,
       Predicate<String> filter) throws IOException {
    // 找到内部类
    Collection<SourceClass> memberClasses = sourceClass.getMemberClasses();
    if (!memberClasses.isEmpty()) {
       List<SourceClass> candidates = new ArrayList<>(memberClasses.size());
       for (SourceClass memberClass : memberClasses) {
          // 对内部类进行校验,通过校验,加入到候选类中进行实例化
          if (ConfigurationClassUtils.isConfigurationCandidate(memberClass.getMetadata()) &&
                !memberClass.getMetadata().getClassName().equals(configClass.getMetadata().getClassName())) {
             candidates.add(memberClass);
          }
       }
       OrderComparator.sort(candidates);
       for (SourceClass candidate : candidates) {
          if (this.importStack.contains(configClass)) {
             this.problemReporter.error(new CircularImportProblem(configClass, this.importStack));
          }
          else {
             this.importStack.push(configClass);
             try {
                processConfigurationClass(candidate.asConfigClass(configClass), filter);
             }
             finally {
                this.importStack.pop();
             }
          }
       }
    }
}

可以看到这里有两处可能被校验掉的条件:

  • 找内部类的getMemberClasses方法

  • 校验的isConfigurationCandidate方法

先看getMemberClasses方法

public Collection<SourceClass> getMemberClasses() throws IOException {
    ……
          Class<?>[] declaredClasses = sourceClass.getDeclaredClasses();
          List<SourceClass> members = new ArrayList<>(declaredClasses.length);
          for (Class<?> declaredClass : declaredClasses) {
             members.add(asSourceClass(declaredClass, DEFAULT_EXCLUSION_FILTER));
          }
          return members;
       }
       ……
    }

可以看到是直接通过反射getDeclaredClasses()方法获取的,因此不管什么private、protect、public、static关键词,都是可以拿到的

然后用DEFAULT_EXCLUSION_FITER执行了一次过滤

private static final Predicate<String> DEFAULT_EXCLUSION_FILTER = className ->
       (className.startsWith("java.lang.annotation.") || className.startsWith("org.springframework.stereotype."));

再看isConfigurationCandidate 方法

public static boolean isConfigurationCandidate(AnnotationMetadata metadata) {
    // Do not consider an interface or an annotation...
    if (metadata.isInterface()) {
       return false;
    }
    // Any of the typical annotations found?
    for (String indicator : candidateIndicators) {
       if (metadata.isAnnotated(indicator)) {
          return true;
       }
    }
    // Finally, let's look for @Bean methods...
    return hasBeanMethods(metadata);
}

// ------- ConfigurationClassUtils -----------------------------------------
private static final Set<String> candidateIndicators = new HashSet<>(8);
static {
	candidateIndicators.add(Component.class.getName());
	candidateIndicators.add(ComponentScan.class.getName());
	candidateIndicators.add(Import.class.getName());
	candidateIndicators.add(ImportResource.class.getName());
}

条件有三个:

  • 不允许是内部接口

  • 如果包含@Component、@ComponentScan、@Import、@ImportResource中任意即可

  • 不包含上述四个注解,必须有@Bean注解的方法在其中

到这里结论就很明显了,@Component注解的内部类,外部类也必须被spring托管,这样内部类可以加载成bean

0

评论区