浅谈spring ioc的注入方式及注入不同的数据类型

时间:2022-11-25 13:40:15

关于Spring-IoC的简单使用参考:

spring ioc的简单实例及bean的作用域属性解析

1、通过set方法注入不同数据类型

测试类代码(set方式注入的属性一定要加set方法)

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
/**通过set方法注入示例*/
public class IoC_By_Set {
    /**注入Integer类型参数*/
    private Integer id;
    /**注入String类型参数*/
    private String name;
    /**注入实体Bean*/
    private User user;
    /**注入数组*/
    private Object[] array;
    /**注入List集合*/
    private List<Object> list;
    /**注入Set集合*/
    private Set<Object> set;
    /**注入Map键值对*/
    private Map<Object, Object> map;
    /**注入properties类型*/
    private Properties properties;
    /**注入空字符串*/
    private String emptyValue;
    /**注入null值*/
    private String nullValue = "";
    /**检测注入的属性是否全部正确*/
    public Boolean checkAttr() {
        if(id == null) {
            return false;
        } else {
            System.out.println("id:" + id);
        }
        System.out.println("--------------------------");
        if(name == null) {
            return false;
        } else {
            System.out.println("name:" + name);
        }
        System.out.println("--------------------------");
        if(user == null) {
            return false;
        } else {
            System.out.println("Bean:" + user.getId() + "|" +
                      user.getUserName() + "|" + user.getPassWord());
        }
        System.out.println("--------------------------");
        if(array == null) {
            return false;
        } else {
            System.out.println("array:");
            for (Object object : array) {
                System.out.println(object.toString());
            }
        }
        System.out.println("--------------------------");
        if(list == null) {
            return false;
        } else {
            System.out.println("list:");
            for (Object object : list) {
                System.out.println(object.toString());
            }
        }
        System.out.println("--------------------------");
        if(set == null) {
            return false;
        } else {
            System.out.println("set:");
            for (Object object : set) {
                System.out.println(object.toString());
            }
        }
        System.out.println("--------------------------");
        if(map == null) {
            return false;
        } else {
            Set<Entry<Object, Object>> set = map.entrySet();
            System.out.println("map:");
            for (Entry<Object, Object> entry : set) {
                System.out.println(entry.getKey() + "|" + entry.getValue());
            }
        }
        System.out.println("--------------------------");
        if(properties == null) {
            return false;
        } else {
            Set<Entry<Object, Object>> set = properties.entrySet();
            System.out.println("properties:");
            for (Entry<Object, Object> entry : set) {
                System.out.println(entry.getKey() + "|" + entry.getValue());
            }
        }
        System.out.println("--------------------------");
        if(!"".equals(emptyValue))
              return false;
        System.out.println("--------------------------");
        if(!(null == nullValue))
              return false;
        System.out.println("--------------------------");
        System.out.println("全部正确!!!");
        return true;
    }
    public void setId(Integer id) {
        this.id = id;
    }
    public void setName(String name) {
        this.name = name;
    }
    public void setUser(User user) {
        this.user = user;
    }
    public void setArray(Object[] array) {
        this.array = array;
    }
    public void setList(List<Object> list) {
        this.list = list;
    }
    public void setSet(Set<Object> set) {
        this.set = set;
    }
    public void setMap(Map<Object, Object> map) {
        this.map = map;
    }
    public void setProperties(Properties properties) {
        this.properties = properties;
    }
    public void setEmptyValue(String emptyValue) {
        this.emptyValue = emptyValue;
    }
    public void setNullValue(String nullValue) {
        this.nullValue = nullValue;
    }
}

