Spring依赖注入源码解析(上)
创始人
2024-02-14 10:04:19
0

文章目录

  • 前言
  • 一、Spring中到底有几种依赖注入的方式?
    • 1、手动注入
      • 1.1、set方法进行注入
      • 1.2、通过构造方法进行注入
    • 2、自动注入
      • 2.1、XML的autowire自动注入
      • 2.2、@Autowired注解的自动注入
  • 二、autowireByName && autowireByType核心源码分析
    • 2.1、autowireByName
    • 2.2、获取可以依赖注入的beanName----unsatisfiedNonSimpleProperties()
  • 三、@Autowired核心源码--AutowiredAnnotationBeanPostProcessor
    • 3.1、postProcessProperties--处理自动装配的属性
    • 3.2、findAutowiringMetadata--找到自动装配元数据(寻找注入点)
    • 3.3、buildAutowiringMetadata--构建注入点(核心方法)
    • 3.4、寻找注入点流程总结
    • 3.5、思考?static的字段或方法为什么不支持
    • 3.6、上述源码提到的桥接方法是什么?
  • 四、inject-注入(需要重点关注)
    • 4.1、AutowiredFieldElement#inject--字段的注入
    • 4.2、AutowiredMethodElement#inject--方法的注入


前言

在看@Autowired注解源码之前,需要先了解一下现有的注入方式,才能更好的理解spring底层实现

本篇文章主要以**@Autowired寻找注入点、注入点进行注入**重点解析


一、Spring中到底有几种依赖注入的方式?

分两种:

手动注入
自动注入

1、手动注入

在XML中定义Bean时,就是手动注入,因为是程序员手动给某个属性指定了值。

1.1、set方法进行注入



1.2、通过构造方法进行注入



所以手动注入的底层也就是分为两种:

set方法注入
构造方法注入

2、自动注入

自动注入又分为两种

XML的autowire自动注入
@Autowired注解的自动注入

2.1、XML的autowire自动注入

在XML中,我们可以在定义一个Bean时去指定这个Bean的自动注入模式:

byType
byName
constructor
default
no

比如:

这么写,表示Spring会自动的给userService中所有的属性自动赋值(不需要这个属性上有@Autowired注解,但需要这个属性有对应的set方法)。


2.2、@Autowired注解的自动注入

@Autowired注解,是byType和byName的结合。

@Autowired注解可以写在

属性上:先根据属性类型去找Bean,如果找到多个再根据属性名确定一个
构造方法上:先根据方法参数类型去找Bean,如果找到多个再根据参数名确定一个
set方法上:先根据方法参数类型去找Bean,如果找到多个再根据参数名确定一个

而这种底层用到了:

属性注入
set方法注入
构造方法注入

二、autowireByName && autowireByType核心源码分析

autowireByName 和autowireByType底层逻辑基本差不多,区别就是一个根据类型,一个根据名称

