SpringBoot - 3. Le principe de l'assemblage automatique et les astuces de développement

teneur

1. Caractéristiques de Spring Boot

1.1. Gestion des dépendances

1.2, configuration automatique

1.2.1. Configurer automatiquement Tomcat

1.2.2. Configurer automatiquement SpringMVC

1.2.3. Configurer automatiquement les fonctions Web courantes, telles que : problème d'encodage des caractères

1.2.4, la structure de paquet par défaut

1.2.5, diverses configurations ont des valeurs par défaut

1.2.6. Charger tous les éléments de configuration automatique à la demande

2. Fonction conteneur

2.1, ajout de composants

2.1.1、@Configuration

2.1.2、@Importer

2.1.3、@Conditionnel

2.2, l'introduction de fichiers de configuration natifs

2.2.1、@ImportResource

2.3, liaison de configuration

2.3.1、@Composant + @ConfigurationProperties

 2.3.2、@EnableConfigurationProperties + @ConfigurationProperties

3. Introduction au principe de configuration automatique

3.1. Classe de configuration automatique du chargement au démarrage

 3.1.1、@SpringBootConfiguration

3.1.2、Analyse des composants

3.1.3、@EnableAutoConfiguration

3.2. Activer les éléments de configuration automatique selon les besoins

3.3, modifier la configuration par défaut

3.4. Résumé

3.5. Meilleures pratiques

4. Compétences de développement

4.1、Lombok

4.2、@Slf4j

 4.3、outils de développement

