![75ef9f02299e824a3392a65696972f2d.png](https://i-blog.csdnimg.cn/blog_migrate/79e963b93aa38915ad30b7d76d5cfdf6.jpeg)
1.概述
这篇Spring Framework文章将演示与依赖注入相关的注释的使用,即@ Resource,@ Inject和@Autowired注释。这些注释为类提供了解析依赖关系的声明方式。例如:
@Autowired
ArbitraryClass arbObject;
而不是直接实例化(必要的方式),例如:
ArbitraryClass arbObject = new ArbitraryClass();
三个注释中的两个属于Java扩展包:javax.annotation.Resource和javax.inject.Inject。@Autowired注解属于org.springframework.beans.factory.annotation包。
这些注释中的每一个都可以通过属性注入或通过setter注入来解决依赖性。我们将使用一个简单但实用的示例来演示三个注释之间的区别,这些注释基于每个注释所执行的执行路径。
这些示例将重点介绍如何在集成测试期间使用三个注入注释。测试所需的依赖关系可以是任意文件,也可以是任意类。
2. @Resource 注解
该@Resource注解是的一部分,JSR-250注释收集和包装与Java EE。此批注具有以下执行路径,按优先级列出:
- 按名称匹配
- 按类型匹配
- 按Qualifier匹配
这些执行路径适用于setter和field injection。
2.1 变量注入
通过使用@Resource批注对实例变量进行批注来实现通过字段注入来解决依赖关系。
2.1.1 按名称匹配
用于演示按名称匹配字段注入的集成测试列出如下:
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(
loader=AnnotationConfigContextLoader.class,
classes=ApplicationContextTestResourceNameType.class)
public class FieldResourceInjectionTest {
@Resource(name="namedFile")
private File defaultFile;
@Test
public void givenResourceAnnotation_WhenOnField_ThenDependencyValid(){
assertNotNull(defaultFile);
assertEquals("namedFile.txt", defaultFile.getName());
}
}
我们来看看代码吧。在FieldResourceInjectionTest集成测试中,在第7行,通过将bean名称作为属性值传递给@Resource注释来实现依赖名称的解析:
@Resource(name="namedFile")
private File defaultFile;
此配置将使用按名称匹配的执行路径解决依赖关系。必须在ApplicationContextTestResourceNameType应用程序上下文中定义名为File的bean 。
请注意,bean id和相应的引用属性值必须匹配:
@Configuration
public class ApplicationContextTestResourceNameType {
@Bean(name="namedFile")
public File namedFile() {
File namedFile = new File("namedFile.txt");
return namedFile;
}
}
如果未在应用程序上下文中定义bean,将导致抛出org.springframework.beans.factory.NoSuchBeanDefinitionException。这可以通过在ApplicationContextTestResourceNameType应用程序上下文中更改传递给@Bean批注的属性值来证明; 或者在FieldResourceInjectionTest集成测试中更改传递给@Resource注释的属性值。
2.1.2 按类型匹配
要演示按类型匹配的执行路径,只需删除FieldResourceInjectionTest集成测试第7行的属性值,使其如下所示:
@Resource
private File defaultFile;
并再次运行测试。
测试仍然会通过,因为如果@Resource注释没有收到bean名称作为属性值,则Spring Framework将继续使用下一级优先级(按类型匹配),以尝试解析依赖项。
2.1.3 按Qualifier匹配
为了演示按限定符匹配的执行路径,将修改集成测试场景,以便在ApplicationContextTestResourceQualifier应用程序上下文中定义两个bean :
@Configuration
public class ApplicationContextTestResourceQualifier {
@Bean(name="defaultFile")
public File defaultFile() {
File defaultFile = new File("defaultFile.txt");
return defaultFile;
}
@Bean(name="namedFile")
public File namedFile() {
File namedFile = new File("namedFile.txt");
return namedFile;
}
}
该QualifierResourceInjectionTest集成测试将被用来证明比赛由限定符依赖解析。在这种情况下,需要将特定的bean依赖项注入每个引用变量:
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(
loader=AnnotationConfigContextLoader.class,
classes=ApplicationContextTestResourceQualifier.class)
public class QualifierResourceInjectionTest {
@Resource
private File dependency1;
@Resource
private File dependency2;
@Test
public void givenResourceAnnotation_WhenField_ThenDependency1Valid(){
assertNotNull(dependency1);
assertEquals("defaultFile.txt", dependency1.getName());
}
@Test
public void givenResourceQualifier_WhenField_ThenDependency2Valid(){
assertNotNull(dependency2);
assertEquals("namedFile.txt", dependency2.getName());
}
}
运行集成测试,抛出org.springframework.beans.factory.NoUniqueBeanDefinitionException。抛出此异常是因为应用程序上下文找到了两个类型为File的 bean定义,并且对于哪个bean应该解析依赖关系感到困惑。
要解决此问题,请参阅QualifierResourceInjectionTest集成测试的第7行到第10行:
@Resource
private File dependency1;
@Resource
private File dependency2;
并添加以下代码行:
@Qualifier("defaultFile")
@Qualifier("namedFile")
所以代码块看起来如下:
@Resource
@Qualifier("defaultFile")
private File dependency1;
@Resource
@Qualifier("namedFile")
private File dependency2;
再次运行集成测试,这次它应该通过。此测试的目的是证明即使在应用程序上下文中定义了多个bean,@ Qualifier注释也会通过允许将特定依赖项注入类中来清除任何混淆。
2.2 Setter注入
在字段上注入依赖项时执行的执行路径适用于基于setter的注入。
2.2.1 按名称匹配
唯一的区别是MethodResourceInjectionTest集成测试有一个setter方法:
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(
loader=AnnotationConfigContextLoader.class,
classes=ApplicationContextTestResourceNameType.class)
public class MethodResourceInjectionTest {
private File defaultFile;
@Resource(name="namedFile")
protected void setDefaultFile(File defaultFile) {
this.defaultFile = defaultFile;
}
@Test
public void givenResourceAnnotation_WhenSetter_ThenDependencyValid(){
assertNotNull(defaultFile);
assertEquals("namedFile.txt", defaultFile.getName());
}
}
通过setter注入来解决依赖关系是通过注释引用变量的相应setter方法来完成的。将bean依赖项的名称作为属性值传递给@Resource批注:
private File defaultFile;
@Resource(name="namedFile")
protected void setDefaultFile(File defaultFile) {
this.defaultFile = defaultFile;
}
该namedFile豆的依赖将在这个例子中重复使用。bean名称和相应的属性值必须匹配。
按原样运行集成测试,它将通过。
要查看依赖关系确实通过按名称匹配的执行路径解决,请将传递给@Resource批注的属性值更改为您选择的值,然后再次运行测试。这次,测试将失败并出现NoSuchBeanDefinitionException。
2.2.2 按类型匹配
为了演示基于setter的,按类型匹配的执行,我们将使用MethodByTypeResourceTest集成测试:
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(
loader=AnnotationConfigContextLoader.class,
classes=ApplicationContextTestResourceNameType.class)
public class MethodByTypeResourceTest {
private File defaultFile;
@Resource
protected void setDefaultFile(File defaultFile) {
this.defaultFile = defaultFile;
}
@Test
public void givenResourceAnnotation_WhenSetter_ThenValidDependency(){
assertNotNull(defaultFile);
assertEquals("namedFile.txt", defaultFile.getName());
}
}
按原样运行此测试,它将通过。
为了验证File依赖项确实是由类型匹配的执行路径解决的,请将defaultFile变量的类类型更改为另一个类类型,如String。再次执行MethodByTypeResourceTest集成测试,这次将抛出NoSuchBeanDefinitionException。
该异常验证是否确实使用了match-by-type来解析File依赖项。该NoSuchBeanDefinitionException确认参考变量名称不需要匹配bean的名字。相反,依赖项解析依赖于bean的类类型与引用变量的类类型匹配。
2.2.3 按Qualifier匹配
该MethodByQualifierResourceTest集成测试将被用来演示比赛由限定符执行路径:
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(
loader=AnnotationConfigContextLoader.class,
classes=ApplicationContextTestResourceQualifier.class)
public class MethodByQualifierResourceTest {
private File arbDependency;
private File anotherArbDependency;
@Test
public void givenResourceQualifier_WhenSetter_ThenValidDependencies(){
assertNotNull(arbDependency);
assertEquals("namedFile.txt", arbDependency.getName());
assertNotNull(anotherArbDependency);
assertEquals("defaultFile.txt", anotherArbDependency.getName());
}
@Resource
@Qualifier("namedFile")
public void setArbDependency(File arbDependency) {
this.arbDependency = arbDependency;
}
@Resource
@Qualifier("defaultFile")
public void setAnotherArbDependency(File anotherArbDependency) {
this.anotherArbDependency = anotherArbDependency;
}
}
此测试的目的是演示即使在应用程序上下文中定义了特定类型的多个bean实现,也可以将@Qualifier批注与@Resource批注一起使用来解析依赖关系。
与基于字段的依赖项注入类似,如果在应用程序上下文中定义了多个bean,则如果未使用@Qualifier批注指定应使用哪个bean来解析依赖项,则抛出NoUniqueBeanDefinitionException。
3. @Inject Annotation
该@Inject注解属于JSR-330注释集合。此批注具有以下执行路径,按优先级列出:
- 按类型匹配
- 按Qualifier匹配
- 按名称匹配
这些执行路径适用于setter和field injection。为了访问@Inject批注,必须将javax.inject库声明为Gradle或Maven依赖项。
对于Gradle:
testCompile group: 'javax.inject', name: 'javax.inject', version: '1'
对于Maven:
<dependency>
<groupId>javax.inject</groupId>
<artifactId>javax.inject</artifactId>
<version>1</version>
</dependency>
3.1 变量注入
3.1.1 按类型匹配
集成测试示例将被修改为使用其他类型的依赖项,即ArbitraryDependency类。该ArbitraryDependency类依赖只是作为一个简单的依赖,并持有没有进一步的意义。它列出如下:
@Component
public class ArbitraryDependency {
private final String label = "Arbitrary Dependency";
public String toString() {
return label;
}
}
有问题的FieldInjectTest集成测试列出如下:
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(
loader=AnnotationConfigContextLoader.class,
classes=ApplicationContextTestInjectType.class)
public class FieldInjectTest {
@Inject
private ArbitraryDependency fieldInjectDependency;
@Test
public void givenInjectAnnotation_WhenOnField_ThenValidDependency(){
assertNotNull(fieldInjectDependency);
assertEquals("Arbitrary Dependency",
fieldInjectDependency.toString());
}
}
与@Resource注释不同,后者首先按名称解析依赖关系; @Inject批注的默认行为按类型解析依赖关系。
这意味着即使类引用变量名与bean名不同,只要在应用程序上下文中定义了bean,依赖项仍将被解析。请注意以下测试中的引用变量名称:
@Inject
private ArbitraryDependency fieldInjectDependency;
与应用程序上下文中配置的bean名称不同:
@Bean
public ArbitraryDependency injectDependency() {
ArbitraryDependency injectDependency = new ArbitraryDependency();
return injectDependency;
}
并且当执行测试时,它能够解决依赖性。
3.1.2 按Qualifier匹配
但是如果有特定类类型的多个实现,并且某个类需要特定的bean呢?让我们修改集成测试示例,以便需要另一个依赖项。
在这个例子中,我们将ArbitraryDependency类子类化,在类型匹配示例中使用它来创建AnotherArbitraryDependency类:
public class AnotherArbitraryDependency extends ArbitraryDependency {
private final String label = "Another Arbitrary Dependency";
public String toString() {
return label;
}
}
每个测试用例的目标是确保每个依赖项都正确地注入到每个引用变量中:
@Inject
private ArbitraryDependency defaultDependency;
@Inject
private ArbitraryDependency namedDependency;
用于演示按限定符匹配的FieldQualifierInjectTest集成测试如下所示:
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(
loader=AnnotationConfigContextLoader.class,
classes=ApplicationContextTestInjectQualifier.class)
public class FieldQualifierInjectTest {
@Inject
private ArbitraryDependency defaultDependency;
@Inject
private ArbitraryDependency namedDependency;
@Test
public void givenInjectQualifier_WhenOnField_ThenDefaultFileValid(){
assertNotNull(defaultDependency);
assertEquals("Arbitrary Dependency",
defaultDependency.toString());
}
@Test
public void givenInjectQualifier_WhenOnField_ThenNamedFileValid(){
assertNotNull(defaultDependency);
assertEquals("Another Arbitrary Dependency",
namedDependency.toString());
}
}
如果应用程序上下文中存在特定类的多个实现,并且FieldQualifierInjectTest集成测试尝试以下面列出的方式注入依赖项:
@Inject
private ArbitraryDependency defaultDependency;
@Inject
private ArbitraryDependency namedDependency;
一个NoUniqueBeanDefinitionException将被抛出。
抛出此异常是Spring Framework指出某个类有多个实现的方式,并且对于使用哪个类感到困惑。为了解释混淆,请转到FieldQualifierInjectTest集成测试的第7行和第10行:
@Inject
private ArbitraryDependency defaultDependency;
@Inject
private ArbitraryDependency namedDependency;
将所需的bean名称传递给@Qualifier注释,该注释与@Inject注释一起使用。代码块现在看起来如下:
@Inject
@Qualifier("defaultFile")
private ArbitraryDependency defaultDependency;
@Inject
@Qualifier("namedFile")
private ArbitraryDependency namedDependency;
该@Qualifier接收一个bean的名称时,注释需要一个严格的匹配。确保bean名称正确传递给Qualifier,否则将抛出NoUniqueBeanDefinitionException。再次运行测试,这次应该通过。
3.1.3 按名称匹配
用于按名称演示匹配的FieldByNameInjectTest集成测试类似于按类型执行路径匹配。唯一的区别是现在需要特定的bean,而不是特定的类型。在这个例子中,我们再次继承ArbitraryDependency类以生成YetAnotherArbitraryDependency类:
public class YetAnotherArbitraryDependency extends ArbitraryDependency {
private final String label = "Yet Another Arbitrary Dependency";
public String toString() {
return label;
}
}
为了演示按名称匹配的执行路径,我们将使用以下集成测试:
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(
loader=AnnotationConfigContextLoader.class,
classes=ApplicationContextTestInjectName.class)
public class FieldByNameInjectTest {
@Inject
@Named("yetAnotherFieldInjectDependency")
private ArbitraryDependency yetAnotherFieldInjectDependency;
@Test
public void givenInjectQualifier_WhenSetOnField_ThenDependencyValid(){
assertNotNull(yetAnotherFieldInjectDependency);
assertEquals("Yet Another Arbitrary Dependency",
yetAnotherFieldInjectDependency.toString());
}
}
应用程序上下文列出如下:
@Configuration
public class ApplicationContextTestInjectName {
@Bean
public ArbitraryDependency yetAnotherFieldInjectDependency() {
ArbitraryDependency yetAnotherFieldInjectDependency =
new YetAnotherArbitraryDependency();
return yetAnotherFieldInjectDependency;
}
}
按原样运行集成测试,它将通过。
为了验证依赖项确实是由按名称执行的执行路径注入的,请将传递给@Named注释的值allAnotherFieldInjectDependency更改为您选择的另一个名称。再次运行测试 - 这次抛出NoSuchBeanDefinitionException。
3.2 Setter注入
@Inject注释的基于setter的注入类似于基于@Resource setter的注入的方法。不是注释引用变量,而是注释相应的setter方法。基于字段的依赖注入的执行路径也适用于基于setter的注入。
4. @Autowired Annotation
@Autowired注释的行为类似于@Inject注释。唯一的区别是@Autowired注释是Spring框架的一部分。此批注具有与@Inject批注相同的执行路径,按优先顺序列出:
- 按类型匹配
- 按Qualifier匹配
- 按名称匹配
这些执行路径适用于setter和field injection。
4.1 变量注入
4.1.1 按类型匹配
用于演示@Autowired按类型匹配执行路径的集成测试示例将类似于用于演示@Inject按类型匹配执行路径的测试。所述FieldAutowiredTest用于使用所述证明匹配逐类型的集成测试@Autowired注释如下:
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(
loader=AnnotationConfigContextLoader.class,
classes=ApplicationContextTestAutowiredType.class)
public class FieldAutowiredTest {
@Autowired
private ArbitraryDependency fieldDependency;
@Test
public void givenAutowired_WhenSetOnField_ThenDependencyResolved() {
assertNotNull(fieldDependency);
assertEquals("Arbitrary Dependency", fieldDependency.toString());
}
}
此集成测试的应用程序上下文列出如下:
@Configuration
public class ApplicationContextTestAutowiredType {
@Bean
public ArbitraryDependency autowiredFieldDependency() {
ArbitraryDependency autowiredFieldDependency =
new ArbitraryDependency();
return autowiredFieldDependency;
}
}
集成测试的目的是证明按类型匹配优先于其他执行路径。注意FieldAutowiredTest集成测试的第8行如何引用变量名称:
@Autowired
private ArbitraryDependency fieldDependency;
与应用程序上下文中的bean名称不同:
@Bean
public ArbitraryDependency autowiredFieldDependency() {
ArbitraryDependency autowiredFieldDependency =
new ArbitraryDependency();
return autowiredFieldDependency;
}
当测试运行时,它将通过。
为了确认使用按类型匹配的执行路径确实解决了依赖关系,请更改fieldDependency引用变量的类型并再次运行集成测试。这一次,FieldAutowiredTest集成测试必须失败,抛出NoSuchBeanDefinitionException。这将验证是否使用了按类型匹配来解决依赖关系。
4.1.2 按Qualifier匹配
如果遇到在应用程序上下文中定义了多个bean实现的情况,如下所示:
@Configuration
public class ApplicationContextTestAutowiredQualifier {
@Bean
public ArbitraryDependency autowiredFieldDependency() {
ArbitraryDependency autowiredFieldDependency =
new ArbitraryDependency();
return autowiredFieldDependency;
}
@Bean
public ArbitraryDependency anotherAutowiredFieldDependency() {
ArbitraryDependency anotherAutowiredFieldDependency =
new AnotherArbitraryDependency();
return anotherAutowiredFieldDependency;
}
}
如果执行下面列出的FieldQualifierAutowiredTest集成测试:
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(
loader=AnnotationConfigContextLoader.class,
classes=ApplicationContextTestAutowiredQualifier.class)
public class FieldQualifierAutowiredTest {
@Autowired
private ArbitraryDependency fieldDependency1;
@Autowired
private ArbitraryDependency fieldDependency2;
@Test
public void givenAutowiredQualifier_WhenOnField_ThenDep1Valid(){
assertNotNull(fieldDependency1);
assertEquals("Arbitrary Dependency", fieldDependency1.toString());
}
@Test
public void givenAutowiredQualifier_WhenOnField_ThenDep2Valid(){
assertNotNull(fieldDependency2);
assertEquals("Another Arbitrary Dependency",
fieldDependency2.toString());
}
}
一个NoUniqueBeanDefinitionException将被抛出。
异常是由于应用程序上下文中定义的两个bean引起的歧义。Spring Framework不知道哪个bean依赖项应该自动连接到哪个引用变量。通过将@Qualifier注释添加到FieldQualifierAutowiredTest集成测试的第7行和第10行来解决此问题:
@Autowired
private FieldDependency fieldDependency1;
@Autowired
private FieldDependency fieldDependency2;
所以代码块看起来如下:
@Autowired
@Qualifier("autowiredFieldDependency")
private FieldDependency fieldDependency1;
@Autowired
@Qualifier("anotherAutowiredFieldDependency")
private FieldDependency fieldDependency2;
再次运行测试,这次它将通过。
4.1.3 按名称匹配
当使用@Autowired注释注入字段依赖关系时,将使用相同的集成测试场景来演示按名称执行的执行路径。按名称自动装配依赖关系时,@ ComponentScan批注必须与应用程序上下文ApplicationContextTestAutowiredName一起使用:
@Configuration
@ComponentScan(basePackages={"com.baeldung.dependency"})
public class ApplicationContextTestAutowiredName {
}
该@ComponentScan注释将搜索包已经标注了Java类@Component注解。例如,在应用程序上下文中,将扫描com.baeldung.dependency包以查找已使用@Component批注进行批注的类。在这种情况下,Spring Framework必须检测具有@Component注释的ArbitraryDependency类:
@Component(value="autowiredFieldDependency")
public class ArbitraryDependency {
private final String label = "Arbitrary Dependency";
public String toString() {
return label;
}
}
传递给@Component注释的属性值autowiredFieldDependency告诉Spring Framework,ArbitraryDependency类是一个名为autowiredFieldDependency的组件。为了使@Autowired注释按名称解析依赖关系,组件名称必须与FieldAutowiredNameTest集成测试中定义的字段名称相对应; 请参考第8行:
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(
loader=AnnotationConfigContextLoader.class,
classes=ApplicationContextTestAutowiredName.class)
public class FieldAutowiredNameTest {
@Autowired
private ArbitraryDependency autowiredFieldDependency;
@Test
public void givenAutowiredAnnotation_WhenOnField_ThenDepValid(){
assertNotNull(autowiredFieldDependency);
assertEquals("Arbitrary Dependency",
autowiredFieldDependency.toString());
}
}
当FieldAutowiredNameTest集成测试按原样运行时,它将通过。
但是我们怎么知道@Autowired注释确实调用了按名称执行的执行路径?将引用变量autowiredFieldDependency的名称更改为您选择的另一个名称,然后再次运行测试。
这次,测试将失败并抛出NoUniqueBeanDefinitionException。类似的检查是将@Component属性值autowiredFieldDependency更改为您选择的另一个值并再次运行测试。一个NoUniqueBeanDefinitionException也将被抛出。
此异常证明如果使用了错误的bean名称,则不会找到有效的bean。因此,调用了按名称匹配的执行路径。
4.2 Setter注入
@Autowired注释的基于Setter的注入类似于基于@Resource setter的注入的方法。不是使用@Inject注释来注释引用变量,而是注释相应的setter。基于字段的依赖注入之后的执行路径也适用于基于setter的注入。
5.应用这些注释
这提出了一个问题,应该使用哪种注释以及在什么情况下?这些问题的答案取决于相关应用程序所面临的设计方案,以及开发人员希望如何根据每个注释的默认执行路径利用多态性。
5.1 通过多态性在单一应用中使用单例
如果设计是应用程序行为基于接口或抽象类的实现,并且这些行为在整个应用程序中使用,则使用@Inject或@Autowired注释。
这种方法的好处是当应用程序升级时,或者需要应用补丁来修复错误; 然后可以交换类,对整个应用程序行为产生最小的负面影响。在此方案中,主要默认执行路径是按类型匹配。
5.2 通过多态性精细化应用程序行为配置
如果设计是应用程序具有复杂行为,则每个行为都基于不同的接口/抽象类,并且每个实现的使用因应用程序而异,然后使用@Resource注释。在此方案中,主要默认执行路径是按名称匹配。
5.3 Java EE平台应该单独处理依赖注入
如果Java EE平台而不是Spring注入所有依赖项的设计要求,那么选择在@Resource注释和@Inject注释之间。您应该根据需要的默认执行路径缩小两个注释之间的最终决策范围。
5.4 Spring框架应该单独处理依赖注入
如果授权是由Spring Framework处理所有依赖项,则唯一的选择是@Autowired注释。
5.5 讨论摘要
下表总结了讨论。
![81a94b6bb0c46c40d4d0e235041f988c.png](https://i-blog.csdnimg.cn/blog_migrate/2c37a077b15fb83c81d8a6239137fcba.jpeg)
6. 结论
本文旨在更深入地了解每个注释的行为。了解每个注释的行为方式将有助于更好地进行整体应用程序设计和维护。
原文链接:
Spring中@Autowired, @Resource和@Inject有何差异?mp.weixin.qq.com![3d62c4bbded9fe80bbc1483592692936.png](https://i-blog.csdnimg.cn/blog_migrate/66d1fb31d58df4f7b47f10becd12d988.jpeg)
推荐文章:
Java注解面试问答mp.weixin.qq.com![537722df3315702b9bab5c174f8846f3.png](https://i-blog.csdnimg.cn/blog_migrate/12ad07f03e0c12743c21fb3e7d85271d.jpeg)
![b8a6f3d0a2dba7e76d7844dbfcf43004.png](https://i-blog.csdnimg.cn/blog_migrate/a578582780b816218b798e5764469abc.jpeg)
![bdc51d66e1adf65153fc17077f04fb0d.png](https://i-blog.csdnimg.cn/blog_migrate/fe16719ed9b6eafaa94432c4eb6fbda2.jpeg)