applicationContext.xml配置

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
<!-- set方式注入 -->
<bean id="ioC_By_Set" class="com.bc.ioc.demo01.IoC_By_Set">
  <!-- 注入id属性 -->
  <property name="id" value="1"/>
  <!-- 使用<![CDATA[]]>标记处理XML特 殊字符 -->
  <property name="name">
    <!-- 也可以使用P&amp;G -->
    <value><![CDATA[P&G]]></value>
  </property>
  <!-- 定义内部Bean注入 -->
  <property name="user">
    <bean class="com.bc.pojo.User">
      <property name="id" value="1"/>
      <property name="userName" value="内部Bean"/>
      <property name="passWord" value="233"/>
    </bean>
  </property>
  <!-- 注入数组类型 -->
  <property name="array">
    <array>
      <!-- 定义数组元素 -->
      <value>array01</value>
      <value>array02</value>
      <value>array03</value>
    </array>
  </property>
  <!-- 注入List类型 -->
  <property name="list">
    <list>
      <!-- 定义list中元素 -->
      <value>list01</value>
      <value>list02</value>
      <value>list03</value>
    </list>
  </property>
  <!-- 注入Set类型 -->
  <property name="set">
    <set>
      <!-- 定义set中元素 -->
      <value>set01</value>
      <value>set02</value>
      <value>set03</value>
    </set>
  </property>
  <!-- 注入Map类型 -->
  <property name="map">
    <map>
      <!-- 定义map中的键值对 -->
      <entry>
        <key>
          <value>mapKey01</value>
        </key>
        <value>mapValue01</value>
      </entry>
      <entry>
        <key>
          <value>mapKey02</value>
        </key>
        <value>mapValue02</value>
      </entry>
    </map>
  </property>
  <!-- 注入properties类型 -->
  <property name="properties">
    <props>
      <!-- 定义properties中的键值对 -->
      <prop key="propKey1">propValue1</prop>
      <prop key="propKey2">propValue2</prop>
    </props>
  </property>
  <!-- 注入空字符串 -->
  <property name="emptyValue">
    <value></value>
  </property>
  <!-- 注入null值 -->
  <property name="nullValue">
    <null/>
  </property>
</bean>

测试代码

?
1
2
3
4
5
6
7
8
9
10
11
12
13
public class IoC_Test {
    private ApplicationContext ctx;
    @Before
      public void load() {
        //读取applicationContext.xml配置文件
        ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
    }
    @Test
      public void SetTest() {
        IoC_By_Set ioc = (IoC_By_Set) ctx.getBean("ioC_By_Set");
        ioc.checkAttr();
    }
}

控制台结果:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
id:1
--------------------------
name:P&G
--------------------------
Bean:1|内部Bean|233
--------------------------
array:
array01
array02
array03
--------------------------
list:
list01
list02
list03
--------------------------
set:
set01
set02
set03
--------------------------
map:
mapKey01|mapValue01
mapKey02|mapValue02
--------------------------
properties:
propKey2|propValue2
propKey1|propValue1
--------------------------
--------------------------
--------------------------
全部正确!!!

2、通过构造方法注入各种类型属性

注意:使用JDK1.8版本请将spring相关jar包升级到4.x版本以上,否则不兼容构造方法注入

测试类代码

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
/** 通过构造方法注入示例 */
public class IoC_By_Constructor {
    private Integer id;
    private String name;
    private User user;
    private List<Object> list;
    public IoC_By_Constructor() {
    }
    public IoC_By_Constructor(Integer id, String name, User user,
          List<Object> list) {
        this.id = id;
        this.name = name;
        this.user = user;
        this.list = list;
    }
    /**检查是否注入成功*/
    public Boolean checkAttr() {
        if(id == null) {
            return false;
        } else {
            System.out.println("id:" + id);
        }
        System.out.println("----------------------------");
        if(name == null) {
            return false;
        } else {
            System.out.println("name:" + name);
        }
        System.out.println("----------------------------");
        if(user == null) {
            return false;
        } else {
            System.out.println("user:" + user.getId() + "|" +
                      user.getUserName() + "|" + user.getPassWord());
        }
        System.out.println("----------------------------");
        if(list == null) {
            return false;
        } else {
            System.out.println("list:");
            for (Object object : list) {
                System.out.println(object.toString());
            }
        }
        System.out.println("----------------------------");
        System.out.println("全部正确!!!");
        return true;
    }
}

applicationContext.xml配置

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
<!-- 构造方法注入 演示几种类型-->
<bean id="ioC_By_Constructor" class="com.bc.ioc.demo02.IoC_By_Constructor">
  <!-- 注入Integer属性,可以选择使用index指定参数位置,也可以选择使用type指定参数类型 -->
  <constructor-arg index="0" value="1" type="java.lang.Integer"/>
  <!-- 注入字符串 -->
  <constructor-arg value="P&amp;G"/>
  <!-- 注入对象 -->
  <constructor-arg>
    <!-- 内建对象 -->
    <bean class="com.bc.pojo.User">
      <constructor-arg value="1"/>
      <constructor-arg value="构造内部Bean"/>
      <constructor-arg value="666"/>
    </bean>
  </constructor-arg>
  <!-- 注入集合 -->
  <constructor-arg>
    <list>
      <value>list01</value>
      <value>list02</value>
      <value>list03</value>
    </list>
  </constructor-arg>
</bean>

