<span id="ubbei"><video id="ubbei"></video></span>
<span id="ubbei"></span>
<span id="ubbei"><video id="ubbei"></video></span>
<strike id="ubbei"><video id="ubbei"></video></strike><th id="ubbei"></th>
<span id="ubbei"><video id="ubbei"></video></span><span id="ubbei"></span><span id="ubbei"><video id="ubbei"></video></span>
<th id="ubbei"><video id="ubbei"><span id="ubbei"></span></video></th>
<span id="ubbei"></span>
<th id="ubbei"><video id="ubbei"><span id="ubbei"></span></video></th><strike id="ubbei"><dl id="ubbei"><ruby id="ubbei"></ruby></dl></strike>
<span id="ubbei"></span>
<th id="ubbei"></th>
<span id="ubbei"></span>
<th id="ubbei"></th>
<span id="ubbei"></span>
<span id="ubbei"></span>
<span id="ubbei"></span>
<span id="ubbei"><video id="ubbei"><strike id="ubbei"></strike></video></span>
<strike id="ubbei"><video id="ubbei"></video></strike>

  • <output id="ubbei"></output>
          1. <li id="ubbei"><s id="ubbei"><strong id="ubbei"></strong></s></li>
          2. Spring Cloud 快速入门

            课程介绍

            Spring Cloud 是一套完整的微服务解决方案,基于 Spring Boot 框架,准确的说,它不是一个框架,而是一个大的容器,它将市面上较好的微服务框架集成进来,从而简化了开发者的代码量。

            本课程由浅入深带领大家一步步攻克 Spring Cloud 各大模块,接着通过一个实例带领大家了解大型分布式微服务架构的搭建过程,最后深入源码?#30001;?#23545;它的了解。

            本课程共分为四个部分:

            第一部分(第1-3课),初识 Spring Boot,掌握 Spring Boot 基础知识,为后续入门 Spring Cloud 打好基础 。

            第二部分(第4-13课),Spring Cloud 入门篇,主要介绍 Spring Cloud 常用模块,包括服务发现、服务注册、配置中心、链路追踪、异常处理等。

            第三部分(第14-18课),Spring Cloud 进阶篇,介绍大型分布?#36739;?#32479;中事务处理、线程安全等问题,并以一个实例项目手把手教大家搭建完整的微服务系统。

            第四部分(第19-20课),Spring Cloud 高级篇,解析 Spring Cloud 源码,并讲解如何部署基于 Spring Cloud 的大型分布?#36739;?#32479;。

            作者介绍

            李熠,从事 Java 后端开发6年,现任职某大型互联网公司,担任 Java 高级开发工程师,CSDN 博客专家,全栈工程师。

            课程内容

            ?#32423;粒?#20160;么是 Spring Cloud 及应用现状

            Spring Cloud 是什么?

            在学习本课程之前,读者有必要先了解一下 Spring Cloud。

            Spring Cloud 是一系列框架的有序集合,它利用 Spring Boot 的开发便利性简化了分布?#36739;?#32479;的开发,比如服务发现、服务网关、服务路由、链路追踪等。Spring Cloud 并不重复造轮子,而是将市面上开发得比较好的模块集成进去,进行封装,从而减少了各模块的开发成本。换句话说:Spring Cloud 提供了构建分布?#36739;?#32479;所需的“全家桶”。

            Spring Cloud 现状

            目前,国内使用 Spring Cloud 技术的公司并不多见,不是因为 Spring Cloud 不好,主要原因有以?#24405;?#28857;:

            1. Spring Cloud 中文文档较少,出现问题网上没有太多的解决方案。
            2. 国内创业型公司技术老大大多是阿里系员工,而阿里系多采用 Dubbo 来构建微服务架构。
            3. 大型公司基本?#21152;?#33258;己的分布式解决方案,而中小型公司的架构很多用不上微服务,所以没有采用 Spring Cloud 的必要性。

            但是,微服务架构是一个趋势,而 Spring Cloud 是微服务解决方案的佼佼者,这也是作者写本系列课程的意义所在。

            Spring Cloud 优缺点

            其主要优点有:

            1. 集大成者,Spring Cloud 包含了微服务架构的方方面面。
            2. ?#32423;?#20248;于配置,基于注解,没有配置文件。
            3. 轻量级组件,Spring Cloud 整合的组件大多比较轻量级,且都是各自领域的佼佼者。
            4. 开发简便,Spring Cloud 对各个组件进行了大量的封装,从而简化了开发。
            5. 开发灵活,Spring Cloud 的组件都是解耦的,开发人员可以灵活按需选择组件。

            接下来,我们看下它的缺点:

            1. 项目结构复杂,每一个组件或者每一个服务都需要创建一个项目。
            2. 部署门槛高,项目部署需要配合 Docker 等容器技术进行集群部署,而要想深入了解 Docker,学习成本高。

            Spring Cloud 的优势是显而?#20934;?#30340;。因此对于想研?#35838;?#26381;务架构的同学来说,学习 Spring Cloud 是一个不错的选择。

            Spring Cloud 和 Dubbo 对比

            Dubbo 只是实现了服务治理,而 Spring Cloud 实现了微服务架构的方方面面,服务治理只是其中的一个方面。下面通过一张图对其进行比较:

            可以看出,Spring Cloud 比较全面,而 Dubbo 由于只实现了服务治理,需要集成其他模块,需要单独引入,增加?#25628;?#20064;成本?#22270;?#25104;成本。

            Spring Cloud 学习

            Spring Cloud 基于 Spring Boot,因此在研究 Spring Cloud 之前,本课程会首先介绍 Spring Boot 的用法,方便后续 Spring Cloud 的学习。

            本课程不会讲解 SpringMVC 的用法,因?#25628;?#20064;本课程需要读者对 Spring 及 SpringMVC 有过研究。

            本课程共分为四个部分:

            • 第一部分初识 Spring Boot,掌握 Spring Boot 基础知识,为后续入门 Spring Cloud 打好基础 。

            • 第二部分 Spring Cloud 入门篇,主要介绍 Spring Cloud 常用模块,包括服务发现、服务注册、配置中心、链路追踪、异常处理等。

            • 第三部分 Spring Cloud 进阶篇,介绍大型分布?#36739;?#32479;中事务处理、线程安全等问题,并以一个实例项目手把手教大家搭建完整的微服务系统。

            • 第四部分 Spring Cloud 高级篇,解析 Spring Cloud 源码,并讲解如何部署基于 Spring Cloud 的大型分布?#36739;?#32479;。

            本课程的所有示例代码均可在:https://github.com/lynnlovemin/SpringCloudLesson 下载。

            第01课:Spring Boot 入门

            什么是 Spring Boot

            Spring Boot 是由 Pivotal 团队提供的基于 Spring 的全新框架,其设计目的是为了简化 Spring 应用的搭建和开发过程。该框架遵循“?#32423;?#22823;于配置”原则,采用特定的方式进行配置,从而使开发者无需定义大量的 XML 配置。通过这种方式,Spring Boot 致力于在蓬勃发展的快速应用开发领域成为领导者。

            Spring Boot 并不重复造轮子,而?#20197;?#21407;有 Spring 的框架基础上封装了一层,并且它集成了一些类库,用于简化开发。换句话说,Spring Boot 就是一个大容器。

            下面几张图展示了 官网 上提供的 Spring Boot 所集成的所有类库:

            Spring Boot 官方推荐使用 Maven 或 Gradle 来构建项目,本教程采用 Maven。

            第一个 Spring Boot 项目

            大多数教程都是以 Hello World 入门,本教程也不例外,接下来,我们就?#21019;?#24314;一个最简单的 Spring Boot 项目。

            首先创建一个 Maven 工程,请看下图:

            然后在 pom.xml 加入 Spring Boot 依?#25285;?/p>

            <parent>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-parent</artifactId>
            <version>2.0.1.RELEASE</version>
            </parent>
            <dependencies>
            <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
            </dependency>
            </dependencies>

            创建一个 Controller 类 HelloController:

            import org.springframework.boot.SpringApplication;
            import org.springframework.boot.autoconfigure.SpringBootApplication;
            import org.springframework.web.bind.annotation.RequestMapping;
            import org.springframework.web.bind.annotation.RestController;
            
            @RestController
            @SpringBootApplication
            public class HelloController {
            
            @RequestMapping("hello")
            String hello() {
            return "Hello World!";
            }
            
            public static void main(String[] args) {
            SpringApplication.run(HelloController.class, args);
            }
            }

            运行 main 方法,Spring Boot 默认会启动自带的 Tomcat 容器,启动成功后,浏览器访问:http://localhost:8080/hello,则会看?#36739;?#22270;:

            我们可以注意到,没有写任何的配置文件,更没有显示的使用任何容器,它是如何启动程序的呢,具体原理我将在第3课中具体分析。

            这里我们可以初步分析出,Spring Boot 提供了默认的配置,在启动类里加入 @SpringBootApplication 注解,则这个类就是整个应用程序的启动类。

            properties 和 yaml

            Spring Boot 整个应用程序只有一个配置文件,那就是 .properties.yml 文件。但是,在前面的示例代码中,我们并没有看到?#38376;?#32622;文件,那是因为 Spring Boot 对每个配置项?#21152;心现怠5比唬?#25105;?#19988;部?#20197;添加配置文件,用以覆盖其默?#29616;担?#36825;里以 .properties 文件为例,首先在 resources 下新建一个名为 application.properties(注意:文件名必须是 application)的文件,键入内容为:

            server.port=8081
            server.servlet.context-path=/api

            并且启动 main 方法,这时程序请求地址则变成了:http://localhost:8081/api/hello。

            Spring Boot 支持 properties 和 yaml 两种格式的文件,文件名分别对应 application.properties 和 application.yml,下面贴出 yaml 文件格式供大家参考:

            server:
                port: 8080
                servlet:
                    context-path: /api

            可以看出 properties 是以逗号隔开,而 yaml 则换行+ tab 隔开,这里需要注意的是冒号后面必须空格,否则会报错。yaml 文件格式更清晰,更易读,这里作者建议大家都采用 yaml 文件来配置。

            本教程的所有配置均采用 yaml 文件。

            打包、运行

            Spring Boot 打包分为 war 和 jar两个格式,下面将分别演示如何构建这两种格式的启动包。

            在 pom.xml 加入如下配置:

            <packaging>war</packaging>
            <build>
            <finalName>index</finalName>
            <resources>
            <resource>
            <directory>src/main/resources</directory>
            <filtering>true</filtering>
            </resource>
            </resources>
            <plugins>
            <plugin>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
            <plugin>
            <artifactId>maven-resources-plugin</artifactId>
            <version>2.5</version>
            <configuration>
            <encoding>UTF-8</encoding>
            </configuration>
            </plugin>
            <plugin>
            <groupId>org.apache.maven.plugins</groupId>
            <artifactId>maven-surefire-plugin</artifactId>
            <version>2.18.1</version>
            <configuration>
            <skipTests>true</skipTests>
            </configuration>
            </plugin>
            </plugins>
            </build>

            这个时候运行 mvn package 就会生成 war 包,然后放到 Tomcat 当中就能启动,但是我们单纯这样配置在 Tomcat 是不能成功运行的,会报错,需要通过编码指定 Tomcat 容器启动,修改 HelloController 类:

            @RestController
            @SpringBootApplication
            public class HelloController extends SpringBootServletInitializer{
            
            @RequestMapping("hello")
            String hello() {
            return "Hello World!";
            }
            
            public static void main(String[] args) {
            SpringApplication.run(HelloController.class, args);
            }
            @Override
            protected SpringApplicationBuilder configure(SpringApplicationBuilder application) {
            return application.sources(Application.class);
            }
            
            }

            这时再打包放到 Tomcat,启动就不会报错了。

            接下来我们继续看如果达成 jar 包,在 pom.xml 加入如下配置:

            <packaging>jar</packaging>
            <build>
            <finalName>api</finalName>
            <resources>
            <resource>
            <directory>src/main/resources</directory>
            <filtering>true</filtering>
            </resource>
            </resources>
            <plugins>
            <plugin>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-maven-plugin</artifactId>
            <configuration>
            <fork>true</fork>
            <mainClass>com.lynn.yiyi.Application</mainClass>
            </configuration>
            <executions>
            <execution>
            <goals>
            <goal>repackage</goal>
            </goals>
            </execution>
            </executions>
            </plugin>
            <plugin>
            <artifactId>maven-resources-plugin</artifactId>
            <version>2.5</version>
            <configuration>
            <encoding>UTF-8</encoding>
            <useDefaultDelimiters>true</useDefaultDelimiters>
            </configuration>
            </plugin>
            <plugin>
            <groupId>org.apache.maven.plugins</groupId>
            <artifactId>maven-surefire-plugin</artifactId>
            <version>2.18.1</version>
            <configuration>
            <skipTests>true</skipTests>
            </configuration>
            </plugin>
            <plugin>
            <groupId>org.apache.maven.plugins</groupId>
            <artifactId>maven-compiler-plugin</artifactId>
            <version>2.3.2</version>
            <configuration>
            <source>1.8</source>
            <target>1.8</target>
            </configuration>
            </plugin>
            </plugins>
            </build>

            然后通过 mvn package 打包,最后通过 java 命令启动:

            java -jar api.jar

            这样,最简单的 Spring Boot 就完成了,但是对于一个大型项目,这是?#23545;?#19981;够的,Spring Boot 的详?#35206;?#20316;可以?#25569;?官网

            下面展示一个最基础的企业级 Spring Boot 项目的结构:

            其中,Application.java 是程序的启动类,Startup.java 是程序启动完成前执行的类,WebConfig.java 是配置类,所有 bean 注入、配置、拦截器注入等都放在这个类里面。

            以上实例只是最简单的 Spring Boot 项目入门实例,后面会深入研究 Spring Boot。

            第02课:Spring Boot 进阶

            上一篇带领大家初步了解了如?#38382;?#29992; Spring Boot 搭建框架,通过 Spring Boot 和传统的 SpringMVC 架构的对比,我们清晰地发现 Spring Boot 的好处,它使我们的代码更加简单,结构更加清晰。

            从这一篇开始,我将带领大家更?#30001;?#20837;的认识 Spring Boot,将 Spring Boot 涉及到东西进行拆解,从而了解 Spring Boot 的方方面面。学完本文后,读者可以基于 Spring Boot 搭建更加复杂的系统框架。

            我们知道,Spring Boot 是一个大容器,它将很多第三方框架都进行了集成,我们在实际项目中用到哪个模块,再引入哪个模块。比如我们项目中的持久化框架用 MyBatis,则在 pom.xml 添加如下依?#25285;?/p>

            <dependency>
                        <groupId>org.mybatis.spring.boot</groupId>
                        <artifactId>mybatis-spring-boot-starter</artifactId>
                        <version>1.1.1</version>
                    </dependency>
                    <dependency>
                        <groupId>mysql</groupId>
                        <artifactId>mysql-connector-java</artifactId>
                        <version>5.1.40</version>
                    </dependency>

            yaml/properties 文件

            我们知道整个 Spring Boot 项目只有一个配置文件,那就是 application.yml,Spring Boot 在启动时,就会从 application.yml 中读取配置信息,并加载到内存中。上一篇我们只是?#33268;?#30340;列举了几个配置项,其实 Spring Boot 的配置项是很多的,本文我们将学习在实际项目中常用的配置项(注:为了方便说明,配置项均以 properties 文件的格式写出,后续的实际配置都会写成 yaml 格式)。

            配置项 说明 举例
            server.port 应用程序启动端口 server.port=8080,定义应用程序启动端口为8080
            server.context-path 应用程序上下文 server.port=/api,则访?#23454;?#22336;为:http://ip:port/api
            spring.http.multipart.maxFileSize 最大文件上传大小,-1为不限制 spring.http.multipart.maxFileSize=-1
            spring.jpa.database 数据库类型 spring.jpa.database=MYSQL,指定数据库为mysql
            spring.jpa.properties.hibernate.dialect hql方言 spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.MySQL5Dialect
            spring.datasource.url 数据库连接?#22336;?#20018; spring.datasource.url=jdbc:mysql://localhost:3306/database?useUnicode=true&characterEncoding=UTF-8&useSSL=true
            spring.datasource.username 数据库用户名 spring.datasource.username=root
            spring.datasource.password 数据库密码 spring.datasource.password=root
            spring.datasource.driverClassName 数据库驱动 spring.datasource.driverClassName=com.mysql.jdbc.Driver
            spring.jpa.showSql 控制台是否打印sql语句 spring.jpa.showSql=true

            下面是?#20063;?#19982;的某个项目的 application.yml 配置文件内容:

            server:
              port: 8080
              context-path: /api
              tomcat:
                max-threads: 1000
                min-spare-threads: 50
              connection-timeout: 5000
            spring:
              profiles:
                active: dev
              http:
                multipart:
                  maxFileSize: -1
              datasource:
                url: jdbc:mysql://localhost:3306/database?useUnicode=true&characterEncoding=UTF-8&useSSL=true
                username: root
                password: root
                driverClassName: com.mysql.jdbc.Driver
              jpa:
                database: MYSQL
                showSql: true
                hibernate:
                  namingStrategy: org.hibernate.cfg.ImprovedNamingStrategy
                properties:
                  hibernate:
                    dialect: org.hibernate.dialect.MySQL5Dialect
            mybatis:
              configuration:
                 #配置项:开启下划线到驼峰的自动转换. 作用:将数据库字段根据驼峰规则自动注入到对象属性。
                 map-underscore-to-camel-case: true

            以上列举了常用的配置项,所有配置项信息都可以在 官网 中?#19994;劍?#26412;课程就不一一列举了。

            多环境配置

            在一个企业级系统中,我们可能会遇到这样一个问题:开发时使用开发环?#24120;?#27979;试时使用测试环?#24120;?#19978;线时使用生产环境。每个环境的配置都可能不一样,比如开发环境的数据库是本地地址,而测试环境的数据库是测试地址。那我们在打包的时候如何生成不同环境的包呢?

            这里的解决方案有很多:

            1. ?#30475;?#32534;译之前手动把所有配置信息修改成当前运行的环境信息。这种方式导致?#30475;?#37117;需要修?#27169;?#30456;当麻?#24120;?#20063;容易出错。
            2. 利用 Maven,在 pom.xml 里配置多个环?#24120;看?#32534;译之前将 settings.xml 里面修改成当前要编译的环境 ID。这种方式会事先设置好所有环?#24120;?#32570;点就是?#30475;?#20063;需要手动指定环?#24120;?#22914;果环?#25345;?#23450;错误,发布时是不知道的。
            3. 第三种方案就是本文重点介绍的,也是作者强烈推荐的方式。

            首先,创建 application.yml 文件,在里面添加如下内容:

            spring:
              profiles:
                active: dev

            含义是指定当前项目的默认环境为 dev,即项?#31185;?#21160;时如果不指定任何环?#24120;琒pring Boot 会自动从 dev 环境文件中读取配置信息。我们可以将不同环境都共同的配置信息写到这个文件中。

            然后创建多环境配置文件,文件名的格式为:application-{profile}.yml,其中,{profile} 替换为环境名字,如 application-dev.yml,我们可以在其中添加当前环境的配置信息,如添加数据源:

            spring:
              datasource:
                url: jdbc:mysql://localhost:3306/database?useUnicode=true&characterEncoding=UTF-8&useSSL=true
                username: root
                password: root
                driverClassName: com.mysql.jdbc.Driver

            这样,我们就实现了多环境的配置,?#30475;?#32534;译打包我们无需修改任何东西,编译为 jar 文件后,运行命令:

            java -jar api.jar --spring.profiles.active=dev

            其中 --spring.profiles.active 就是我们要指定的环境。

            常用注解

            我们知道,Spring Boot 主要采用注解的方式,在上一篇的入门实例中,我们也用到了一些注解。

            本文,我将详细介绍在实际项目中常用的注解。

            @SpringBootApplication

            我们可以注意到 Spring Boot 支持 main 方法启动,在我们需要启动的主类中加入此注解,告诉 Spring Boot,这个类是程序的入口。如:

            @SpringBootApplication
            public class Application {
            
                public static void main(String[] args) {
                    SpringApplication.run(Application.class, args);
                }
            }

            如果不加这个注解,程序是无法启动的。

            我们查看下 SpringBootApplication 的源码,源码如下:

            @Target(ElementType.TYPE)
            @Retention(RetentionPolicy.RUNTIME)
            @Documented
            @Inherited
            @SpringBootConfiguration
            @EnableAutoConfiguration
            @ComponentScan(excludeFilters = {
                    @Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),
                    @Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) })
            public @interface SpringBootApplication {
            
                /**
                 * Exclude specific auto-configuration classes such that they will never be applied.
                 * @return the classes to exclude
                 */
                @AliasFor(annotation = EnableAutoConfiguration.class, attribute = "exclude")
                Class<?>[] exclude() default {};
            
                /**
                 * Exclude specific auto-configuration class names such that they will never be
                 * applied.
                 * @return the class names to exclude
                 * @since 1.3.0
                 */
                @AliasFor(annotation = EnableAutoConfiguration.class, attribute = "excludeName")
                String[] excludeName() default {};
            
                /**
                 * Base packages to scan for annotated components. Use {@link #scanBasePackageClasses}
                 * for a type-safe alternative to String-based package names.
                 * @return base packages to scan
                 * @since 1.3.0
                 */
                @AliasFor(annotation = ComponentScan.class, attribute = "basePackages")
                String[] scanBasePackages() default {};
            
                /**
                 * Type-safe alternative to {@link #scanBasePackages} for specifying the packages to
                 * scan for annotated components. The package of each class specified will be scanned.
                 * <p>
                 * Consider creating a special no-op marker class or interface in each package that
                 * serves no purpose other than being referenced by this attribute.
                 * @return base packages to scan
                 * @since 1.3.0
                 */
                @AliasFor(annotation = ComponentScan.class, attribute = "basePackageClasses")
                Class<?>[] scanBasePackageClasses() default {};
            
            }

            在这个注解类上有3个注解,如下:

            @SpringBootConfiguration
            @EnableAutoConfiguration
            @ComponentScan(excludeFilters = {
                    @Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),
                    @Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) })

            因此,我们可以用这三个注解代替 SpringBootApplication,如:

            @SpringBootConfiguration
            @EnableAutoConfiguration
            @ComponentScan
            public class Application {
            
                public static void main(String[] args) {
                    SpringApplication.run(Application.class, args);
                }
            }

            其中,SpringBootConfiguration 表示 Spring Boot 的配置注解,EnableAutoConfiguration 表示自动配置,ComponentScan 表示 Spring Boot 扫描 Bean 的规则,比如扫描哪些包。

            @Configuration

            加入了这个注解的类被认为是 Spring Boot 的配置类,我们知道可以在 application.yml 设置一些配置,?#37096;?#20197;通过代码设?#38376;?#32622;。

            如果我们要通过代码设?#38376;?#32622;,就必须在这个类上标注 Configuration 注解。如下代码:

            @Configuration
            public class WebConfig extends WebMvcConfigurationSupport{
            
                @Override
                protected void addInterceptors(InterceptorRegistry registry) {
                    super.addInterceptors(registry);
                    registry.addInterceptor(new ApiInterceptor());
                }
            }

            不过 Spring Boot 官方推荐 Spring Boot 项目用 SpringBootConfiguration ?#21019;?#26367; Configuration。

            @Bean

            这个注解是方法级别上的注解,主要添加在 @Configuration@SpringBootConfiguration 注解的类,有时?#37096;?#20197;添加在 @Component 注解的类。它的作用是定义一个Bean。

            请看下面代码:

            @Bean
                public ApiInterceptor interceptor(){
                    return new ApiInterceptor();
                }

            那么,我们可以在 ApiInterceptor 里面注入其他 Bean,?#37096;?#20197;在其他 Bean 注入这个类。

            @Value

            通常情况下,我们需要定义一些全局变量,都会想到的方法是定义一个 public static 变量,在需要时调用,是否有其他更好的方案呢?#30475;?#26696;是肯定的。下面请?#21019;?#30721;:

            @Value("${server.port}")
                String port;
                @RequestMapping("/hello")
                public String home(String name) {
                    return "hi "+name+",i am from port:" +port;
                }

            其中,server.port 就是我们在 application.yml 里面定义的属性,我们可以自定义任意属性名,通过 @Value 注解就可以将其取出来。

            它的好处不言而喻:

            1. 定义在配置文件里,变量发生变化,无需修改代码。
            2. 变量交给Spring来管理,性能更好。

            注:本课程默认针对于对 SpringMVC 有所了解的读者,Spring Boot 本身基于 Spring 开发的,因此,本文不再讲解其他 Spring 的注解。

            注入任何类

            本节通过一个实际的例子来讲解如何注入一个普通类,并且说明这样做的好处。

            假设一个需求是这样的:项目要求使用阿里云的 OSS 进行文件上传。

            我们知道,一个项目一般会分为开发环境、测试环境和生产环境。OSS 文件上传一般有如?#24405;?#20010;?#38382;篴ppKey、appSecret、bucket、endpoint 等。不同环境的?#38382;?#37117;可能不一样,这样便于区分。按照传统的做法,我们在代码里设置这些?#38382;?#36825;样做的话,?#30475;?#21457;布不同的环境包都需要手动修改代码。

            这个时候,我们就可以考虑将这些?#38382;?#23450;义到配置文件里面,通过前面提到的 @Value 注解取出来,再通过 @Bean 将其定义为一个 Bean,这时我们只需要在需要使用的地方注入该 Bean 即可。

            首先在 application.yml 加入如下内容:

            appKey: 1
            appSecret: 1
            bucket: lynn
            endPoint: https://www.aliyun.com

            其次创建一个普通类:

            public class Aliyun {
            
                private String appKey;
            
                private String appSecret;
            
                private String bucket;
            
                private String endPoint;
            
                public static class Builder{
            
                    private String appKey;
            
                    private String appSecret;
            
                    private String bucket;
            
                    private String endPoint;
            
                    public Builder setAppKey(String appKey){
                        this.appKey = appKey;
                        return this;
                    }
            
                    public Builder setAppSecret(String appSecret){
                        this.appSecret = appSecret;
                        return this;
                    }
            
                    public Builder setBucket(String bucket){
                        this.bucket = bucket;
                        return this;
                    }
            
                    public Builder setEndPoint(String endPoint){
                        this.endPoint = endPoint;
                        return this;
                    }
            
                    public Aliyun build(){
                        return new Aliyun(this);
                    }
                }
            
                public static Builder options(){
                    return new Aliyun.Builder();
                }
            
                private Aliyun(Builder builder){
                    this.appKey = builder.appKey;
                    this.appSecret = builder.appSecret;
                    this.bucket = builder.bucket;
                    this.endPoint = builder.endPoint;
                }
            
                public String getAppKey() {
                    return appKey;
                }
            
                public String getAppSecret() {
                    return appSecret;
                }
            
                public String getBucket() {
                    return bucket;
                }
            
                public String getEndPoint() {
                    return endPoint;
                }
            }

            然后在 @SpringBootConfiguration 注解的类添加如下代码:

            @Value("${appKey}")
                private String appKey;
                @Value("${appSecret}")
                private String appSecret;
                @Value("${bucket}")
                private String bucket;
                @Value("${endPoint}")
                private String endPoint;
            
                @Bean
                public Aliyun aliyun(){
                    return Aliyun.options()
                            .setAppKey(appKey)
                            .setAppSecret(appSecret)
                            .setBucket(bucket)
                            .setEndPoint(endPoint)
                            .build();
                }

            最后在需要的地方注入这个 Bean 即可:

            @Autowired
                private Aliyun aliyun;

            拦截器

            我们在提供 API 的时候,经常需要对 API 进?#22411;?#19968;的拦截,比如进行接口的安全性校验。

            本节,?#19968;?#35762;解 Spring Boot 是如何进行拦截器设置的,请看接下来的代码。

            创建一个拦截器类:ApiInterceptor,并实现 HandlerInterceptor 接口:

            public class ApiInterceptor implements HandlerInterceptor {
                //请求之前
                @Override
                public boolean preHandle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o) throws Exception {
                    System.out.println("进入拦截器");
                    return true;
                }
                //请求时
                @Override
                public void postHandle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o, ModelAndView modelAndView) throws Exception {
            
                }
                //请求完成
                @Override
                public void afterCompletion(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o, Exception e) throws Exception {
            
                }
            }

            @SpringBootConfiguration 注解的类继承 WebMvcConfigurationSupport 类,并重写 addInterceptors 方法,将 ApiInterceptor 拦截器类添加进去,代码如下:

            @SpringBootConfiguration
            public class WebConfig extends WebMvcConfigurationSupport{
            
                @Override
                protected void addInterceptors(InterceptorRegistry registry) {
                    super.addInterceptors(registry);
                    registry.addInterceptor(new ApiInterceptor());
                }
            }

            异常处理

            我们在 Controller 里提供接口,通常需要捕捉异常,并进行友好提示,否则一旦出错,界面上就会显示报错信息,给用户一种不好的体验。最简单的做法就是每个方法都使用 try catch 进行捕捉,报错后,则在 catch 里面设置友好的报错提示。如果方法很多,每个都需要 try catch,代码会显得?#20998;祝?#20889;起来也比较麻烦。

            我们可不可以提供一个公共的入口进?#22411;?#19968;的异常处理呢??#27604;?#21487;以。方法很多,这里我们通过 Spring 的 AOP 特性就可以很方便的实现异常的统一处理。

            @Aspect
            @Component
            public class WebExceptionAspect {
            
                private static final Logger logger = LoggerFactory.getLogger(WebExceptionAspect.class);
            
            //凡是注解了RequestMapping的方法都被拦截   @Pointcut("@annotation(org.springframework.web.bind.annotation.RequestMapping)")
                private void webPointcut() {
                }
            
                /**
                 * 拦截web层异常,记录异常日志,并返回友好信息到前端 目前只拦截Exception,是否要拦截Error需再做考虑
                 *
                 * @param e
                 *            异常对象
                 */
                @AfterThrowing(pointcut = "webPointcut()", throwing = "e")
                public void handleThrowing(Exception e) {
                    e.printStackTrace();
                    logger.error("发现异常!" + e.getMessage());
                    logger.error(JSON.toJSONString(e.getStackTrace()));
                    //这里输入友好性信息
                    writeContent("出现异常");
                }
            
                /**
                 * 将内容输出到浏览器
                 *
                 * @param content
                 *            输出内容
                 */
                private void writeContent(String content) {
                    HttpServletResponse response = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes())
                            .getResponse();
                    response.reset();
                    response.setCharacterEncoding("UTF-8");
                    response.setHeader("Content-Type", "text/plain;charset=UTF-8");
                    response.setHeader("icop-content-type", "exception");
                    PrintWriter writer = null;
                    try {
                        writer = response.getWriter();
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                    writer.print(content);
                    writer.flush();
                    writer.close();
                }
            }

            这样,我们无需每个方法都添加 try catch,一旦报错,则会执行 handleThrowing 方法。

            优雅的输入合法性校验

            为了接口的健壮性,我们通常除了客户端进行输入合法性校验外,在 Controller 的方法里,我们也需要对?#38382;?#36827;行合法性校验,传统的做法是每个方法的?#38382;?#37117;做一遍判断,这种方式和上一节讲的异常处理一个道理,不太优雅,也不易维护。

            其实,SpringMVC 提供?#25628;?#35777;接口,下面请?#21019;?#30721;:

            @GetMapping("authorize")
            public void authorize(@Valid AuthorizeIn authorize, BindingResult ret){
                if(result.hasFieldErrors()){
                        List<FieldError> errorList = result.getFieldErrors();
                        //通过断言抛出?#38382;?#19981;合法的异常
                        errorList.stream().forEach(item -> Assert.isTrue(false,item.getDefaultMessage()));
                    }
            }
            public class AuthorizeIn extends BaseModel{
            
                @NotBlank(message = "缺少response_type?#38382;?quot;)
                private String responseType;
                @NotBlank(message = "缺少client_id?#38382;?quot;)
                private String ClientId;
            
                private String state;
            
                @NotBlank(message = "缺少redirect_uri?#38382;?quot;)
                private String redirectUri;
            
                public String getResponseType() {
                    return responseType;
                }
            
                public void setResponseType(String responseType) {
                    this.responseType = responseType;
                }
            
                public String getClientId() {
                    return ClientId;
                }
            
                public void setClientId(String clientId) {
                    ClientId = clientId;
                }
            
                public String getState() {
                    return state;
                }
            
                public void setState(String state) {
                    this.state = state;
                }
            
                public String getRedirectUri() {
                    return redirectUri;
                }
            
                public void setRedirectUri(String redirectUri) {
                    this.redirectUri = redirectUri;
                }
            }

            在 controller 的方法需要校验的?#38382;?#21518;面必须跟 BindingResult,否则无法进行校验。但是这样会抛出异常,对用户而言不太友好!

            那怎么办呢?

            很简单,我们可以利用上一节讲的异常处理,对报错进行拦截:

            @Component
            @Aspect
            public class WebExceptionAspect implements ThrowsAdvice{
            
                public static final Logger logger = LoggerFactory.getLogger(WebExceptionAspect.class);
            
            //拦截被GetMapping注解的方法    @Pointcut("@annotation(org.springframework.web.bind.annotation.GetMapping)")
                private void webPointcut() {
                }
            
                @AfterThrowing(pointcut = "webPointcut()",throwing = "e")
                public void afterThrowing(Exception e) throws Throwable {
                    logger.debug("exception 来了!");
                    if(StringUtils.isNotBlank(e.getMessage())){
                                       writeContent(e.getMessage());
                    }else{
                        writeContent("?#38382;?#38169;误!");
                    }
            
                }
            
                /**
                 * 将内容输出到浏览器
                 *
                 * @param content
                 *            输出内容
                 */
                private void writeContent(String content) {
                    HttpServletResponse response = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes())
                            .getResponse();
                    response.reset();
                    response.setCharacterEncoding("UTF-8");
                    response.setHeader("Content-Type", "text/plain;charset=UTF-8");
                    response.setHeader("icop-content-type", "exception");
                    PrintWriter writer = null;
                    try {
                        writer = response.getWriter();
            
                        writer.print((content == null) ? "" : content);
                        writer.flush();
                        writer.close();
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
            }

            这样当我们传入不合法的?#38382;?#26102;就会进入 WebExceptionAspect 类,从而输出友好?#38382;?/p>

            我们再把验证的代码单独封装成方法:

            protected void validate(BindingResult result){
                    if(result.hasFieldErrors()){
                        List<FieldError> errorList = result.getFieldErrors();
                        errorList.stream().forEach(item -> Assert.isTrue(false,item.getDefaultMessage()));
                    }
                }

            这样?#30475;尾问?#26657;验只需要调用 validate 方法就行了,我们可以看到代码的可读性也大大的提高了。

            接口版本控制

            一个系统上线后会不?#31995;?#20195;更新,需求?#19981;?#19981;断变化,有可能接口的?#38382;不?#21457;生变化,如果在原有的?#38382;?#19978;直接修?#27169;?#21487;能会影响线上系统的正常运行,这时我们就需要设置不同的版本,这样即?#20849;问?#21457;生变化,由于?#20064;?#26412;没有变化,因此不会影响上线系统的运行。

            一般我们可以在地址上带上版本号,?#37096;?#20197;在?#38382;?#19978;带上版本号,还可以再 header 里带上版本号,这里我们在地址上带上版本号,大致的地址如:http://api.example.com/v1/test,其中,v1 ?#21019;?#34920;的是版本号。具体做法请?#21019;?#30721;:

            @Target({ElementType.METHOD,ElementType.TYPE})
            @Retention(RetentionPolicy.RUNTIME)
            @Documented
            @Mapping
            public @interface ApiVersion {
            
                /**
                 * 标识版本号
                 * @return
                 */
                int value();
            }
            public class ApiVersionCondition implements RequestCondition<ApiVersionCondition> {
            
                // 路径中版本的前缀, 这里用 /v[1-9]/的形式
                private final static Pattern VERSION_PREFIX_PATTERN = Pattern.compile("v(\\d+)/");
            
                private int apiVersion;
            
                public ApiVersionCondition(int apiVersion){
                    this.apiVersion = apiVersion;
                }
            
                @Override
                public ApiVersionCondition combine(ApiVersionCondition other) {
                    // 采用最后定义优先原则,则方法上的定义覆盖类上面的定义
                    return new ApiVersionCondition(other.getApiVersion());
                }
            
                @Override
                public ApiVersionCondition getMatchingCondition(HttpServletRequest request) {
                    Matcher m = VERSION_PREFIX_PATTERN.matcher(request.getRequestURI());
                    if(m.find()){
                        Integer version = Integer.valueOf(m.group(1));
                        if(version >= this.apiVersion)
                        {
                            return this;
                        }
                    }
                    return null;
                }
            
                @Override
                public int compareTo(ApiVersionCondition other, HttpServletRequest request) {
                    // 优先匹配最新的版本号
                    return other.getApiVersion() - this.apiVersion;
                }
            
                public int getApiVersion() {
                    return apiVersion;
                }
            }
            public class CustomRequestMappingHandlerMapping extends
                    RequestMappingHandlerMapping {
            
                @Override
                protected RequestCondition<ApiVersionCondition> getCustomTypeCondition(Class<?> handlerType) {
                    ApiVersion apiVersion = AnnotationUtils.findAnnotation(handlerType, ApiVersion.class);
                    return createCondition(apiVersion);
                }
            
                @Override
                protected RequestCondition<ApiVersionCondition> getCustomMethodCondition(Method method) {
                    ApiVersion apiVersion = AnnotationUtils.findAnnotation(method, ApiVersion.class);
                    return createCondition(apiVersion);
                }
            
                private RequestCondition<ApiVersionCondition> createCondition(ApiVersion apiVersion) {
                    return apiVersion == null ? null : new ApiVersionCondition(apiVersion.value());
                }
            }
            @SpringBootConfiguration
            public class WebConfig extends WebMvcConfigurationSupport {
            
                @Bean
                public AuthInterceptor interceptor(){
                    return new AuthInterceptor();
                }
            
                @Override
                public void addInterceptors(InterceptorRegistry registry) {
                    registry.addInterceptor(new AuthInterceptor());
                }
            
                @Override
                @Bean
                public RequestMappingHandlerMapping requestMappingHandlerMapping() {
                    RequestMappingHandlerMapping handlerMapping = new CustomRequestMappingHandlerMapping();
                    handlerMapping.setOrder(0);
                    handlerMapping.setInterceptors(getInterceptors());
                    return handlerMapping;
                }
            }

            Controller 类的接口定义如下:

            @ApiVersion(1)
            @RequestMapping("{version}/dd")
            public class HelloController{}

            这样我们就实现了版本控制,如果增加了一个版本,则创建一个新的 Controller,方法名一致,ApiVersion 设置为2,则地?#20998;?v1 会?#19994;?ApiVersion 为1的方法,v2 会?#19994;?ApiVersion 为2的方法。

            自定义 JSON 解析

            Spring Boot 中 RestController 返回的?#22336;?#20018;默认使用 Jackson 引擎,它也提供了工厂类,我们可以自定义 JSON 引擎,本节实例我们将 JSON 引擎替换为 fastJSON,首先需要引入 fastJSON:

            <dependency>
                        <groupId>com.alibaba</groupId>
                        <artifactId>fastjson</artifactId>
                        <version>${fastjson.version}</version>
                    </dependency>

            其次,在 WebConfig 类重写 configureMessageConverters 方法:

            @Override
                public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
                    super.configureMessageConverters(converters);
                    /*
                    1.需要先定义一个convert转换消息的对象;
                    2.添加fastjson的配置信息,比如是否要格式化返回的json数据
                    3.在convert中添加配置信息
                    4.将convert添加到converters中
                     */
                    //1.定义一个convert转换消息对象
                    FastJsonHttpMessageConverter fastConverter=new FastJsonHttpMessageConverter();
                    //2.添加fastjson的配置信息,比如:是否要格式化返回json数据
                    FastJsonConfig fastJsonConfig=new FastJsonConfig();
                    fastJsonConfig.setSerializerFeatures(
                            SerializerFeature.PrettyFormat
                    );
                    fastConverter.setFastJsonConfig(fastJsonConfig);
                    converters.add(fastConverter);
                }

            单元测试

            Spring Boot 的单元测试很简单,直接?#21019;?#30721;:

            @SpringBootTest(classes = Application.class)
            @RunWith(SpringJUnit4ClassRunner.class)
            public class TestDB {
            
                @Test
                public void test(){
                }
            }

            模板引擎

            在传统的 SpringMVC 架构中,我们一般将 JSP、HTML 页面放到 webapps 目录下面,但是 Spring Boot 没有 webapps,更没有 web.xml,如果我们要写界面的话,该如何做呢?

            Spring Boot 官方提供了几种模板引擎:FreeMarker、Velocity、Thymeleaf、Groovy、mustache、JSP。

            这里以 FreeMarker 为例讲解 Spring Boot 的使用。

            首先引入 FreeMarker 依?#25285;?/p>

            <dependency>
                    <groupId>org.springframework.boot</groupId>
                    <artifactId>spring-boot-starter-freemarker</artifactId>
                </dependency>

            在 resources 下面建立两个目录:static 和 templates,如图所示:

            其中 static 目录用于存放静态资源,譬如:CSS、JS、HTML 等,templates 目录存放模板引擎文件,我们可以在 templates 下面创建一个文件:index.ftl(freemarker 默认后缀为 .ftl ),并添加内容:

            <!DOCTYPE html>
            <html>
                <head>
            
                </head>
                <body>
                    <h1>Hello World!</h1>
                </body>
            </html>

            然后创建 PageController 并添加内容:

            @Controller
            public class PageController {
            
                @RequestMapping("index.html")
                public String index(){
                    return "index";
                }
            }

            启动 Application.java,访问:http://localhost:8080/index.html,就可以看到如图所示:

            第03课:Spring Boot 启动原理

            第04课:初识 Spring Cloud

            第05课:服务注册与发现

            第06课:服务网关

            第07课:服务消费者

            第08课:服务异常处理

            第09课:配置中心

            第10课?#21512;?#24687;总线

            第11课:服务链路追踪

            第12课:分布式锁

            第13课:分布式事务

            第14课:Spring Cloud 实例详解——基础框架搭建(一)

            第15课:Spring Cloud 实例详解——基础框架搭建(二)

            第16课:Spring Cloud 实例详解——基础框架搭建(三)

            第17课:Spring Cloud 实例详解——业务代码实现

            第18课:Spring Cloud 实例详解——系统发布

            第19课:Spring Cloud 源码解析

            第20课:K8S+Docker 部署 Spring Cloud 集群

            阅读全文: http://gitbook.cn/gitchat/column/5af108d20a989b69c385f47a

            我来评几句
            登录后评论

            已发表评论数()

            相关站点

            +订阅
            热门文章
            陕西高频十一选五