4.4, Spring Initailizr (assistant d'initialisation de projet)


1. Caractéristiques de Spring Boot

1.1. Gestion des dépendances

  • Le projet parent fait la gestion des dépendances
    <!-- 依赖管理 -->
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.6.5</version>
    </parent>

Cliquez sur spring-boot-starter-parent, affichez son projet parent, puis cliquez sur spring-boot-dependencies dans la cinquième ligne, vous pouvez voir que presque toutes les versions de dépendance couramment utilisées y sont définies

  • Pas besoin de faire attention au numéro de version, arbitrage automatique des versions, vous pouvez modifier le numéro de version par défaut

Comme mentionné ci-dessus, SpringBoot définit presque toutes les versions de dépendance couramment utilisées, vous pouvez donc importer des dépendances sans écrire la version par défaut.

Si vous souhaitez utiliser d'autres versions de dépendances, vous pouvez définir la version requise dans pom.xml, comme suit, la version pilotée par mysql est 5.1.47

    <properties>
        <mysql.version>5.1.47</mysql.version>
    </properties>

    <dependencies>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
        </dependency>
    </dependencies>
  • Démarrage de la scène de démarrage d'importation de développement

1. Je vois beaucoup de spring-boot-starter-* : * représente un certain scénario
2. Tant que le démarreur est introduit, nous introduirons automatiquement toutes les dépendances régulières de ce scénario.
3. Tous les scénarios pris en charge par SpringBoot   https://docs.spring.io/spring-boot/docs/current/reference/html/using.html#using.build-systems.starters 4. Voir * -spring
-boot-starter : un scénario de développement simplifié fourni par un tiers pour nous Launcher.
5. Les dépendances de niveau inférieur de tous les lanceurs de scène sont les suivantes

    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter</artifactId>
      <version>2.6.5</version>
      <scope>compile</scope>
    </dependency>

1.2, configuration automatique

1.2.1. Configurer automatiquement Tomcat

        Introduire tomcat, configurer Tomcat

    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-tomcat</artifactId>
      <version>2.6.5</version>
      <scope>compile</scope>
    </dependency>

1.2.2. Configurer automatiquement SpringMVC

        Introduire un ensemble complet de composants SpringMVC et configurer automatiquement les composants communs SpringMVC (fonctions)

1.2.3. Configurer automatiquement les fonctions Web courantes, telles que : problème d'encodage des caractères

        SpringBoot nous aide à configurer tous les scénarios courants de développement Web. Si vous voulez le voir, vous pouvez l'obtenir dans le programme principal

@SpringBootApplication  // 标识为一个SpringBoot应用,被标识的类称为主程序类
public class MainApplication {
    public static void main(String[] args) {
        // 返回IOC容器
        ConfigurableApplicationContext run = SpringApplication.run(MainApplication.class, args);

        // 查看容器里面的组件
        String[] names = run.getBeanDefinitionNames();
        for (String name : names) {
            System.out.println(name);
        }
    }
}

1.2.4 , la structure de paquet par défaut

  ○ Les composants du package où se trouve le programme principal et tous les sous-packages en dessous seront analysés par défaut, comme indiqué ci-dessous


  ○ Aucune configuration d'analyse de package précédente n'est requise
  ○ Pour modifier le chemin d'analyse, vous devez spécifier le chemin d'analyse avec @ComponentScan ou utiliser la déclaration scanBasePackages dans l'annotation @SpringBootApplication, par exemple :

@SpringBootApplication(scanBasePackages = "com.zyj")

Remarque : Étant donné que l'annotation @ComponentScan a été déclarée dans l'annotation @SpringBootApplication pour analyser le package où se trouve le programme principal, @SpringBootApplication et @ComponentScan ne peuvent pas être utilisés ensemble.

1.2.5, diverses configurations ont des valeurs par défaut

  La configuration par défaut est finalement mappée à une classe, telle que : MultipartProperties

  La valeur du fichier de configuration sera éventuellement liée à chaque classe, ce qui créera l'objet dans le conteneur

1.2.6. Charger tous les éléments de configuration automatique à la demande

  ○ Il existe de nombreux démarreurs, qui peuvent être introduits selon les besoins
  ○ Quels scénarios sont introduits avant que la configuration automatique de ce scénario ne soit activée
  ○ Toutes les fonctions de configuration automatique de SpringBoot se trouvent dans le package spring-boot-autoconfigure

2. Fonction conteneur

2.1, ajout de composants

2.1.1、@Configuration

Mode complet et mode simplifié

Full(proxyBeanMethods = true) : l'objet proxy appelle la méthode, SpringBoot vérifiera si le composant a été enregistré dans le conteneur, c'est-à-dire qu'il conservera l'instance unique du composant
Lite(proxyBeanMethods = false) : ne conservera pas l'instance unique 

Il n'existe aucune dépendance entre les composants de la classe de configuration. Utilisez le mode Lite pour accélérer le processus de démarrage du conteneur et réduire le jugement
. S'il existe une relation de dépendance entre les composants de la classe de configuration, la méthode sera appelée pour obtenir le composant à instance unique précédent. Utilisez Full mode

Il existe des dépendances entre les composants : par exemple, un composant nécessite un autre composant en tant que propriété, dans l'exemple suivant, le composant utilisateur dépend du composant pet

Exemple

① Préparez d'abord deux classes, créez la classe Pet et la classe User sous src/main/java/com/zyj/boot/bean.

Les attributs de la classe Pet sont les suivants, générant une structure sans paramètre, une structure paramétrée, des méthodes set et get et des méthodes toString

    private String name;

Les propriétés de la classe User sont les suivantes, générant une structure sans paramètre, une structure paramétrée, des méthodes set et get et des méthodes toString

    private String name;
    private Integer age;
    private Pet pet;

② Générer une classe de configuration

Créez la classe de configuration MyConfig sous src/main/java/com/zyj/boot/config

/**
 * 1、配置类里面使用@Bean标注在方法上给容器注册组件,默认是单实例的
 * 2、@Configuration 标识的类也是容器的一个组件
 * 3、proxyBeanMethods:代理bean方法
 *      Full(proxyBeanMethods = true):代理对象调用方法,SpringBoot会检查这个组件在容器中是否已被注册,即保持组件单实例
 *      Lite(proxyBeanMethods = false):不会保持单实例
 *      用于解决组件依赖:组件依赖必须使用Full模式默认。其他默认是Lite模式(减少判断,加快运行)
 */
@Configuration(proxyBeanMethods = true)  // 告诉SpringBoot这是一个配置类 == 配置文件
public class MyConfig {
    /**
     * 在 proxyBeanMethods = true 的情况下:
     * 外部无论对配置类中的这个组件方法调用多少次,获取的都是之前注册到容器的单实例对象
     * @return
     */
    @Bean()  //给容器添加组件,默认以方法名作为组件的id(可以在括号中自定义),返回类型就是组件类型,返回的值就是组件在容器中的实例
    public User user01(){
        User zhangsan = new User("zhangsan", 18);
        // user组件依赖了pet组件,需要使用Full模式
        zhangsan.setPet(tomcatPet());
        return new User("zhangsan", 18);
    }

    @Bean("tom")
    public Pet tomcatPet(){
        return new Pet("tomcat");
    }
}

③ Classe de programme principale :

@SpringBootApplication(scanBasePackages = "com.zyj")  // 标识为一个SpringBoot应用,被标识的类称为主程序类
public class MainApplication {
    public static void main(String[] args) {
        // 返回IOC容器
        ConfigurableApplicationContext run = SpringApplication.run(MainApplication.class, args);

        // 查看容器里面的组件
        String[] names = run.getBeanDefinitionNames();
        for (String name : names) {
            System.out.println(name);
        }

        // 从容器中获取组件
        Pet tom01 = run.getBean("tom", Pet.class);
        Pet tom02 = run.getBean("tom", Pet.class);
        // 若proxyBeanMethods为true,返回true,否则返回false
        System.out.println("组件:" + (tom01 == tom02));

        MyConfig bean = run.getBean(MyConfig.class);
        System.out.println(bean);

        // 如果@Configuration(proxyBeanMethods = true),则是代理对象调用方法
        // SpringBoot会检查这个组件在容器中是否已被注册,即保持组件单实例
        User user1 = bean.user01();
        User user2 = bean.user01();
        // 若proxyBeanMethods为true,返回true,否则返回false
        System.out.println("user比较:" + (user1 == user2));

        User user01 = run.getBean("user01", User.class);
        Pet tom = run.getBean("tom", Pet.class);
        // 若proxyBeanMethods为true,返回false,否则返回true
        System.out.println("用户的宠物与容器的宠物比较:" + (user01.getPet() == tom));
    }
}

2.1.2、@Importer

@Import({User.class, Matcher.class}) : le composant entre parenthèses est automatiquement créé pour le conteneur via une construction sans paramètre (plusieurs composants sont séparés par des virgules). Le nom du composant par défaut est le nom complet de la classe

Classe de configuration : 

@Import({User.class, Matcher.class})
@Configuration(proxyBeanMethods = true)  // 告诉SpringBoot这是一个配置类 == 配置文件
public class MyConfig {
}

Vérifiez dans le programme principal :

        // 验证@Import注解获取组件
        String[] beanNamesForType = run.getBeanNamesForType(User.class);
        System.out.println("===================================");
        System.out.println("验证@Import注解获取组件");
        for (String s : beanNamesForType) {
            System.out.println(s);
        }
        //com.zyj.boot.bean.User    @Import注解生成的
        //user01    MyConfig中生成的
        Matcher bean1 = run.getBean(Matcher.class);
        System.out.println(bean1);  //java.util.regex.Matcher[pattern=null region=0,0 lastmatch=]

2.1.3、@Conditionnel

Assemblage conditionnel : Si les conditions spécifiées par Conditionnel sont remplies, l'injection de composant est effectuée

 Par exemple, si l'annotation @ConditionalOnBean(name = "tom") est sur une méthode, le composant généré par cette méthode sera injecté uniquement lorsqu'il y a un composant tom dans le conteneur. S'il est sur une classe, le composant généré par la méthode de la classe ne sera injecté que lorsqu'il y a un composant tom dans le conteneur.

Classe de configuration : 

@ConditionalOnBean(name = "tom")
@Import({User.class, Matcher.class})
@Configuration(proxyBeanMethods = true)  // 告诉SpringBoot这是一个配置类 == 配置文件
public class MyConfig {
    /**
     * 在 proxyBeanMethods = true 的情况下:
     * 外部无论对配置类中的这个组件方法调用多少次,获取的都是之前注册到容器的单实例对象
     * @return
     */
    //@ConditionalOnBean(name = "tom") // 容器中有tom组件时才注入user01组件
    @Bean()  //给容器添加组件,默认以方法名作为组件的id(可以在括号中自定义),返回类型就是组件类型,返回的值就是组件在容器中的实例
    public User user01(){
        User zhangsan = new User("zhangsan", 18);
        // user组件依赖了pet组件,需要使用Full模式
        zhangsan.setPet(tomcatPet());
        return new User("zhangsan", 18);
    }

    @Bean("tom22")
    public Pet tomcatPet(){
        return new Pet("tomcat");
    }
}

Classe principale du programme :

        // 去掉com.zyj.boot.config.MyConfig.tomcatPet的@Bean("tom")注解后,查看是否有tom组件
        boolean tom = run.containsBean("tom");
        System.out.println("查看是否有tom组件:" + tom); //false
        // 给com.zyj.boot.config.MyConfig.user01添加注解@ConditionalOnBean(name = "tom")后
        boolean user01 = run.containsBean("user01");
        System.out.println("查看是否有user01组件:" + user01); //false
        // 给com.zyj.boot.config.MyConfig添加注解@ConditionalOnBean(name = "tom")后
        boolean tom22 = run.containsBean("tom22");
        System.out.println("查看是否有tom22组件:" + tom22); //false

2.2, l'introduction de fichiers de configuration natifs

2.2.1、@ImportResource

L'annotation @ImportResource peut importer des fichiers de configuration

haricots.xml:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">

    <bean id="haha" class="com.zyj.boot.bean.User">
        <property name="name" value="zhangsan"></property>
        <property name="age" value="18"></property>
    </bean>

    <bean id="hehe" class="com.zyj.boot.bean.Pet">
        <property name="name" value="tomcat"></property>
    </bean>

</beans>

Classe de configuration MyConfig :

@ImportResource("classpath:beans.xml")  //导入配置文件
public class MyConfig {
}

Classe principale du programme :

        boolean haha = run.containsBean("haha");
        boolean hehe = run.containsBean("hehe");
        //在配置类加入@ImportResource("classpath:bean.xml")之前为false
        System.out.println("查看是否有haha组件" + haha);
        System.out.println("查看是否有hehe组件" + hehe);

2.3, liaison de configuration

Il est très gênant d'utiliser Java pour lire le contenu du fichier de propriétés et de l'encapsuler dans un JavaBean pour une utilisation immédiate

public class getProperties {
     public static void main(String[] args) throws FileNotFoundException, IOException {
         Properties pps = new Properties();
         pps.load(new FileInputStream("a.properties"));
         Enumeration enum1 = pps.propertyNames();//得到配置文件的名字
         while(enum1.hasMoreElements()) {
             String strKey = (String) enum1.nextElement();
             String strValue = pps.getProperty(strKey);
             System.out.println(strKey + "=" + strValue);
             //封装到JavaBean。
         }
     }
 }

2.3.1、@Composant + @ConfigurationProperties

src/main/resources/application.properties

mycar.brand=BYD
mycar.price=100000

Créer une voiture sous src/main/java/com/zyj/boot/bean

// 只有在容器中的组件才有SpringBoot提供的强大功能
@Component
@ConfigurationProperties(prefix = "mycar")
public class Car {

    private String brand;
    private Integer price;

    public Car() {
    }

    public Car(String brand, Integer price) {
        this.brand = brand;
        this.price = price;
    }

    public String getBrand() {
        return brand;
    }

    public void setBrand(String brand) {
        this.brand = brand;
    }

    public Integer getPrice() {
        return price;
    }

    public void setPrice(Integer price) {
        this.price = price;
    }

    @Override
    public String toString() {
        return "Car{" +
                "brand='" + brand + '\'' +
                ", price=" + price +
                '}';
    }
}

在 src/main/java/com/zyj/boot/controller/HelloController.java 

@RestController  // @RestController可以代替@Controller和@ResponseBody
public class HelloController {

    @Autowired
    Car car;

    @RequestMapping("/car")
    public Car car(){
        return car;
    }

    @RequestMapping("/hello")
    public String handler01(){
        return "hello,Spring Boot 2!" + "你好";
    }

}

Après exécution, accédez à l'adresse correspondante, la page s'affiche comme suit

 2.3.2、@EnableConfigurationProperties + @ConfigurationProperties

Cette méthode doit être écrite dans la classe de configuration

src/main/resources/application.properties

mycar.brand=BYD
mycar.price=100000

src/main/java/com/zyj/boot/config/MyConfig.java

@EnableConfigurationProperties(Car.class)  //开启Car配置绑定功能,并把Car这个组件自动注册到容器中
public class MyConfig {
}
@ConfigurationProperties(prefix = "mycar")
public class Car {

    private String brand;
    private Integer price;

    public Car() {
    }

    public Car(String brand, Integer price) {
        this.brand = brand;
        this.price = price;
    }

    public String getBrand() {
        return brand;
    }

    public void setBrand(String brand) {
        this.brand = brand;
    }

    public Integer getPrice() {
        return price;
    }

    public void setPrice(Integer price) {
        this.price = price;
    }

    @Override
    public String toString() {
        return "Car{" +
                "brand='" + brand + '\'' +
                ", price=" + price +
                '}';
    }
}

Rendez-vous sur l'adresse correspondante pour visualiser le produit généré

3. Introduction au principe de configuration automatique

3.1. Classe de configuration automatique du chargement au démarrage

En regardant le code source de SpringBootApplication, vous pouvez voir qu'il contient @SpringBootConfiguration, @EnableAutoConfiguration, @ComponentScan

 3.1.1、@SpringBootConfiguration

@SpringBootConfiguration contient l'annotation @Configuration, ce qui signifie qu'il s'agit actuellement d'une classe de configuration

3.1.2、Analyse des composants

spécifier le colis à scanner,

3.1.3、@EnableAutoConfiguration

 @EnableAutoConfiguration contient les deux annotations suivantes

@AutoConfigurationPackage
@Import({AutoConfigurationImportSelector.class})
public @interface EnableAutoConfiguration {
}

① @AutoConfigurationPackage

P13 

@AutoConfigurationPackage est un package de configuration automatique qui spécifie la stratégie de package par défaut

@Import({Registrar.class})  //给容器导入一个组件
public @interface AutoConfigurationPackage {
}

Cliquez sur Registrar, vous pouvez voir que vous utilisez Registrar pour importer une série de composants dans le conteneur

        public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
            AutoConfigurationPackages.register(registry, (String[])(new AutoConfigurationPackages.PackageImports(metadata)).getPackageNames().toArray(new String[0]));
        }

