1. Spring如何扫描自身注解并将其初始化为BeanDefinition。
可以看看我的这篇文章@ComponentScan注解原理
Spring扫描@Component注解的类并将其初始化为BeanDefinition
首先Spring有一个扫描器ClassPathBeanDefinitionScanner,来扫描@SpringBootApplication注解里面scanBasePackages()方法配置的扫描路径,如果没有配置,那就扫描启动类包路径下所有的类。
如何扫描呢?先看看ClassPathBeanDefinitionScanner扫描类,注释上说了要扫描@Component、@Repository、@Service、@Controller以及ManagedBean和Named。
/**
* A bean definition scanner that detects bean candidates on the classpath,
* registering corresponding bean definitions with a given registry ({@code BeanFactory}
* or {@code ApplicationContext}).
*
* <p>Candidate classes are detected through configurable type filters. The
* default filters include classes that are annotated with Spring's
* {@link org.springframework.stereotype.Component @Component},
* {@link org.springframework.stereotype.Repository @Repository},
* {@link org.springframework.stereotype.Service @Service}, or
* {@link org.springframework.stereotype.Controller @Controller} stereotype.
*
* <p>Also supports Java EE 6's {@link javax.annotation.ManagedBean} and
* JSR-330's {@link javax.inject.Named} annotations, if available.
*
* @author Mark Fisher
* @author Juergen Hoeller
* @author Chris Beams
* @since 2.5
* @see AnnotationConfigApplicationContext#scan
* @see org.springframework.stereotype.Component
* @see org.springframework.stereotype.Repository
* @see org.springframework.stereotype.Service
* @see org.springframework.stereotype.Controller
*/
public class ClassPathBeanDefinitionScanner extends ClassPathScanningCandidateComponentProvider {
public ClassPathBeanDefinitionScanner(BeanDefinitionRegistry registry, boolean useDefaultFilters,
Environment environment, @Nullable ResourceLoader resourceLoader) {
Assert.notNull(registry, "BeanDefinitionRegistry must not be null");
this.registry = registry;
if (useDefaultFilters) {
// 初始化的时候调用注册默认filter,添加@Component注解
registerDefaultFilters();
}
setEnvironment(environment);
setResourceLoader(resourceLoader);
}
protected Set<BeanDefinitionHolder> doScan(String... basePackages) {
Assert.notEmpty(basePackages, "At least one base package must be specified");
Set<BeanDefinitionHolder> beanDefinitions = new LinkedHashSet<>();
for (String basePackage : basePackages) {
// 扫描得到候选BeanDefinition
Set<BeanDefinition> candidates = findCandidateComponents(basePackage);
···
}
}
后面两个很少用,这里说前面4个注解。你会发现有一个共同点@Repository、@Service、@Controller这三个注解里面都标注了@Component.
其实你自定义一个注解,跟上面三个注解一样,标注到类上,也会被spring扫描到,然后初始化为BeanDefinition。效果等同于@Repository、@Service、@Controller。再说一嘴,其实这三个注解本质上是一样的,可以相互替代,只是官方为了使注解有不同的含义,才弄了几个名字。具体看官网,位置我忘记在哪里了。
比如@NameRepository:
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Component
public @interface StringRepository {
String value() default "";
}
至于具体是怎么生效的,可以看我另一篇文章
这里只简单提一下如何扫描@Component.
org.springframework.context.annotation.ClassPathScanningCandidateComponentProvider#scanCandidateComponents
private Set<BeanDefinition> scanCandidateComponents(String basePackage) {
Set<BeanDefinition> candidates = new LinkedHashSet<>();
try {
String packageSearchPath = ResourcePatternResolver.CLASSPATH_ALL_URL_PREFIX +
resolveBasePackage(basePackage) + '/' + this.resourcePattern;
Resource[] resources = getResourcePatternResolver().getResources(packageSearchPath);
for (Resource resource : resources) {
if (resource.isReadable()) {
try {
MetadataReader metadataReader = getMetadataReaderFactory().getMetadataReader(resource);
// 这里是关键
if (isCandidateComponent(metadataReader)) {
ScannedGenericBeanDefinition sbd = new ScannedGenericBeanDefinition(metadataReader);
sbd.setSource(resource);
if (isCandidateComponent(sbd)) {
candidates.add(sbd);
}
···
}
return candidates;
}
protected boolean isCandidateComponent(MetadataReader metadataReader) throws IOException {
for (TypeFilter tf : this.excludeFilters) {
if (tf.match(metadataReader, getMetadataReaderFactory())) {
return false;
}
}
// 只要includeFilters包含@Component注解,就满足.
// 而初始化ClassPathBeanDefinitionScanner的时候调用registerDefaultFilters()方法注入了@Component
for (TypeFilter tf : this.includeFilters) {
if (tf.match(metadataReader, getMetadataReaderFactory())) {
return isConditionMatch(metadataReader);
}
}
return false;
}
protected void registerDefaultFilters() {
this.includeFilters.add(new AnnotationTypeFilter(Component.class));
ClassLoader cl = ClassPathScanningCandidateComponentProvider.class.getClassLoader();
try {
this.includeFilters.add(new AnnotationTypeFilter(
((Class<? extends Annotation>) ClassUtils.forName("javax.annotation.ManagedBean", cl)), false));
···
}
总结:初始化为BeanDefinition共分为2步
- 扫描器扫描类
- 满足isCandidateComponent()方法
那要是初始化为bean呢?还需要一步,调用getBean(beanName)方法
org.springframework.beans.factory.support.DefaultListableBeanFactory#preInstantiateSingletons
public void preInstantiateSingletons() throws BeansException {
List<String> beanNames = new ArrayList<>(this.beanDefinitionNames);
// Trigger initialization of all non-lazy singleton beans...
for (String beanName : beanNames) {
RootBeanDefinition bd = getMergedLocalBeanDefinition(beanName);
// 初始化为bean是需要条件的 不能是抽象类或者接口,因为无法实例化
if (!bd.isAbstract() && bd.isSingleton() && !bd.isLazyInit()) {
if (isFactoryBean(beanName)) {
Object bean = getBean(FACTORY_BEAN_PREFIX + beanName);
if (bean instanceof FactoryBean) {
FactoryBean<?> factory = (FactoryBean<?>) bean;
boolean isEagerInit;
if (System.getSecurityManager() != null && factory instanceof SmartFactoryBean) {
isEagerInit = AccessController.doPrivileged(
(PrivilegedAction<Boolean>) ((SmartFactoryBean<?>) factory)::isEagerInit,
getAccessControlContext());
}
else {
isEagerInit = (factory instanceof SmartFactoryBean &&
((SmartFactoryBean<?>) factory).isEagerInit());
}
if (isEagerInit) {
getBean(beanName);
}
}
}
else {
getBean(beanName);
}
}
}
所以还需要加一条初始化成bean的时候,该类不能为接口或者抽象类。
2. Spring如何初始化自定义注解?
open-Feign里面的@FeignClient使用。可以参考我的文章@FeignClient使用及原理
都是定义一个接口,然后加上自定义的注解,就可以初始化为bean的,这与之前分析的Spring扫描自身注解的原理相悖论。
- 扫描自定义注解,拿feign里面的
@FeignClient来说,那就重写一个扫描器,让isCandidateComponent方法里面的includeFilters包含@FeignClient即可
org.springframework.cloud.openfeign.FeignClientsRegistrar#registerFeignClients
public void registerFeignClients(AnnotationMetadata metadata,
BeanDefinitionRegistry registry) {
ClassPathScanningCandidateComponentProvider scanner = getScanner();
scanner.setResourceLoader(this.resourceLoader);
Set<String> basePackages;
Map<String, Object> attrs = metadata
.getAnnotationAttributes(EnableFeignClients.class.getName());
// 这里添加了注解@FeignClient
AnnotationTypeFilter annotationTypeFilter = new AnnotationTypeFilter(
FeignClient.class);
final Class<?>[] clients = attrs == null ? null
: (Class<?>[]) attrs.get("clients");
if (clients == null || clients.length == 0) {
scanner.addIncludeFilter(annotationTypeFilter);
basePackages = getBasePackages(metadata);
}
else {
final Set<String> clientClasses = new HashSet<>();
basePackages = new HashSet<>();
for (Class<?> clazz : clients) {
basePackages.add(ClassUtils.getPackageName(clazz));
clientClasses.add(clazz.getCanonicalName());
}
AbstractClassTestingTypeFilter filter = new AbstractClassTestingTypeFilter() {
@Override
protected boolean match(ClassMetadata metadata) {
String cleaned = metadata.getClassName().replaceAll("\\$", ".");
return clientClasses.contains(cleaned);
}
};
// 添加到IncludeFilter
scanner.addIncludeFilter(
new AllTypeFilter(Arrays.asList(filter, annotationTypeFilter)));
}
for (String basePackage : basePackages) {
Set<BeanDefinition> candidateComponents = scanner
.findCandidateComponents(basePackage);
for (BeanDefinition candidateComponent : candidateComponents) {
if (candidateComponent instanceof AnnotatedBeanDefinition) {
// verify annotated class is an interface
AnnotatedBeanDefinition beanDefinition = (AnnotatedBeanDefinition) candidateComponent;
AnnotationMetadata annotationMetadata = beanDefinition.getMetadata();
Assert.isTrue(annotationMetadata.isInterface(),
"@FeignClient can only be specified on an interface");
Map<String, Object> attributes = annotationMetadata
.getAnnotationAttributes(
FeignClient.class.getCanonicalName());
String name = getClientName(attributes);
registerClientConfiguration(registry, name,
attributes.get("configuration"));
registerFeignClient(registry, annotationMetadata, attributes);
}
}
}
}
- 接口不能注入成bean,这个可以使用动态代理解决。
给生成的BeanDefinition的beanClass属性设置一个FactoryBean.class,那么实例化的时候,就会通过FactoryBean.getObject()获取一个实例。
我们熟知的很多框架,比如MyBatis中的@Mapper,Dubbo里面的@Service和@Reference以及JPA框架里面的Repository接口。
都是上面这种套路来扫描自定义的注解或者接口模式,有兴趣的自己跟着文章去debug一下。