测试代码:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
public class IoC_Test {
    private ApplicationContext ctx;
    @Before
      public void load() {
        //读取applicationContext.xml配置文件
        ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
    }
    @Test
      public void constructorTest() {
        IoC_By_Constructor ioc = (IoC_By_Constructor) ctx.getBean("ioC_By_Constructor");
        ioc.checkAttr();
    }
}

控制台结果:

?
1
2
3
4
5
6
7
8
9
10
11
12
id:1
----------------------------
name:P&G
----------------------------
user:1|构造内部Bean|666
----------------------------
list:
list01
list02
list03
----------------------------
全部正确!!!

3、自动注入(自动装配)

自动装配虽然能节省一些代码但是不推荐使用

测试类代码:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
/**自动装配注入*/
public class IoC_By_Auto {
    private User user;
    /**检查是否注入成功*/
    public Boolean checkAttr() {
        if(user == null) {
            return false;
        } else {
            System.out.println("user:" + user.getId() + "|" +
                      user.getUserName() + "|" + user.getPassWord());
        }
        System.out.println("正确!!!");
        return true;
    }
    /**自动装配的属性需要设置set方法*/
    public void setUser(User user) {
        this.user = user;
    }
}

applicationContext.xml配置

?
1
2
3
4
5
6
7
8
9
10
11
12
13
<!-- 被自动装配获取的bean -->
<bean id="user" class="com.bc.pojo.User">
  <property name="id" value="1"/>
  <property name="userName" value="自动装配"/>
  <property name="passWord" value="233"/>
</bean>
<!-- 自动装配的bean
   autowire:byName 根据类的属性名查找与之命名相同的id的bean进行装配
       byType 根据类的属性类型查找唯一一个匹配类型的bean,如果有多个bean匹配则抛出异常
       constructor 根据类的构造方法参数类型匹配对应的bean
       no 默认,表示不使用自动装配
       default:由上级标签<beans>的default-autowire属性确定 -->
<bean id="ioC_By_Auto" class="com.bc.ioc.demo03.IoC_By_Auto" autowire="byName"></bean>

测试代码

?
1
2
3
4
5
6
7
8
9
10
11
12
13
public class IoC_Test {
    private ApplicationContext ctx;
    @Before
      public void load() {
        //读取applicationContext.xml配置文件
        ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
    }
    @Test
      public void AutoTest() {
        IoC_By_Auto ioc = (IoC_By_Auto) ctx.getBean("ioC_By_Auto");
        ioc.checkAttr();
    }
}

控制台结果

?
1
2
user:1|自动装配|233
正确!!!

以上使用的是byName模式,其他模式配置代码已经注明,不做测试。

4、使用P命名空间注入属性

测试类代码

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
/**使用P命名空间注入*/
public class IoC_By_P {
    private Integer id;
    private String name;
    private User user;
    /**检查是否注入成功*/
    public Boolean checkAttr() {
        if(id == null) {
            return false;
        } else {
            System.out.println("id:" + id);
        }
        System.out.println("----------------------------");
        if(name == null) {
            return false;
        } else {
            System.out.println("name:" + name);
        }
        System.out.println("----------------------------");
        if(user == null) {
            return false;
        } else {
            System.out.println("user:" + user.getId() + "|" +
                      user.getUserName() + "|" + user.getPassWord());
        }
        System.out.println("----------------------------");
        System.out.println("全部正确!!!");
        return true;
    }
    //使用P命名空间注入属性需要设置set方法
    public void setId(Integer id) {
        this.id = id;
    }
    public void setName(String name) {
        this.name = name;
    }
    public void setUser(User user) {
        this.user = user;
    }
}

applicationContext.xml配置

?
1
2
3
4
5
6
7
8
<!-- 使用P命名空间注入各种类型属性 -->
<bean id="user2" class="com.bc.pojo.User">
  <property name="id" value="1"/>
  <property name="userName" value="P"/>
  <property name="passWord" value="233"/>
</bean>
<bean id="ioC_By_P" class="com.bc.ioc.demo04.IoC_By_P" p:id="1"
  p:name="命名空间" p:user-ref="user2"></bean>

测试代码

?
1
2
3
4
5
6
7
8
9
10
11
12
13
public class IoC_Test {
    private ApplicationContext ctx;
    @Before
      public void load() {
        //读取applicationContext.xml配置文件
        ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
    }
    @Test
      public void PTest() {
        IoC_By_P ioc = (IoC_By_P) ctx.getBean("ioC_By_P");
        ioc.checkAttr();
    }
}

控制台结果

?
1
2
3
4
5
6
7
id:1
----------------------------
name:命名空间
----------------------------
user:1|P|233
----------------------------
全部正确!!!