alt + F8, vous pouvez voir que tous les composants sous le package où se trouve le programme principal sont importés dans le conteneur 

② @Importer({AutoConfigurationImportSelector.class})

1. Utilisez getAutoConfigurationEntry(annotationMetadata) pour importer par lots certains composants dans le conteneur
2. Call List<String> configurations = getCandidateConfigurations(annotationMetadata, attributes) pour obtenir toutes les classes de configuration qui doivent être importées dans le conteneur
3. Utilisez la fabrique pour load Map<String, List <String>> loadSpringFactories(@Nullable ClassLoader classLoader); obtenir tous les composants
4. Chargez un fichier à partir de l'emplacement META-INF/spring.factories.
    Par défaut, les fichiers spring-boot-autoconfigure-2.3.4.RELEASE.jar dans tous les emplacements META-INF/spring.factories de notre système actuel sont analysés
    Il y a aussi META-INF/spring.factories dans le paquet, et spring-boot est écrit dans le fichier. Toutes les classes de configuration chargées dans le conteneur seront chargées au démarrage

configurations a 133 composants 

3.2. Activer les éléments de configuration automatique selon les besoins

Bien que toutes les configurations automatiques de 133 scènes soient chargées par défaut lors de leur démarrage. xxxxAutoConfiguration
mais selon les règles d'assemblage conditionnel (@Conditional), il sera éventuellement configuré à la demande.

