译 | 为什么使用 Spring Boot?

但在深入了解SpringBoot之前,我们将快速浏览一下Spring框架,看看SpringBoot试图解决的问题是什么。

Spring 是一个非常流行的基于Java语言的开发框架,此框架用来构建web和企业应用程序。与许多其他仅关注一个领域的框架不同,Spring框架提供了广泛的功能,通过其组合项目满足现代业务需求。

Spring框架提供了以多种方式配置bean的灵活性,例如XML,注解和JavaConfig。随着功能数量的增加,复杂性也会增加,配置Spring应用程序变得乏味且容易出错。

Spring团队创建了Spring Boot来解决配置的复杂性。

但在深入了解SpringBoot之前,我们将快速浏览一下Spring框架,看看SpringBoot试图解决的问题是什么。

在本文中,我们将介绍:

  • Spring框架概述
  • 使用Spring MVC和JPA(Hibernate)的Web应用
  • 快速试用SpringBoot

Spring 框架概述

如果你是 Java 开发人员,那么你多半听说过 Spring 框架,甚至可能在项目中使用过这一框架。Spring 框架主要起源于一个依赖注入容器,但它远不止于此。

Spring 之所以流行,是因为:

  • Spring 的依赖注入方法支持编写可测试的代码
  • 强大且易用的数据库事务管理能力
  • Spring 非常容易与其它 Java 框架集成,如 JPA/Hibernate ORM、Struts、JSF等。
  • 使用前沿的 MVC 框架构建 Web 应用

Spring 除了框架之外,还有很多姊妹项目可以助力构建满足现代业务需求的应用程序:

  • Spring Data:为访问关系型数据库或 NoSQL 数据库提供便利。
  • Spring Batch:强大的批处理框架。
  • Spring Security:为安全应用而生的强健的安全框架。
  • Spring Social:支持整合社区网络,比如 Facebook、Twitter、LinkedIn、Github 等。
  • Spring Integration:企业级集成模型的实现,使用轻量级的消息传递和声明式适配器与其他企业应用集成。

还有不少其他有趣的项目,他们用于解决各种现代应用开发需求。欲知详情,请看 http://spring.io/projects。

Spring 框架一开始提供了基于 XML 的方法来配置 Bean。之后 Spring 引入了基于 XML 的 DSL、注解和 JavaConfig 来配置 Bean 的方法。

让我们快速看看每种配置方式的样子。

基于XML的配置

图0:为什么使用 Spring Boot?

基于注释的配置

图1:为什么使用 Spring Boot?

基于JavaConfig的配置

图2:为什么使用 Spring Boot?

哇… Spring提供了很多方法来做同样的事情,我们甚至可以混合使用这些方法,并在同一个应用程序中同时使用基于JavaConfig和注解的配置方式。

但是没有一种适合所有类型的解决方案。必须根据自己的应用需求选择方法。

好了,现在您已经看到了各种样式的Spring bean配置的例子。

让我们快速浏览一下典型的SpringMVC + JPA / Hibernate Web应用程序配置的样子。

使用Spring MVC和JPA(Hibernate)的Web应用程序

在了解SpringBoot是什么以及提供了什么样的功能之前,让我们先看看典型的Spring Web应用程序配置如何,难点又是什么,其次我们论述SpringBoot将如何解决这些问题。

第1步:配置Maven依赖项

首要的是我们需要配置pom.xml中所需的所有依赖项。

<?xml version="1.0" encoding="UTF-8"?>

