问题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创建流程看:
问题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
评论区