3.3, modifier la configuration par défaut

Ajout de l'analyseur de téléchargement de fichiers au conteneur

		@ConditionalOnMissingBean(name = DispatcherServlet.MULTIPART_RESOLVER_BEAN_NAME) //容器中没有这个名字 multipartResolver 的组件
		public MultipartResolver multipartResolver(MultipartResolver resolver) {
            //给@Bean标注的方法传入了对象参数,这个参数的值就会从容器中找。
            //SpringMVC multipartResolver。防止有些用户配置的文件上传解析器不符合规范
			// Detect if the user has created a MultipartResolver but named it incorrectly
			return resolver;
		}

SpringBoot configure tous les composants en bas par défaut. Mais si l'utilisateur configure lui-même la priorité de l'utilisateur

    @Bean
	@ConditionalOnMissingBean
	public CharacterEncodingFilter characterEncodingFilter() {
    }

3.4. Résumé

  • SpringBoot charge d'abord toutes les classes de configuration automatique xxxxxAutoConfiguration

  • Chaque classe de configuration automatique prend effet selon des conditions, et la valeur par défaut est liée à la valeur spécifiée par le fichier de configuration. Prenez-le à l'intérieur de xxxxProperties. xxxProperties et les fichiers de configuration sont liés

  • La classe de configuration effective assemblera de nombreux composants dans le conteneur

  • Tant qu'il y a ces composants dans le conteneur, il est équivalent à ces fonctions.

  • Paramétrage personnalisé

    • Les utilisateurs remplacent directement les composants sous-jacents par leur propre @Bean

    • L'utilisateur peut modifier la valeur du fichier de configuration obtenu par ce composant.

    xxxxxAutoConfiguration ---> Composant ---> prendre la valeur dans xxxxProperties ----> application.properties