5、使用注解方式注入

Spring在3.0以后,提供了基于Annotation(注解)的注入。

1.@Autowired-对成员变量、方法和构造函数进行标注,来完成自动装配的工作,不推荐使用

2.@Qualifier-配合@Autowired来解决装配多个同类型的bean

3.@Resource-JSR-250标准注解,作用相当于@Autowired,只不过@Autowired按byType自动注入,而@Resource默认按byName自动注入

4.@PostConstruct-在方法上加上注解@PostConstruct,这个方法就会在Bean初始化之后被Spring容器执行

5.@PreDestroy-在方法上加上注解@PreDestroy,这个方法就会在Bean初始化之后被Spring容器执行

6.@Component-只需要在对应的类上加上一个@Component注解,就将该类定义为一个Bean,不推荐使用,推荐使用更加细化的三种:@Repository、@Service、@Controller

@Repository存储层Bean

@Service业务层Bean

@Controller展示层Bean

7.@Scope-定义Bean的作用范围

首先配置applicationContext.xml开启注解

?
1
2
<!-- 扫描包中注解标注的类 -->
<context:component-scan base-package="com.bc.ioc.demo05"/>

实体Bean加注解

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
@Repository
public class User {
    private Integer id = 1;
    private String userName = "注解注入";
    private String passWord = "233";
    public User() {
        super();
    }
    public User(Integer id, String userName, String passWord) {
        super();
        this.id = id;
        this.userName = userName;
        this.passWord = passWord;
    }
    public Integer getId() {
        return id;
    }
    public String getUserName() {
        return userName;
    }
    public String getPassWord() {
        return passWord;
    }
    public void setId(Integer id) {
        this.id = id;
    }
    public void setUserName(String userName) {
        this.userName = userName;
    }
    public void setPassWord(String passWord) {
        this.passWord = passWord;
    }
}

测试类代码加注解

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
/**使用注解注入属性*/
@Service("ioC_By_Annotation")
public class IoC_By_Annotation {
    @Resource
      private User user;
    public void setUser(User user) {
        this.user = user;
    }
    /**检查是否注入成功*/
    public Boolean checkAttr() {
        if(user == null) {
            return false;
        } else {
            System.out.println("user:" + user.getId() + "|" +
                      user.getUserName() + "|" + user.getPassWord());
        }
        System.out.println("正确!!!");
        return true;
    }
}

测试代码

?
1
2
3
4
5
6
7
8
9
10
11
12
13
public class IoC_Test {
    private ApplicationContext ctx;
    @Before
      public void load() {
        //读取applicationContext.xml配置文件
        ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
    }
    @Test
      public void annotationTest() {
        IoC_By_Annotation ioc = (IoC_By_Annotation) ctx.getBean("ioC_By_Annotation");
        ioc.checkAttr();
    }
}

控制台输出

经测试使用注解注入如果applicationContext.xml配置有其他注入方式会报错,也会导致其他注入方式异常。

?
1
2
user:1|注解注入|233
正确!!!

6、通过配置静态工厂方法Bean注入

静态工厂代码

?
1
2
3
4
5
6
7
8
9
10
11
12
/**静态工厂*/
public class StaticFactory {
    public static Integer getId() {
        return 1;
    }
    public static String getName() {
        return "静态工厂";
    }
    public static User getUser() {
        return new User(1, "工厂User", "666");
    }
}

测试类代码

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
/** 通过静态工厂方式注入 */
public class IoC_By_StaticFactory {
    private Integer id;
    private String name;
    private User user;
    /** 检查是否注入成功 */
    public Boolean checkAttr() {
        if (id == null) {
            return false;
        } else {
            System.out.println("id:" + id);
        }
        System.out.println("----------------------------");
        if (name == null) {
            return false;
        } else {
            System.out.println("name:" + name);
        }
        System.out.println("----------------------------");
        if (user == null) {
            return false;
        } else {
            System.out.println("user:" + user.getId() + "|"
                      + user.getUserName() + "|" + user.getPassWord());
        }
        System.out.println("----------------------------");
        System.out.println("全部正确!!!");
        return true;
    }
    /**需要为需要注入的属性设置set方法*/
    public void setId(Integer id) {
        this.id = id;
    }
    public void setName(String name) {
        this.name = name;
    }
    public void setUser(User user) {
        this.user = user;
    }
}

applicationContext.xml配置