<project xmlns="http://maven.apache.org/POM/4.0.0"

    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"

    xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 

                        http://maven.apache.org/maven-v4_0_0.xsd">

    <modelVersion>4.0.0</modelVersion>

    <groupId>com.sivalabs</groupId>

    <artifactId>springmvc-jpa-demo</artifactId>

    <packaging>war</packaging>

    <version>1.0-SNAPSHOT</version>

    <name>springmvc-jpa-demo</name>


    <properties>

        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>

        <maven.compiler.source>1.8</maven.compiler.source>

        <maven.compiler.target>1.8</maven.compiler.target>      

        <failOnMissingWebXml>false</failOnMissingWebXml>

    </properties>


    <dependencies>

        <dependency>

            <groupId>org.springframework</groupId>

            <artifactId>spring-webmvc</artifactId>

            <version>4.2.4.RELEASE</version>

        </dependency>

        <dependency>

            <groupId>org.springframework.data</groupId>

            <artifactId>spring-data-jpa</artifactId>

            <version>1.9.2.RELEASE</version>

        </dependency>

        <dependency>

            <groupId>org.slf4j</groupId>

            <artifactId>jcl-over-slf4j</artifactId>

            <version>1.7.13</version>

        </dependency>

        <dependency>

            <groupId>org.slf4j</groupId>

            <artifactId>slf4j-api</artifactId>

            <version>1.7.13</version>

        </dependency>

        <dependency>

            <groupId>org.slf4j</groupId>

            <artifactId>slf4j-log4j12</artifactId>

            <version>1.7.13</version>

        </dependency>

        <dependency>

            <groupId>log4j</groupId>

            <artifactId>log4j</artifactId>

            <version>1.2.17</version>

        </dependency>

        <dependency>

            <groupId>com.h2database</groupId>

            <artifactId>h2</artifactId>

            <version>1.4.190</version>

        </dependency>

        <dependency>

            <groupId>commons-dbcp</groupId>

            <artifactId>commons-dbcp</artifactId>

            <version>1.4</version>

        </dependency>

        <dependency>

            <groupId>mysql</groupId>

            <artifactId>mysql-connector-java</artifactId>

            <version>5.1.38</version>

        </dependency>

        <dependency>

            <groupId>org.hibernate</groupId>

            <artifactId>hibernate-entitymanager</artifactId>

            <version>4.3.11.Final</version>

        </dependency>

        <dependency>

            <groupId>javax.servlet</groupId>

            <artifactId>javax.servlet-api</artifactId>

            <version>3.1.0</version>

            <scope>provided</scope>

        </dependency>

        <dependency>

            <groupId>org.thymeleaf</groupId>

            <artifactId>thymeleaf-spring4</artifactId>

            <version>2.1.4.RELEASE</version>

        </dependency>

    </dependencies>

</project>

我们已经配置好了所有Maven jar依赖项,包括Spring MVC,Spring Data JPA,JPA / Hibernate,Thymeleaf和Log4j。

第 2 步:使用 JavaConfig 配置 Service/DAO 层的 Bean

@Configuration

@EnableTransactionManagement

@EnableJpaRepositories(basePackages="com.sivalabs.demo")

@PropertySource(value = { "classpath:application.properties" })

public class AppConfig 

{

    @Autowired

    private Environment env;

 

    @Bean

    public static PropertySourcesPlaceholderConfigurer placeHolderConfigurer()

    {

        return new PropertySourcesPlaceholderConfigurer();

    }

 

    @Value("${init-db:false}")

    private String initDatabase;

 

    @Bean

    public PlatformTransactionManager transactionManager()

    {

        EntityManagerFactory factory = entityManagerFactory().getObject();

        return new JpaTransactionManager(factory);

    }

 

    @Bean

    public LocalContainerEntityManagerFactoryBean entityManagerFactory()

    {

        LocalContainerEntityManagerFactoryBean factory = new LocalContainerEntityManagerFactoryBean();

 

        HibernateJpaVendorAdapter vendorAdapter = new HibernateJpaVendorAdapter();

        vendorAdapter.setGenerateDdl(Boolean.TRUE);

        vendorAdapter.setShowSql(Boolean.TRUE);

 

        factory.setDataSource(dataSource());

        factory.setJpaVendorAdapter(vendorAdapter);

        factory.setPackagesToScan("com.sivalabs.demo");

 

        Properties jpaProperties = new Properties();

        jpaProperties.put("hibernate.hbm2ddl.auto", env.getProperty("hibernate.hbm2ddl.auto"));

        factory.setJpaProperties(jpaProperties);

 

        factory.afterPropertiesSet();

        factory.setLoadTimeWeaver(new InstrumentationLoadTimeWeaver());

        return factory;

    }

 

    @Bean

    public HibernateExceptionTranslator hibernateExceptionTranslator()

    {

        return new HibernateExceptionTranslator();

    }

 

    @Bean

    public DataSource dataSource()