if (resolvedAutowireMode == AUTOWIRE_BY_NAME || resolvedAutowireMode == AUTOWIRE_BY_TYPE) {MutablePropertyValues newPvs = new MutablePropertyValues(pvs);// Add property values based on autowire by name if applicable.if (resolvedAutowireMode == AUTOWIRE_BY_NAME) {autowireByName(beanName, mbd, bw, newPvs);}// Add property values based on autowire by type if applicable.if (resolvedAutowireMode == AUTOWIRE_BY_TYPE) {autowireByType(beanName, mbd, bw, newPvs);}pvs = newPvs;}

2.1、autowireByName

干了啥?
遍历beanName属性名,根据属性名getBean,添加到pvs集合中

protected void autowireByName(String beanName, AbstractBeanDefinition mbd, BeanWrapper bw, MutablePropertyValues pvs) {// 当前Bean中能进行自动注入的属性名String[] propertyNames = unsatisfiedNonSimpleProperties(mbd, bw);// 遍历每个属性名,获取getBean并且添加到pvs中for (String propertyName : propertyNames) {if (containsBean(propertyName)) {Object bean = getBean(propertyName);pvs.add(propertyName, bean);// 记录一下当前遍历的Bean被哪个bean依赖了(可以理解为propertyName=orderService,beanName=userService)registerDependentBean(propertyName, beanName);if (logger.isTraceEnabled()) {logger.trace("Added autowiring by name from bean name '" + beanName +"' via property '" + propertyName + "' to bean named '" + propertyName + "'");}}else {if (logger.isTraceEnabled()) {logger.trace("Not autowiring property '" + propertyName + "' of bean '" + beanName +"' by name: no matching bean found");}}}}

2.2、获取可以依赖注入的beanName----unsatisfiedNonSimpleProperties()

主要做了几件事情
1、底层获取PropertyDescriptor对象数组
2、遍历,筛选到满足可以自动注入的benaName集合,返回

protected String[] unsatisfiedNonSimpleProperties(AbstractBeanDefinition mbd, BeanWrapper bw) {Set result = new TreeSet<>();PropertyValues pvs = mbd.getPropertyValues();// 1、拿到当前类的所有set方法,setxxx,拿到的name就是xxx。PropertyDescriptor[] pds = bw.getPropertyDescriptors();// 2、遍历for (PropertyDescriptor pd : pds) {// 什么样的属性能进行自动注入?// 1.getWriteMethod---该类有对应的set方法// 2.isExcludedFromDependencyCheck --- 忽略自动装配的给定依赖类型:例如,String。默认为none。// 3.pvs.contains(pd.getName())---程序员自己没有给当前属性设置值// 4.!BeanUtils.isSimpleProperty(pd.getPropertyType())---属性类型不是简单类型,比如int、Integer、int[]、Date、Number、URL....if (pd.getWriteMethod() != null && !isExcludedFromDependencyCheck(pd) && !pvs.contains(pd.getName()) &&!BeanUtils.isSimpleProperty(pd.getPropertyType())) {// 把满足注入条件的加入到集合result.add(pd.getName());}}return StringUtils.toStringArray(result);}


三、@Autowired核心源码–AutowiredAnnotationBeanPostProcessor

for (BeanPostProcessor bp : getBeanPostProcessors()) {if (bp instanceof InstantiationAwareBeanPostProcessor) {InstantiationAwareBeanPostProcessor ibp = (InstantiationAwareBeanPostProcessor) bp;PropertyValues pvsToUse = ibp.postProcessProperties(pvs, bw.getWrappedInstance(), beanName);if (pvsToUse == null) {if (filteredPds == null) {filteredPds = filterPropertyDescriptorsForDependencyCheck(bw, mbd.allowCaching);}pvsToUse = ibp.postProcessPropertyValues(pvs, filteredPds, bw.getWrappedInstance(), beanName);if (pvsToUse == null) {return;}}pvs = pvsToUse;}}

AutowiredAnnotationBeanPostProcessor实现了上面的InstantiationAwareBeanPostProcessor接口

在属性填充代码逻辑中,上面这一段代码,会去调用AutowiredAnnotationBeanPostProcessor#postProcessProperties()做处理

这段代码主要是对bean里面包含了@Autowired注解的属性做处理


3.1、postProcessProperties–处理自动装配的属性

@Overridepublic PropertyValues postProcessProperties(PropertyValues pvs, Object bean, String beanName) {// 找到自动装配元数据 InjectionMetadata metadata = findAutowiringMetadata(beanName, bean.getClass(), pvs);try {// 注入metadata.inject(bean, beanName, pvs);}catch (BeanCreationException ex) {throw ex;}catch (Throwable ex) {throw new BeanCreationException(beanName, "Injection of autowired dependencies failed", ex);}return pvs;}

3.2、findAutowiringMetadata–找到自动装配元数据(寻找注入点)

这个方法会有多个地方调用

第一次调用的地方其实是在Bean后置处理器里面调用applyMergedBeanDefinitionPostProcessors,然后才是在属性填充时调用

这个方法主要是把类名称添加到缓存里

private InjectionMetadata findAutowiringMetadata(String beanName, Class clazz, @Nullable PropertyValues pvs) {// 类名作为缓存keyString cacheKey = (StringUtils.hasLength(beanName) ? beanName : clazz.getName());// 查看缓存里有没有类名key,第一次进来肯定是没有的InjectionMetadata metadata = this.injectionMetadataCache.get(cacheKey);// 为空则进if,双重锁检测if (InjectionMetadata.needsRefresh(metadata, clazz)) {synchronized (this.injectionMetadataCache) {metadata = this.injectionMetadataCache.get(cacheKey);if (InjectionMetadata.needsRefresh(metadata, clazz)) {if (metadata != null) {//清除缓存metadata.clear(pvs);}// 构建自动装配元数据,然后添加到缓存metadata = buildAutowiringMetadata(clazz);this.injectionMetadataCache.put(cacheKey, metadata);}}}return metadata;}

3.3、buildAutowiringMetadata–构建注入点(核心方法)

主要做了几件事情
1、看你这个类型是不是一个基础类型
2.、遍历当前类下的属性,寻找注入点,看是否有有@Autowired & @Value注解的属性
3、然后确定required状态,然后添加到需要注入的集合里
4、遍历当前类下的方法,寻找注入点,看是否有有@Autowired & @Value注解的set方法
5、同2一致
6、while循环,查找父类有没有注入点,直到找到所有的注入点结束

private InjectionMetadata buildAutowiringMetadata(final Class clazz) {// 检查有没有前缀是不是以"java.开头,如String,Integer等...基础类型,基础类型不会被注入if (!AnnotationUtils.isCandidateClass(clazz, this.autowiredAnnotationTypes)) {return InjectionMetadata.EMPTY;}List elements = new ArrayList<>();Class targetClass = clazz;do {final List currElements = new ArrayList<>();// 处理当前类下的属性ReflectionUtils.doWithLocalFields(targetClass, field -> {// 有@Autowired & @Value注解的属性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;}// 判断注解是否包含required,一般情况这里都返回trueboolean required = determineRequiredStatus(ann);// 添加到集合(里面包含需要依赖注入的属性)currElements.add(new AutowiredFieldElement(field, required));}});// 处理当前类下的method方法ReflectionUtils.doWithLocalMethods(targetClass, method -> {// 查找桥接方法,一般没有自己返回Method bridgedMethod = BridgeMethodResolver.findBridgedMethod(method);if (!BridgeMethodResolver.isVisibilityBridgeMethodPair(method, bridgedMethod)) {return;}//  查找是否有@Autowired & @Value注解的方法,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);}}//  判断注解是否包含required,一般情况这里都返回trueboolean required = determineRequiredStatus(ann);// PropertyDescriptor pd = BeanUtils.findPropertyForMethod(bridgedMethod, clazz);// 添加到需要依赖注入的集合里currElements.add(new AutowiredMethodElement(method, required, pd));}});// 添加所有属性和method的需要依赖的beanelements.addAll(0, currElements);// 获取父类targetClass = targetClass.getSuperclass();}// 循环直到没有父类为止,全部添加到一个集合里while (targetClass != null && targetClass != Object.class);return InjectionMetadata.forElements(elements, clazz);}

3.4、寻找注入点流程总结

在创建一个Bean的过程中,Spring会利用AutowiredAnnotationBeanPostProcessor的postProcessMergedBeanDefinition()找出注入点并缓存,找注入点的流程为

1、遍历当前类的所有的属性字段Field

2、查看字段上是否存在@Autowired、@Value、@Inject中的其中任意一个,存在则认为该字段是一个注入点

3、如果字段是static的,则不进行注入

4、获取@Autowired中的required属性的值

5、将字段信息构造成一个AutowiredFieldElement对象,作为一个注入点对象添加到currElements集合中。

6、遍历当前类的所有方法Method

7、判断当前Method是否是桥接方法,如果是找到原方法

8、查看方法上是否存在@Autowired、@Value、@Inject中的其中任意一个,存在则认为该方法是一个注入点

9、如果方法是static的,则不进行注入

10、获取@Autowired中的required属性的值

11、将方法信息构造成一个AutowiredMethodElement对象,作为一个注入点对象添加到currElements集合中。

12、遍历完当前类的字段和方法后,将遍历父类的,直到没有父类。

13、最后将currElements集合封装成一个InjectionMetadata对象,作为当前Bean对于的注入点集合对象,并缓存。


3.5、思考?static的字段或方法为什么不支持

@Component
@Scope("prototype")
public class OrderService {}
@Component
@Scope("prototype")
public class UserService  {@Autowiredprivate static OrderService orderService;public void test() {System.out.println("test123");}}

看上面代码,UserService和OrderService都是原型Bean,假设Spring支持static字段进行自动注入,那么现在调用两次

UserService userService1 = (UserService) context.getBean("userService");
UserService userService2 = (UserService) context.getBean("userService");

问此时,userService1的orderService值是什么?还是它自己注入的值吗?

答案是:不是,一旦userService2 创建好了之后,static orderService字段的值就发生了修改了,从而出现bug。


3.6、上述源码提到的桥接方法是什么?

public interface UserInterface {void setOrderService(T t);
}
@Component
public class UserService implements UserInterface {private OrderService orderService;@Override@Autowiredpublic void setOrderService(OrderService orderService) {this.orderService = orderService;}public void test() {System.out.println("test123");}}

UserService对应的字节码为:

// class version 52.0 (52)
// access flags 0x21
// signature Ljava/lang/Object;Lcom/ljc/service/UserInterface;
// declaration: com/ljc/service/UserService implements com.ljc.service.UserInterface
public class com/ljc/service/UserService implements com/ljc/service/UserInterface {// compiled from: UserService.java@Lorg/springframework/stereotype/Component;()// access flags 0x2private Lcom/ljc/service/OrderService; orderService// access flags 0x1public ()VL0LINENUMBER 12 L0ALOAD 0INVOKESPECIAL java/lang/Object. ()VRETURNL1LOCALVARIABLE this Lcom/ljc/service/UserService; L0 L1 0MAXSTACK = 1MAXLOCALS = 1// access flags 0x1public setOrderService(Lcom/ljc/service/OrderService;)V@Lorg/springframework/beans/factory/annotation/Autowired;()L0LINENUMBER 19 L0ALOAD 0ALOAD 1PUTFIELD com/ljc/service/UserService.orderService : Lcom/ljc/service/OrderService;L1LINENUMBER 20 L1RETURNL2LOCALVARIABLE this Lcom/ljc/service/UserService; L0 L2 0LOCALVARIABLE orderService Lcom/ljc/service/OrderService; L0 L2 1MAXSTACK = 2MAXLOCALS = 2// access flags 0x1public test()VL0LINENUMBER 23 L0GETSTATIC java/lang/System.out : Ljava/io/PrintStream;LDC "test123"INVOKEVIRTUAL java/io/PrintStream.println (Ljava/lang/String;)VL1LINENUMBER 24 L1RETURNL2LOCALVARIABLE this Lcom/ljc/service/UserService; L0 L2 0MAXSTACK = 2MAXLOCALS = 1// access flags 0x1041public synthetic bridge setOrderService(Ljava/lang/Object;)V@Lorg/springframework/beans/factory/annotation/Autowired;()L0LINENUMBER 11 L0ALOAD 0ALOAD 1CHECKCAST com/ljc/service/OrderServiceINVOKEVIRTUAL com/ljc/service/UserService.setOrderService (Lcom/ljc/service/OrderService;)VRETURNL1LOCALVARIABLE this Lcom/ljc/service/UserService; L0 L1 0MAXSTACK = 2MAXLOCALS = 2
}

可以看到在UserSerivce的字节码中有两个setOrderService方法:

public setOrderService(Lcom/ljc/service/OrderService;)V
public synthetic bridge setOrderService(Ljava/lang/Object;)V

并且都是存在@Autowired注解的。

所以在Spring中需要处理这种情况**,当遍历到桥接方法时,得找到原方法**。


四、inject-注入(需要重点关注)

在上一步构建好注入点后,寻找开始依赖注入!

Spring在AutowiredAnnotationBeanPostProcessor的**postProcessProperties()**方法中,会遍历所找到的注入点依次进行注入。

public void inject(Object target, @Nullable String beanName, @Nullable PropertyValues pvs) throws Throwable {Collection checkedElements = this.checkedElements;Collection elementsToIterate =(checkedElements != null ? checkedElements : this.injectedElements);if (!elementsToIterate.isEmpty()) {// 遍历每个注入点,开始依赖注入for (InjectedElement element : elementsToIterate) {element.inject(target, beanName, pvs);}}}

需要注意上面的

element.inject(target, beanName, pvs);

并不是直接看父类的实现,在@Autowired实现这里 只需要看 field和method的2个实现即可,@Resource的实现是用的父类实现

在这里插入图片描述


4.1、AutowiredFieldElement#inject–字段的注入

主要做了几件事情
1、遍历所有的AutowiredFieldElement对象。

2、将对应的字段封装为DependencyDescriptor对象。

3、调用BeanFactory的resolveDependency()方法,传入DependencyDescriptor对象,进行依赖查找,找到当前字段所匹配的Bean对象。

4、将DependencyDescriptor对象和所找到的结果对象beanName封装成一个**ShortcutDependencyDescriptor对象作为缓存,**比如如果当前Bean是原型Bean,那么下次再来创建该Bean时,就可以直接拿缓存的结果对象beanName去BeanFactory中去那bean对象了,不用再次进行查找了

5、将结果对象赋值给字段。

@Overrideprotected void inject(Object bean, @Nullable String beanName, @Nullable PropertyValues pvs) throws Throwable {Field field = (Field) this.member;Object value;// 默认是false,第一次进来肯定不会进ifif (this.cached) {value = resolvedCachedArgument(beanName, this.cachedFieldValue);}else {// 将对应的字段封装为DependencyDescriptor对象(创建依赖描述符对象)DependencyDescriptor desc = new DependencyDescriptor(field, this.required);// 设置calssdesc.setContainingClass(bean.getClass());Set autowiredBeanNames = new LinkedHashSet<>(1);Assert.state(beanFactory != null, "No BeanFactory available");// 获取类型转换器TypeConverter typeConverter = beanFactory.getTypeConverter();try {// 重点关注的核心方法,进行依赖查找,找到当前字段所匹配的Bean对象value = beanFactory.resolveDependency(desc, beanName, autowiredBeanNames, typeConverter);}catch (BeansException ex) {throw new UnsatisfiedDependencyException(null, beanName, new InjectionPoint(field), ex);}synchronized (this) {if (!this.cached) {Object cachedFieldValue = null;if (value != null || this.required) {cachedFieldValue = desc;registerDependentBeans(beanName, autowiredBeanNames);if (autowiredBeanNames.size() == 1) {String autowiredBeanName = autowiredBeanNames.iterator().next();if (beanFactory.containsBean(autowiredBeanName) &&beanFactory.isTypeMatch(autowiredBeanName, field.getType())) {// 创建缓存对象,方便缓存cachedFieldValue = new ShortcutDependencyDescriptor(desc, autowiredBeanName, field.getType());}}}// 缓存this.cachedFieldValue = cachedFieldValue;this.cached = true;}}}if (value != null) {ReflectionUtils.makeAccessible(field);// 将结果对象赋值给字段field.set(bean, value);}}
}

4.2、AutowiredMethodElement#inject–方法的注入

和字段注入实现逻辑类似…

主要做了几件事情
1、遍历所有的AutowiredMethodElement对象

2、遍历将对应的方法的参数,将每个参数封装成MethodParameter对象

3、将MethodParameter对象封装为DependencyDescriptor对象

4、调用BeanFactory的resolveDependency()方法,传入DependencyDescriptor对象,进行依赖查找,找到当前方法参数所匹配的Bean对象。

5、将DependencyDescriptor对象和所找到的结果对象beanName封装成一个ShortcutDependencyDescriptor对象作为缓存,比如如果当前Bean是原型Bean,那么下次再来创建该Bean时,就可以直接拿缓存的结果对象beanName去BeanFactory中去那bean对象了,不用再次进行查找了

6、利用反射将找到的所有结果对象传给当前方法,并执行。

		@Overrideprotected void inject(Object bean, @Nullable String beanName, @Nullable PropertyValues pvs) throws Throwable {if (checkPropertySkipping(pvs)) {return;}Method method = (Method) this.member;Object[] arguments;if (this.cached) {// Shortcut for avoiding synchronization...arguments = resolveCachedArguments(beanName);}else {int argumentCount = method.getParameterCount();arguments = new Object[argumentCount];DependencyDescriptor[] descriptors = new DependencyDescriptor[argumentCount];Set autowiredBeans = new LinkedHashSet<>(argumentCount);Assert.state(beanFactory != null, "No BeanFactory available");TypeConverter typeConverter = beanFactory.getTypeConverter();for (int i = 0; i < arguments.length; i++) {// 每个参数封装成MethodParameter对象MethodParameter methodParam = new MethodParameter(method, i);// 将MethodParameter对象封装为DependencyDescriptor对象DependencyDescriptor currDesc = new DependencyDescriptor(methodParam, this.required);currDesc.setContainingClass(bean.getClass());descriptors[i] = currDesc;try {// 进行依赖查找,找到当前方法参数所匹配的Bean对象Object arg = beanFactory.resolveDependency(currDesc, beanName, autowiredBeans, typeConverter);if (arg == null && !this.required) {arguments = null;break;}arguments[i] = arg;}catch (BeansException ex) {throw new UnsatisfiedDependencyException(null, beanName, new InjectionPoint(methodParam), ex);}}synchronized (this) {if (!this.cached) {if (arguments != null) {DependencyDescriptor[] cachedMethodArguments = Arrays.copyOf(descriptors, arguments.length);registerDependentBeans(beanName, autowiredBeans);if (autowiredBeans.size() == argumentCount) {Iterator it = autowiredBeans.iterator();Class[] paramTypes = method.getParameterTypes();for (int i = 0; i < paramTypes.length; i++) {String autowiredBeanName = it.next();if (beanFactory.containsBean(autowiredBeanName) &&beanFactory.isTypeMatch(autowiredBeanName, paramTypes[i])) {// 封装缓存对象cachedMethodArguments[i] = new ShortcutDependencyDescriptor(descriptors[i], autowiredBeanName, paramTypes[i]);}}}this.cachedMethodArguments = cachedMethodArguments;}else {this.cachedMethodArguments = null;}this.cached = true;}}}if (arguments != null) {try {ReflectionUtils.makeAccessible(method);// 注入method.invoke(bean, arguments);}catch (InvocationTargetException ex) {throw ex.getTargetException();}}}

相关内容

热门资讯

喜欢穿一身黑的男生性格(喜欢穿... 今天百科达人给各位分享喜欢穿一身黑的男生性格的知识,其中也会对喜欢穿一身黑衣服的男人人好相处吗进行解...
发春是什么意思(思春和发春是什... 本篇文章极速百科给大家谈谈发春是什么意思,以及思春和发春是什么意思对应的知识点,希望对各位有所帮助,...
网络用语zl是什么意思(zl是... 今天给各位分享网络用语zl是什么意思的知识,其中也会对zl是啥意思是什么网络用语进行解释,如果能碰巧...
为什么酷狗音乐自己唱的歌不能下... 本篇文章极速百科小编给大家谈谈为什么酷狗音乐自己唱的歌不能下载到本地?,以及为什么酷狗下载的歌曲不是...
家里可以做假山养金鱼吗(假山能... 今天百科达人给各位分享家里可以做假山养金鱼吗的知识,其中也会对假山能放鱼缸里吗进行解释,如果能碰巧解...
华为下载未安装的文件去哪找(华... 今天百科达人给各位分享华为下载未安装的文件去哪找的知识,其中也会对华为下载未安装的文件去哪找到进行解...
四分五裂是什么生肖什么动物(四... 本篇文章极速百科小编给大家谈谈四分五裂是什么生肖什么动物,以及四分五裂打一生肖是什么对应的知识点,希...
怎么往应用助手里添加应用(应用... 今天百科达人给各位分享怎么往应用助手里添加应用的知识,其中也会对应用助手怎么添加微信进行解释,如果能...
客厅放八骏马摆件可以吗(家里摆... 今天给各位分享客厅放八骏马摆件可以吗的知识,其中也会对家里摆八骏马摆件好吗进行解释,如果能碰巧解决你...
苏州离哪个飞机场近(苏州离哪个... 本篇文章极速百科小编给大家谈谈苏州离哪个飞机场近,以及苏州离哪个飞机场近点对应的知识点,希望对各位有...