mybatis篇
mybatis3.5.3
mybatis-spring2.0.3
用过mybatis的同学应该对@MapperScan
这个注解都不陌生吧,为什么启用了这个注解就能将mybatis集成到了spring中了呢。带着这个疑问我们先来看下这个注解里面有什么。
@Import(MapperScannerRegistrar.class)
@Repeatable(MapperScans.class)
public @interface MapperScan {
String[] basePackages() default {};
Class>[] basePackageClasses() default {};
}
代码里面唯一能和spring扯上关系的应该就是 @Import(MapperScannerRegistrar.class)
这一行了吧。先来看下spring官方对Import
这个注解是怎样解释的。
Provides functionality equivalent to theelement in Spring XML. Allows for importing @Configuration classes, ImportSelector and ImportBeanDefinitionRegistrar implementations
翻译过来就是:这个注解的功能类似于早期xml配置里的import标签,可以用来导入带@Configuration注解的配置类或者实现了ImportSelector,ImportBeanDefinitionRegistrar
接口的实现类。
再来看下 MapperScannerRegistrar
类里的核心逻辑
public class MapperScannerRegistrar implements ImportBeanDefinitionRegistrar, ResourceLoaderAware {
@Override
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
AnnotationAttributes mapperScanAttrs = AnnotationAttributes
.fromMap(importingClassMetadata.getAnnotationAttributes(MapperScan.class.getName()));
if (mapperScanAttrs != null) {
registerBeanDefinitions(mapperScanAttrs, registry, generateBaseBeanName(importingClassMetadata, 0));
}
}
void registerBeanDefinitions(AnnotationAttributes annoAttrs, BeanDefinitionRegistry registry, String beanName) {
BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(MapperScannerConfigurer.class);
Class extends Annotation> annotationClass = annoAttrs.getClass("annotationClass");
if (!Annotation.class.equals(annotationClass)) {
builder.addPropertyValue("annotationClass", annotationClass);
}
Class> markerInterface = annoAttrs.getClass("markerInterface");
if (!Class.class.equals(markerInterface)) {
builder.addPropertyValue("markerInterface", markerInterface);
}
....
builder.addPropertyValue("basePackage", StringUtils.collectionToCommaDelimitedString(basePackages));
registry.registerBeanDefinition(beanName, builder.getBeanDefinition());
}
}
整个类的逻辑总结出来就是:根据@MapperScan的属性生成一个MapperScannerConfigurer
类型的BeanDefinition注册到spring中,此时只是生成一个BeanDefinition还没被spring实例化。再来看下MapperScannerConfigurer
的核心逻辑:
public class MapperScannerConfigurer
implements BeanDefinitionRegistryPostProcessor, InitializingBean, ApplicationContextAware, BeanNameAware {
@Override
public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) {
if (this.processPropertyPlaceHolders) {
processPropertyPlaceHolders();
}
ClassPathMapperScanner scanner = new ClassPathMapperScanner(registry);
scanner.setAddToConfig(this.addToConfig);
scanner.setAnnotationClass(this.annotationClass);
scanner.setSqlSessionFactory(this.sqlSessionFactory);
scanner.setSqlSessionTemplate(this.sqlSessionTemplate);
...
scanner.registerFilters();
scanner.scan(
StringUtils.tokenizeToStringArray(this.basePackage, ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS));
}
}
MapperScannerConfigurer
实现了spring的一个生命周期接口BeanDefinitionRegistryPostProcessor
,这个接口很重要是spring的一等公民,它是BeanPostProcessor
的子接口,只要是集成spring基本都绕不过它。mybatis就是在这里扫描Mapper
接口然后注入到spring中。在这里定义了一个 ClassPathMapperScanner
,是ClassPathBeanDefinitionScanner
的一个子类。我们先来展开了解ClassPathBeanDefinitionScanner
,从这个类的名字就能看出来这个是用来扫描类路径下的bean的,它根据过滤规则扫描类路径下所有的jar包来找出候选者,我们常用 @Component, @Repository, @Service, @Controller
注解的类spring内部也是通过这个类扫描出来的。我们再回到ClassPathMapperScanner
来看下它是怎么实现的。
public class ClassPathMapperScanner extends ClassPathBeanDefinitionScanner {
@Override
public Set doScan(String... basePackages) {
Set beanDefinitions = super.doScan(basePackages);
processBeanDefinitions(beanDefinitions);
return beanDefinitions;
}
private void processBeanDefinitions(Set beanDefinitions) {
GenericBeanDefinition definition;
for (BeanDefinitionHolder holder : beanDefinitions) {
...
//mapperFactoryBeanClass为MapperFactoryBean
definition.setBeanClass(this.mapperFactoryBeanClass);
...
}
}
public void registerFilters() {
boolean acceptAllInterfaces = true;
if (this.annotationClass != null) {
//扫描带有@Mapper的类
addIncludeFilter(new AnnotationTypeFilter(this.annotationClass));
acceptAllInterfaces = false;
}
...
}
}
这个类在MapperScannerConfigurer
实例化后调用了registerFilters()
,就是在这里指定了扫描的过滤规则,过滤出了只带有@Mapper
的类,然后用父类扫描出来BeanDefinition,因为我们扫描出来的类都是接口,如果直接把BeanDefinition的类型设置为接口类型spring在后面是实例化不了的,所以这里设置的类型是一个工厂类也就是MapperFactoryBean
,spring实例化时如果识别到这个类是FactoryBean
时会调用工厂方法去生成代理类来实例化。到这里所有的Mapper接口都已经注册到spring中了。
dubbo篇
dubbo2.7.0
参考mybatis的集成思路,先来看下@EnableDubbo
的定义
@EnableDubboConfig
@DubboComponentScan
public @interface EnableDubbo {
@AliasFor(annotation = DubboComponentScan.class, attribute = "basePackages")
String[] scanBasePackages() default {};
}
这回并没有看到熟悉的@import
, 接着继续查看EnableDubbo
上里的两个注解的定义
@Import(DubboConfigConfigurationRegistrar.class)
public @interface EnableDubboConfig {
boolean multiple() default false;
}
@Import(DubboComponentScanRegistrar.class)
public @interface DubboComponentScan {
String[] basePackages() default {};
}
万变不离其宗,果然有DubboConfigConfigurationRegistrar
和DubboComponentScanRegistrar
。先来看DubboComponentScanRegistrar
的实现:
public class DubboComponentScanRegistrar implements ImportBeanDefinitionRegistrar {
@Override
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
Set packagesToScan = getPackagesToScan(importingClassMetadata);
registerServiceAnnotationBeanPostProcessor(packagesToScan, registry);
registerReferenceAnnotationBeanPostProcessor(registry);
}
}
DubboComponentScanRegistrar
的作用主要是注册两个注解处理器ServiceAnnotationBeanPostProcessor
和ReferenceAnnotationBeanPostProcessor
。看到这两个类的名字估计能猜出来这两个类的主要作用了,没错就是用来扫描带有dubbo包里@Reference
和@Service
注解的。先来看ServiceAnnotationBeanPostProcessor
的扫描组件的代码:
DubboClassPathBeanDefinitionScanner scanner =
new DubboClassPathBeanDefinitionScanner(registry, environment, resourceLoader);
BeanNameGenerator beanNameGenerator = resolveBeanNameGenerator(registry);
scanner.setBeanNameGenerator(beanNameGenerator);
//这里的Service是dubbo里的注解,
scanner.addIncludeFilter(new AnnotationTypeFilter(Service.class));
for (String packageToScan : packagesToScan) {
scanner.scan(packageToScan);
Set beanDefinitionHolders =
findServiceBeanDefinitionHolders(scanner, packageToScan, registry, beanNameGenerator);
for (BeanDefinitionHolder beanDefinitionHolder : beanDefinitionHolders) {
registerServiceBean(beanDefinitionHolder, registry, scanner);
}
}
这里注意的是,调用scan()
方法时扫描出来的BeanDefinition
会自动注册到spring中,这也就是为什么用spring的@Autowired
也能装配dubbo的@Service
注解的bean的原因。虽然已经注册到了spring中,但是这些bean还要注册到注册中心暴露出来给消费者调用的,dubbo没有在原有的BeanDefinition进行标记这是个DubboService,而是根据原有的BeanDefinition再次注册类型为ServiceBean
的BeanDefinition
。
再看下ServiceBeanDefinition
的代码逻辑
BeanDefinitionBuilder builder = rootBeanDefinition(ServiceBean.class);
AbstractBeanDefinition beanDefinition = builder.getBeanDefinition();
...
List registryRuntimeBeanReferences = toRuntimeBeanReferences(registryConfigBeanNames);
//配置为@service注解指定的注册中心
if (!registryRuntimeBeanReferences.isEmpty()) {
builder.addPropertyValue("registries", registryRuntimeBeanReferences);
}
String[] protocolConfigBeanNames = service.protocol();
List protocolRuntimeBeanReferences = toRuntimeBeanReferences(protocolConfigBeanNames);
//配置为@service注解指定的dubbo协议
if (!protocolRuntimeBeanReferences.isEmpty()) {
builder.addPropertyValue("protocols", protocolRuntimeBeanReferences);
}
到这里所有的DubboService已经定义好了,那这些service是什么时候注册到注册中心的呢。接着来看ServiceBean
的定义:
public class ServiceBean extends ServiceConfig implements InitializingBean, DisposableBean,
ApplicationContextAware, ApplicationListener, BeanNameAware,
ApplicationEventPublisherAware {
public void afterPropertiesSet() throws Exception {
...
//前面一大堆set注册中心配置,协议配置,监控配置和元数据配置实例的逻辑
export();
}
}
可以看到ServiceBean
实现了多个spring的生命周期接口,其中InitializingBean接口会在bean初始换完成调用,dubbo就是在这个阶段调用export()
方法暴露到注册中心,export()
方法就不展开说了,感兴趣的同学可以自行去看源码。
到这里DubboService已经注册完成了,接下来我们看看消费端是怎样使用的。在spring中要使用一个bean常常会用到一个注解那就是@Autowired
,那为什么dubbo为什么不用spring的这个注解非要搞一个@Reference出来呢,那是因为dubbo识别不出来用@Autowired
注解的字段是bean引用还是一个dubbo引用,那dubbo的引用注入逻辑又是怎样的呢。我们先来了解spring的一个接口:InstantiationAwareBeanPostProcessor
,翻译过来就是实例化感知后置处理器。看一下它的定义:
public interface InstantiationAwareBeanPostProcessor extends BeanPostProcessor {
//bean在初始化前调用该方法
@Nullable
default Object postProcessBeforeInstantiation(Class> beanClass, String beanName) throws BeansException {
return null;
}
//bean在初始化之后调用该方法
default boolean postProcessAfterInstantiation(Object bean, String beanName) throws BeansException {
return true;
}
//bean在实例化的时候调用该方法,pvs就是属性的元信息。
@Nullable
default PropertyValues postProcessProperties(PropertyValues pvs, Object bean, String beanName)
throws BeansException {
return null;
}
}
基于这个接口dubbo封装了一个 AnnotationInjectedBeanPostProcessor
,之前我们说的ReferenceAnnotationBeanPostProcessor
就是它的一个子类。先来看下dubbo是怎样封装的:
public abstract class AnnotationInjectedBeanPostProcessor extends
InstantiationAwareBeanPostProcessorAdapter implements MergedBeanDefinitionPostProcessor, PriorityOrdered,
BeanFactoryAware, BeanClassLoaderAware, EnvironmentAware, DisposableBean{
@Override
public PropertyValues postProcessPropertyValues(
PropertyValues pvs, PropertyDescriptor[] pds, Object bean, String beanName) throws BeanCreationException {
//找出带有@Reference注解的字段或者方法。
InjectionMetadata metadata = findInjectionMetadata(beanName, bean.getClass(), pvs);
try {
//注入带有@Reference的字段或方法,
//metadata里有AnnotatedFieldElement和AnnotatedMethodElement
metadata.inject(bean, beanName, pvs);
} catch (BeanCreationException ex) {
throw ex;
} catch (Throwable ex) {
throw new BeanCreationException(beanName, "Injection of @" + getAnnotationType().getName()
+ " dependencies is failed", ex);
}
return pvs;
}
protected Object getInjectedObject(A annotation, Object bean, String beanName, Class> injectedType,
InjectionMetadata.InjectedElement injectedElement) throws Exception {
String cacheKey = buildInjectedObjectCacheKey(annotation, bean, beanName, injectedType, injectedElement);
Object injectedObject = injectedObjectsCache.get(cacheKey);
if (injectedObject == null) {
//调用子类来获取注入的实例
injectedObject = doGetInjectedBean(annotation, bean, beanName, injectedType, injectedElement);
// Customized inject-object if necessary
injectedObjectsCache.putIfAbsent(cacheKey, injectedObject);
}
return injectedObject;
}
// 这个类是dubbo封装的内部类,抽象出了当前带@Reference字段的所有信息
public class AnnotatedFieldElement extends InjectionMetadata.InjectedElement {
private final Field field;
private final A annotation;
private volatile Object bean;
protected AnnotatedFieldElement(Field field, A annotation) {
super(field, null);
this.field = field;
this.annotation = annotation;
}
@Override
protected void inject(Object bean, String beanName, PropertyValues pvs) throws Throwable {
Class> injectedType = field.getType();
//获取被注入的对象
Object injectedObject = getInjectedObject(annotation, bean, beanName, injectedType, this);
ReflectionUtils.makeAccessible(field);
field.set(bean, injectedObject);
}
}
}
总结下这段代码的流程:先获取需要注入的元数据InjectionMetadata
对象,里面包含了被@Reference
注解的字段和方法,也就是InjectedElement
,然后对每个InjectedElement
调用inject()
方法,要注入字段前提要先拿到注入的值,也就是调用了getInjectedObject()
方法获取了被注入的值,这个方法实际调用了抽象方法doGetInjectedBean()
,看下具体实现:
protected Object doGetInjectedBean(Reference reference, Object bean, String beanName, Class> injectedType,
InjectionMetadata.InjectedElement injectedElement) throws Exception {
String referencedBeanName = buildReferencedBeanName(reference, injectedType);
ReferenceBean referenceBean = buildReferenceBeanIfAbsent(referencedBeanName, reference, injectedType, getClassLoader());
cacheInjectedReferenceBean(referenceBean, injectedElement);
Object proxy = buildProxy(referencedBeanName, referenceBean, injectedType);
return proxy;
}
代码的核心逻辑就是构造一个代理对象返回给spring注入到带@Reference注解的字段中。
总结下:spring会在bean实例化时会调用生命周期接口InstantiationAwareBeanPostProcessor
来解决依赖,dubbo实时生成代理对象注入到字段当中。
来看下InstantiationAwareBeanPostProcessor
这个接口有多重要:
实现类 | 作用 |
---|---|
AutowiredAnnotationBeanPostProcessor | 注入@Autowired @Value所在的字段 |
ReferenceAnnotationBeanPostProcessor | 注入@Reference所在的字段 |
NacosValueAnnotationBeanPostProcessor | 注入@NacosValue所在的字段 |
SpyPostProcessor | 注入@SpyBean所在的字段 |
XxlRpcSpringInvokerFactory | 注入@XxlRpcReference所在的字段 |
RequiredAnnotationBeanPostProcessor | 注入@Required所在的字段 |
spring boot篇
springboot2.4.0
不知道大家有没有疑问,为什么用了@SpringBootApplication
这个注解后,类路径下spring boot标准的starter配置的bean都会被注入到spring容器中?我们先来看下spring boot官方是怎样加载一个starter的:
Spring Boot checks for the presence of a META-INF/spring.factories file within your published jar. The file should list your configuration classes under the EnableAutoConfiguration key, as shown in the following example:
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.mycorp.libx.autoconfigure.LibXAutoConfiguration,\
com.mycorp.libx.autoconfigure.LibXWebAutoConfiguration
翻译过来就是:spring boot会加载所有jar包META-INF文件夹下的spring.factories文件,并且所有的自动配置类要定义在org.springframework.boot.autoconfigure.EnableAutoConfiguration key下面。
了解了spring boot的spi机制后我们先来看下@SpringBootApplication的定义:
@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan(excludeFilters = { @Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),
@Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) })
public @interface SpringBootApplication {
}
根据以往的经验来看,带有Enable前缀的往往都是一个比较重要的注解,我们先来看@EnableAutoConfiguration
注解的定义:
@AutoConfigurationPackage
@Import(AutoConfigurationImportSelector.class)
public @interface EnableAutoConfiguration {
}
在mybatis篇中我们知道@Import
是可以用来导入实现了ImportSelector
,ImportBeanDefinitionRegistrar
接口的类。ImportSelector
也是spring很重要的一个接口,先来看下它的定义:
public interface ImportSelector {
//根据注解的元数据获取需要导入的类,返回的是包含全类名的数组
String[] selectImports(AnnotationMetadata importingClassMetadata);
}
但是查看的AutoConfigurationImportSelector
源码发现它实现的是DeferredImportSelector
,
public class AutoConfigurationImportSelector implements DeferredImportSelector, BeanClassLoaderAware,
ResourceLoaderAware, BeanFactoryAware, EnvironmentAware, Ordered {
}
DeferredImportSelector
和ImportSelector
区别很大,从源码可以得知这个类是spring4.0版本加入的,而spring-boot第一个版本0.5.0.M1使用的spring版本正是4.0,可以看出来这个接口是专门为spring-boot打造的。
先来看的DeferredImportSelector
定义
public interface DeferredImportSelector extends ImportSelector {
/**
* @since 5.0
*/
@Nullable
default Class extends Group> getImportGroup() {
return null;
}
/**
* @since 5.0
*/
interface Group {
/**
* Process the {@link AnnotationMetadata} of the importing @{@link Configuration}
* class using the specified {@link DeferredImportSelector}.
*/
void process(AnnotationMetadata metadata, DeferredImportSelector selector);
/**
* An entry that holds the {@link AnnotationMetadata} of the importing
* {@link Configuration} class and the class name to import.
*/
Iterable selectImports();
}
}
根据注释可以看到实际处理配置类的方法是DeferredImportSelector
内部类里的process()
方法,先来看下在AutoConfigurationImportSelector
的实现:
public void process(AnnotationMetadata annotationMetadata, DeferredImportSelector deferredImportSelector) {
...
//调用下面的 getAutoConfigurationEntry
AutoConfigurationEntry autoConfigurationEntry = ((AutoConfigurationImportSelector) deferredImportSelector)
.getAutoConfigurationEntry(annotationMetadata);
this.autoConfigurationEntries.add(autoConfigurationEntry);
for (String importClassName : autoConfigurationEntry.getConfigurations()) {
this.entries.putIfAbsent(importClassName, annotationMetadata);
}
}
protected AutoConfigurationEntry getAutoConfigurationEntry(AnnotationMetadata annotationMetadata) {
...
AnnotationAttributes attributes = getAttributes(annotationMetadata);
//调用下面的getCandidateConfigurations
List configurations = getCandidateConfigurations(annotationMetadata, attributes);
...
return new AutoConfigurationEntry(configurations, exclusions);
}
//从spring.factories文件获取配置类
protected List getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {
List configurations = SpringFactoriesLoader.loadFactoryNames(getSpringFactoriesLoaderFactoryClass(),
getBeanClassLoader());
return configurations;
}
通过调用栈可以看到最终是通过SpringFactoriesLoader.loadFactoryNames()
来加载配置类的,加载的文件就是开头说的spring.factories文件。
虽然所有的配置类在这里已经被加载到,但是配置类往往是存在加载的先后顺序的。我们经常会在spring boot starter中看到@AutoConfigureAfter
、@AutoConfigureBeforer
这两个注解,很明显这些注解是用来给我们控制加载顺序用的,我们接下来看看这些注解是在哪里起作用的。
我们刚才看的DeferredImportSelector
内部类里的process()
方法是用来加载配置类,剩下还有一个方法selectImports()
,没错这个就是获取最终的配置类的,顺序也是在这里控制的,先来看它的实现:
public Iterable selectImports() {
Set allExclusions = this.autoConfigurationEntries.stream()
.map(AutoConfigurationEntry::getExclusions).flatMap(Collection::stream).collect(Collectors.toSet());
Set processedConfigurations = this.autoConfigurationEntries.stream()
.map(AutoConfigurationEntry::getConfigurations).flatMap(Collection::stream)
.collect(Collectors.toCollection(LinkedHashSet::new));
//过滤掉排除的类
processedConfigurations.removeAll(allExclusions);
//排序
return sortAutoConfigurations(processedConfigurations, getAutoConfigurationMetadata()).stream()
.map((importClassName) -> new Entry(this.entries.get(importClassName), importClassName))
.collect(Collectors.toList());
}
private List sortAutoConfigurations(Set configurations,
AutoConfigurationMetadata autoConfigurationMetadata) {
return new AutoConfigurationSorter(getMetadataReaderFactory(), autoConfigurationMetadata)
.getInPriorityOrder(configurations);
}
排序规则都在AutoConfigurationSorter这个类的getInPriorityOrder()里了,直接上代码:
List getInPriorityOrder(Collection classNames) {
AutoConfigurationClasses classes = new AutoConfigurationClasses(this.metadataReaderFactory,
this.autoConfigurationMetadata, classNames);
List orderedClassNames = new ArrayList<>(classNames);
// 先按照字母排序
Collections.sort(orderedClassNames);
// 按照实现了Order接口的order值排序
orderedClassNames.sort((o1, o2) -> {
int i1 = classes.get(o1).getOrder();
int i2 = classes.get(o2).getOrder();
return Integer.compare(i1, i2);
});
// 最后根据@AutoConfigureBefore和@AutoConfigureAfter排序
orderedClassNames = sortByAnnotation(classes, orderedClassNames);
return orderedClassNames;
}
具体的排序算法有兴趣的同学可以看AutoConfigurationSorter
这个类的源码,此处不再展开叙述了。
总的来说,spring boot 只是提供了一种自动配置的能力,让我们免去配置bean的各种繁琐达到开箱即用的效果。但是随之而来的问题是单元测试变得更加麻烦,虽然spring boot提供了个@SpringBootTest
用来启动测试容器,但是由于'自动配置'会导致容器里的bean越来越多,特别是遇到和网络挂钩的dubbo,测试案例执行时间一言难尽。面对这种情况,spring boot 官方也提供了更多的注解来避免加载所有的bean来进行测试,如下表:
点击查看完整列表
@JooqTest |
---|
@DataJdbcTest |
@DataJpaTest |
@DataMongoTest |
@WebMvcTest |
... |
遗憾的是这些注解灵活性和适应性太差了,碰到“本土化项目”启动sping容器失败的概率很大。了解了spring boot自动配置的原理后我们就能解决这个问题了。我们先来看一个常用的spring boot测试案例:
@SpringBootTest(classes = Application.class)
@RunWith(SpringRunner.class)
public class SimpleServiceTest {
@Autowired
SimpleServie simpleService;
@Test
public void excuteTest() {
Object result= simpleService.excute();
Assert.assertEquals(result, "success");
}
}
由于启动@SpringBootTest指定的类是我们spring boot容器的启动类(带有@SpringBootApplication
注解的那个类),所以就和我们启动项目的流程差不多,所有的bean还是会加载进来。我们可以考虑改成这样:
@SpringBootTest(classes = SimpleServiceTest.class)
@RunWith(SpringRunner.class)
@Import(value = {NacosConfigAutoConfiguration.class,
DruidDataSourceAutoConfigure.class,
MybatisPlusAutoConfiguration.class})
@MapperScan("xxx.mapper")
@ComponentScan("xxx.xxx")
public class SimpleServiceTest {
@Autowired
SimpleServie simpleService;
@Test
public void excuteTest() {
Object result= simpleService.excute();
Assert.assertEquals(result, "success");
}
}
指定启动类为我们当前的测试类就不会加载所有的bean了,然后再通过@import
按需加载我们的基础设施配置,例如NacosConfigAutoConfiguration
、DruidDataSourceAutoConfigure
之类的,最后还要指定我们需要测试的bean的包为组件扫描路径。
总结
通过mybatis、dubbo和spring boot的使用注解,我们可以看出它们都是通过spring提供的@Import
注解和sping进行交互的。期间我们也提到了几个spring的核心接口,这些接口具体是怎么工作的,后面的spring源码系列再和大家探讨。