?
1
2
3
4
5
6
7
8
9
10
<!-- 配置静态工厂方法Bean 其实就是将工厂方法返回的数值配置成Bean -->
<bean id="factory_id" class="com.bc.ioc.demo06.StaticFactory" factory-method="getId"/>
<bean id="factory_name" class="com.bc.ioc.demo06.StaticFactory" factory-method="getName"/>
<bean id="factory_user" class="com.bc.ioc.demo06.StaticFactory" factory-method="getUser"/>
<!-- 注入对应的静态工厂方法Bean -->
<bean id="ioC_By_StaticFactory" class="com.bc.ioc.demo06.IoC_By_StaticFactory">
  <property name="id" ref="factory_id"/>
  <property name="name" ref="factory_name"/>
  <property name="user" ref="factory_user"/>
</bean>

测试代码

?
1
2
3
4
5
6
7
8
9
10
11
12
13
public class IoC_Test {
    private ApplicationContext ctx;
    @Before
      public void load() {
        //读取applicationContext.xml配置文件
        ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
    }
    @Test
      public void staticFactoryTest() {
        IoC_By_StaticFactory ioc = (IoC_By_StaticFactory) ctx.getBean("ioC_By_StaticFactory");
        ioc.checkAttr();
    }
}

控制台输出结果

?
1
2
3
4
5
6
7
id:1
----------------------------
name:静态工厂
----------------------------
user:1|工厂User|666
----------------------------
全部正确!!!

7、通过实例工厂方法注入

与静态工厂区别在于实例工厂不是静态的,需要先new 一个实例工厂对象,才可以配置其方法,而new 的这个对象也由spring来管理

工厂代码

?
1
2
3
4
5
6
7
8
9
10
11
12
/**实例工厂*/
public class Factory {
    public Integer getId() {
        return 1;
    }
    public String getName() {
        return "实例工厂";
    }
    public User getUser() {
        return new User(1, "实例工厂User", "233");
    }
}

测试类代码

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
/**实例工厂注入*/
public class IoC_By_Factory {
    private Integer id;
    private String name;
    private User user;
    /** 检查是否注入成功 */
    public Boolean checkAttr() {
        if (id == null) {
            return false;
        } else {
            System.out.println("id:" + id);
        }
        System.out.println("----------------------------");
        if (name == null) {
            return false;
        } else {
            System.out.println("name:" + name);
        }
        System.out.println("----------------------------");
        if (user == null) {
            return false;
        } else {
            System.out.println("user:" + user.getId() + "|"
                      + user.getUserName() + "|" + user.getPassWord());
        }
        System.out.println("----------------------------");
        System.out.println("全部正确!!!");
        return true;
    }
    /**需要为需要注入的属性设置set方法*/
    public void setId(Integer id) {
        this.id = id;
    }
    public void setName(String name) {
        this.name = name;
    }
    public void setUser(User user) {
        this.user = user;
    }
}

applicationContext.xml配置

?
1
2
3
4
5
6
7
8
9
10
11
12
<!-- 配置实例工厂Bean -->
<bean id="factory" class="com.bc.ioc.demo07.Factory"/>
<!-- 配置实例工厂方法Bean -->
<bean id="f_id" factory-bean="factory" factory-method="getId"/>
<bean id="f_name" factory-bean="factory" factory-method="getName"/>
<bean id="f_user" factory-bean="factory" factory-method="getUser"/>
<!-- 注入对应的实例工厂方法Bean -->
<bean id="ioC_By_Factory" class="com.bc.ioc.demo07.IoC_By_Factory">
  <property name="id" ref="f_id"/>
  <property name="name" ref="f_name"/>
  <property name="user" ref="f_user"/>
</bean>

测试类代码

?
1
2
3
4
5
6
7
8
9
10
11
12
13
public class IoC_Test {
    private ApplicationContext ctx;
    @Before
      public void load() {
        //读取applicationContext.xml配置文件
        ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
    }
    @Test
      public void factoryTest() {
        IoC_By_Factory ioc = (IoC_By_Factory) ctx.getBean("ioC_By_Factory");
        ioc.checkAttr();
    }
}

控制台输出

?
1
2
3
4
5
6
7
id:1
----------------------------
name:实例工厂
----------------------------
user:1|实例工厂User|233
----------------------------
全部正确!!!

总结

以上就是本文关于浅谈spring ioc的注入方式及注入不同的数据类型的全部内容,希望对大家有所帮助。感兴趣的朋友可以继续参阅本站其他相关专题,如有不足之处,欢迎留言指出。感谢朋友们对本站的支持!

原文链接:http://blog.csdn.net/qq_32588349/article/details/51554379