    {

        BasicDataSource dataSource = new BasicDataSource();

        dataSource.setDriverClassName(env.getProperty("jdbc.driverClassName"));

        dataSource.setUrl(env.getProperty("jdbc.url"));

        dataSource.setUsername(env.getProperty("jdbc.username"));

        dataSource.setPassword(env.getProperty("jdbc.password"));

        return dataSource;

    }

 

    @Bean

    public DataSourceInitializer dataSourceInitializer(DataSource dataSource) 

    {

        DataSourceInitializer dataSourceInitializer = new DataSourceInitializer();

        dataSourceInitializer.setDataSource(dataSource);

        ResourceDatabasePopulator databasePopulator = new ResourceDatabasePopulator();

        databasePopulator.addScript(new ClassPathResource("data.sql"));

        dataSourceInitializer.setDatabasePopulator(databasePopulator);

        dataSourceInitializer.setEnabled(Boolean.parseBoolean(initDatabase));

        return dataSourceInitializer;

    }   

}

在 AppConfig.java 所描述的配置类中,我们做了下面的事情:

  • 使用 @Configuration 注解使之成为 Spring 配置类。
  • 使用 @EnableTransactionManagement 注解允许基于注解的事务管理。
  • 配置 @EnableJpaRepositories 指示在哪里寻找 Spring Data JPA 库。
  • Configured PropertyPlaceHolder bean u使用 @PropertySource 注解和 PropertySourcesPlaceholderConfigurer 定义来配置 PropertyPlaceHolder Bean。配置属性来自 application.properties 文件。
  • 为 DataSource、JPA EntityManagerFactory 和 JpaTransactionManager 定义 Bean。
  • 配置 DataSourceInitializer Bean,使之在应用启动时执行 data.sql 脚本,初始化数据库。

我们需要在 application.properties 中配置占位属性值:

图3:为什么使用 Spring Boot?

创建简单的 SQL 脚本,data.sql,将示例数据填入 USER 表中:

图4:为什么使用 Spring Boot?

创建 log4j.properties 文件,在其中写一些基础配置:

图5:为什么使用 Spring Boot?

第3步:配置Spring MVC Web层Beans

我们必须配置Thymeleaf ViewResolver,静态ResourceHandlers,适用于国际化的MessageSource,等等。

@Configuration

@ComponentScan(basePackages = { "com.sivalabs.demo"}) 

@EnableWebMvc

public class WebMvcConfig extends WebMvcConfigurerAdapter

{

    @Bean

    public TemplateResolver templateResolver() {

        TemplateResolver templateResolver = new ServletContextTemplateResolver();

        templateResolver.setPrefix("/WEB-INF/views/");

        templateResolver.setSuffix(".html");

        templateResolver.setTemplateMode("HTML5");

        templateResolver.setCacheable(false);

        return templateResolver;

    }

 

    @Bean

    public SpringTemplateEngine templateEngine() {

        SpringTemplateEngine templateEngine = new SpringTemplateEngine();

        templateEngine.setTemplateResolver(templateResolver());

        return templateEngine;

    }

 

    @Bean

    public ThymeleafViewResolver viewResolver() {

        ThymeleafViewResolver thymeleafViewResolver = new ThymeleafViewResolver();

        thymeleafViewResolver.setTemplateEngine(templateEngine());

        thymeleafViewResolver.setCharacterEncoding("UTF-8");

        return thymeleafViewResolver;

    }

 

    @Override

    public void addResourceHandlers(ResourceHandlerRegistry registry)

    {

        registry.addResourceHandler("/resources/**").addResourceLocations("/resources/");

    }

 

    @Override

    public void configureDefaultServletHandling(DefaultServletHandlerConfigurer configurer)

    {

        configurer.enable();

    }

 

    @Bean(name = "messageSource")

    public MessageSource configureMessageSource()

    {

        ReloadableResourceBundleMessageSource messageSource = new ReloadableResourceBundleMessageSource();

        messageSource.setBasename("classpath:messages");

        messageSource.setCacheSeconds(5);

        messageSource.setDefaultEncoding("UTF-8");

        return messageSource;

    }

}