3.5. Meilleures pratiques

  • Introduire des dépendances de scène

  • Voir ce qui est automatiquement configuré (facultatif)

    • Analyse par vous-même, la configuration automatique correspondant à la scène d'introduction prend généralement effet

    • Dans le fichier de configuration, debug=true active le rapport de configuration automatique. Négatif (inefficace)\Positif (efficace)

  • Faut-il le modifier

    • Reportez-vous à la documentation pour modifier les éléments de configuration

    • Ajouter ou remplacer des composants personnalisés

      • @Bean、@Composant。。。

    • Personnalisateur XXXXXPersonnalisateur ;

    • ......

4. Compétences de développement

4.1、Lombok

① Introduire des dépendances

        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
        </dependency>

② Ajouter des notes

@ConfigurationProperties(prefix = "mycar")
@Data //setter和getter方法
@ToString //toString方法
@AllArgsConstructor //全参构造
@NoArgsConstructor //无参构造
@EqualsAndHashCode //equals和hashCode方法
public class Car {

    private String brand;
    private Integer price;

}

4.2、@Slf4j

① Ajouter des annotations et ajouter des journaux dans la méthode du contrôleur

@RestController  // @RestController可以代替@Controller和@ResponseBody
@Slf4j
public class HelloController {
    @Autowired
    Car car;

    @RequestMapping("/car")
    public Car car(){
        return car;
    }

    @RequestMapping("/hello")
    public String handler01(){
        log.info("请求进来了...");
        return "hello,Spring Boot 2!" + "你好";
    }
}

② Accédez à l'adresse correspondante, la sortie de la console est la suivante

 4.3、outils de développement

L'essentiel est de redémarrer, si vous souhaitez charger automatiquement, vous pouvez utiliser JRebel (payant)

① Introduire des dépendances

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-devtools</artifactId>
            <optional>true</optional>
        </dependency>

② Après avoir importé les dépendances et modifié la page, vous pouvez appuyer sur ctrl + F9 (la touche de raccourci pour construire le projet), recompiler, puis actualiser la page Web

4.4, Spring Initailizr (assistant d'initialisation de projet)

① Lors de la création d'un nouveau projet, sélectionnez Spring Initailizr, puis remplissez les cases nécessaires, puis cliquez sur Suivant

 ② Choisissez ce dont vous avez besoin

 ③ Une fois la création terminée, le répertoire est le suivant, les dépendances ont été automatiquement introduites et une classe de programme principale a été créée 

 

Acho que você gosta

Origin blog.csdn.net/Mr_zhangyj/article/details/123852839
Recomendado
Clasificación