在我们的WebMvcConfig.java配置类中我们完成以下操作:

  • 使用@Configuration注解将其标记为Spring配置类。
  • 使用@EnableWebMvc启用基于注解的Spring MVC配置,
  • 通过注册TemplateResolverSpringTemplateEngineThymeleafViewResolver Bean来配置Thymeleaf ViewResolver。
  • 注册ResourceHandlers Bean表明URI/resources/**静态资源请求将由本地/resources/目录提供服务。
  • 配置MessageSource Bean通过ResourceBundle加载classpath下的messages-{country-code}.properties国际化信息。

目前我们没有要配置的信息,所以在src/main/resources文件夹下创建一个空的messages.properties文件。

第4步:注册Spring MVC FrontController Servlet DispatcherServlet

在Servlet 3.x规范之前我们必须在web.xml注册Servlets/Filters。自从Servlet 3.x规范出台我们能够使用ServletContainerInitializer以编程方式注册Servlets/Filters。

Spring MVC提供了一个方便的类AbstractAnnotationConfigDispatcherServletInitializer用于注册DispatcherServlet.

public class SpringWebAppInitializer extends AbstractAnnotationConfigDispatcherServletInitializer

{

 

    @Override

    protected Class<?>[] getRootConfigClasses()

    {

        return new Class<?>[] { AppConfig.class};

    }

 

    @Override

    protected Class<?>[] getServletConfigClasses()

    {

        return new Class<?>[] { WebMvcConfig.class };

    }

 

    @Override

    protected String[] getServletMappings()

    {

        return new String[] { "/" };

    }

 

    @Override

    protected Filter[] getServletFilters() {

       return new Filter[]{ new OpenEntityManagerInViewFilter() };

    }

}

SpringWebAppInitializer.java中我们已经完成了以下的事情:

  • 配置了AppConfig.class 作为 RootConfirationClasses ,它将成为包含所有子上下文(DispatcherServlet)共享的bean定义的父ApplicationContext
  • 配置WebMvcConfig.class作为ServletConfigClasses,它是包括WebMvc bean定义的子ApplicationContext
  • 配置ServletMapping为”/”,让DispatcherServlet处理所有请求。
  • 注册OpenEntityManagerInViewFilter过滤器,用于展开视图时懒加载JPA实体集合。

第五步:创建JPA实体和Spring Data JPA repository
创建JPA实体User.java和User实体对应的Spring Data JPA repository。

@Entity

public class User

{

    @Id @GeneratedValue(strategy=GenerationType.AUTO)

    private Integer id;

    private String name;

 

    //setters and getters

}

public interface UserRepository extends JpaRepository<User, Integer>

{

}

第六步:创建Spring MVC控制器
创建SpringMVC控制器来处理”/”URL请求并返回用户列表。

@Controller

public class HomeController

{

    @Autowired UserRepository userRepo;

 

    @RequestMapping("/")

    public String home(Model model)

    {

        model.addAttribute("users", userRepo.findAll());

        return "index";

    }

}

第七步:创建Thymeleaf视图:/WEB-INF/views/index.html来展示用户列表

<!DOCTYPE html>

<html xmlns="http://www.w3.org/1999/xhtml"

      xmlns:th="http://www.thymeleaf.org">

<head>

<meta charset="utf-8"/>

<title>Home</title>

</head>

<body>

    <table>

        <thead>

            <tr>

                <th>Id</th>

                <th>Name</th>

            </tr>

        </thead>

        <tbody>

            <tr th:each="user : ${users}">

                <td th:text="${user.id}">Id</td>

                <td th:text="${user.name}">Name</td>

            </tr>

        </tbody>

    </table>

</body>

</html>

我们准备好运行程序啦。但在此之前,我们需要下载和配置诸如Tomcat或者Jetty或者Wilddfly等容器。

你可以下载Tomcat 8并在你喜欢使用的IDE中配置并运行。然后打开浏览器访问http://localhost:8080/springmvcjpa-demo。你可以看到user的详细列表。
耶…我们做到了。

但是等一下~这不是花了非常多的工作量才能做一个从数据库中读取并显示用户列表信息吗?

我们公正公平来看问题。所有的这种配置并不只是一个用户案例。这个配置也是应用程序其它部分的基础。

回过头来,如果你只是想快速获得和运行一个应用来说这是在是太麻烦太复杂了。

另一个问题是,如果你想开发另一个有类似技术栈的 SpringMVC 应用吗?

好的,你可能为了拷贝粘贴这些配置而感到苦恼。是吗?提出一个记忆深刻的问题:如果你要做类似的事情重复一遍又一遍,那么你是不是想寻找一个能自动处理的方式解决问题。

各种分散的配置写了一遍又一遍,你会想到这里会有一些其他的麻烦问题吗?

好的,在这让我把这些我能想到的问题列出。

  • 你需要注意所有的类库的兼容性与 Spring 版本搭配和配置。
  • 在配置里面有 95% 的时间我们都是在配置 DataSourceEntityManagerFactoryTransactionManger 之类的实体。但如果 Spring 能够自动的为我们完成这些配置岂不是更好啊。
  • 类似的配置 SpringMVC 实体类比如 ViewResolverMessageSource 等又需要花更多的工作时间了。

想一想,要怎样才能让 Spring 能够自动化的完成配置实体?怎么样你才可以快速简单的使用基于可定制的属性完成自动的配置呢?

比如,将 DispacherServlet 的 url-pattern 映射 “/” 映射到 “/app/”。将 “/WEB-INF/views” 目录 替换为在 “/WEB-INF/templates/” 目录加入 Thymeleaf 视图。

所以基本上,你希望 Spring 自动执行一些操作,但是又能够提供更简单的方式灵活的提供重写默认配置?

好的,现在你正在走进 Spring Boot 的世界在里面你所期待已久的那些东西都已经有了!!!

快速上手品味 Spring Boot

欢迎来到 Spring Boot 世界!Spring Boot 并不是需要你完全地关注和掌握很多东西。它可以默认的完成自动化的配置但是你又可以按自己的需要去重新配置任意配置。

相比我更喜欢用一个例子来说明而不是更多的解析。

所以让我们现在开始用 Spring Boot 来快速构建一个应用。

Step 1:创建一个 Spring Boot 的 Maven 项目

创建一个 Maven 项目和(在 pom.xml 里面)配置项目 jar 包依赖如下:

<?xml version="1.0" encoding="UTF-8"?>

<project xmlns="http://maven.apache.org/POM/4.0.0"

        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"

        xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 

                        http://maven.apache.org/maven-v4_0_0.xsd">

    <modelVersion>4.0.0</modelVersion>

    <groupId>com.sivalabs</groupId>

    <artifactId>hello-springboot</artifactId>

    <packaging>jar</packaging>

    <version>1.0-SNAPSHOT</version>

    <name>hello-springboot</name>

 

    <parent>

        <groupId>org.springframework.boot</groupId>

        <artifactId>spring-boot-starter-parent</artifactId>

        <version>1.3.2.RELEASE</version>

    </parent>

 

    <properties>

        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>

        <java.version>1.8</java.version>

    </properties>

 

 

    <dependencies>

        <dependency>

            <groupId>org.springframework.boot</groupId>

            <artifactId>spring-boot-starter-test</artifactId>

        </dependency>

        <dependency>

            <groupId>org.springframework.boot</groupId>

            <artifactId>spring-boot-starter-data-jpa</artifactId>

        </dependency>

        <dependency>

            <groupId>org.springframework.boot</groupId>

            <artifactId>spring-boot-starter-web</artifactId>

        </dependency>

        <dependency>

            <groupId>org.springframework.boot</groupId>

            <artifactId>spring-boot-starter-thymeleaf</artifactId>

        </dependency>

        <dependency>

            <groupId>org.springframework.boot</groupId>

            <artifactId>spring-boot-devtools</artifactId>

        </dependency>

        <dependency>

            <groupId>mysql</groupId>

            <artifactId>mysql-connector-java</artifactId>

        </dependency>

    </dependencies>

</project>

哇原来 pom.xml 的配置这么少感觉简单多了!

Step 2:在 application.properties 中配置数据源或JPA 属性配置如下:

spring.datasource.driver-class-name=com.mysql.jdbc.Driver

spring.datasource.url=jdbc:mysql://localhost:3306/test

spring.datasource.username=root

spring.datasource.password=admin

spring.datasource.initialize=true

spring.jpa.hibernate.ddl-auto=update

spring.jpa.show-sql=true

你可以拷贝一些类似 data.sql 的文件到 src/main/resources 目录中。

Step 3:创建一些 JAP 实体类和实体类所需要的 Spring Data JAP Repository 接口

在 springmvc-jpa-demo 应用项目中创建 User.javaUserRepository.java 和 HomeController.java 之类所需的东西。

Step 4:用 Thymeleaf 模板语言创建一个显示用户列表的视图

在 springmvc-jap-demo 应用拷贝 /WEB-INF/views/index.html 到我们的新项目的 src/main/resources/templates 目录中。

Step 5:创建一个 SpringBoot 启动入口实体的类

创建一个有 main 函数的 Java 类 Application.java 如下:

@SpringBootApplication

public class Application

{

    public static void main(String[] args)

    {

        SpringApplication.run(Application.class, args);

    }

}

现在用 Java Application 的方式开始启动运行 Application.java 之后打开浏览器访问 http://localhost:8080/

你就可以在页面的表格上看到用户列表的显示。这感觉实在太酷了!!!

OK 好的,我想你会大声说”这里边到底发生了做了些什么???”。

让我说明下刚才发生了什么

1. 轻松地依赖管理

  • 首要察觉一点是我们用的是一些名为spring-boot-starter-*.的依赖项。要记得我说过 “95%的时间里用的配置相同”。因此当默认添加springboot-starter-web依赖项时,它将在开发Spring-webmvcjackson-jsonvalidation-apitomcatSpring MVC应用程序中提取所有常用库。
  • 我们添加了spring-boot-starter-data-jpa依赖项。这会拉取所有spring-data-jpa依赖项并添加Hibernate库,因为大多数应用程序都把Hibernate作为JPA实施。

2. 自动配置

  • spring-boot-starter-web不仅添加了所有这些库,还配置了常见的bean,例如DispatcherServletResourceHandlersMessageSourcebean,具有合理的缺省值。
  • 我们还添加了spring-boot-starter-Thymeleaf,它不仅添加了Thymeleaf库依赖项,还自动配置了ThymeleafViewResolver bean
  • 我们并没有定义任何的DataSourceEntityManagerFactoryTransactionManageretc bean,但是它们会自动地创建。如何?如果我们的类路径中有任何像H2或是HSQL的内存数据库驱动程序,那么SpringBoot将自动创建一个内存中的DataSource,然后自动注册EntityManagerFactory,以及拥有合理的缺省值TransactionManager bean。但我们在用MySQL,所以我们需要提供明确的MySQL连接细节。我们在application.propertiesfile中配置了这些MySQL连接细节,并且SpringBoot使用了这些属性来创建一个DataSource

3.嵌入式Servlet容器支持

最重要也是最惊奇的是我们创建了一个简单的Java类,注释了一些神奇的注解@SpringApplication,它有一个main方法,通过运行main,我们可以运行应用程序并在http:// localhost:8080 /上访问它。

servlet容器从何而来?

我们已经添加了spring-boot-starter-web,它自动拉出spring-boot-starter-tomcat,当我们运行main()方法时,它为嵌入式容器启动了tomcat,这样我们就不必在任何外部安装的tomcat服务器上部署我们的应用程序了。

另外而言,你注意到我们在pom.xml中的打包类型是’jar’而不是’war’。 极其有趣!

好吧,但如果我想使用Jetty服务器而不用tomcat呢?

很简单,从spring-boot-starter-web中将spring-bootstarter-tomcat排除,只留有spring-boot-starter-jetty。

就是这样。

但是,这看起来很神奇!我可以想象你在想什么。你觉得SpringBoot看起来很酷,它会自动为我做很多事情。但我仍然没有完全理解它是如何真正在幕后工作的。对么?

我明白。观看魔术表演通常很有趣,但在软件开发中却没有。别担心,未来文章中我们将会查看这些内容,并详细解释幕后发生的事情。但是现在我不想在此文中将所有内容都转发给你,从而压倒你。

概要

在本文中,我们快速概述了各种Spring配置样式,并了解了配置Spring应用程序的复杂性。此外,我们通过创建一个简单的Web应用程序快速浏览了SpringBoot。

在下一篇文章中,我们将深入探讨SpringBoot并了解它是如何工作的。

阅读余下内容

发表评论

电子邮件地址不会被公开。 必填项已用*标注


京ICP备12002735号