<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 各大模塊,接著通過一個實例帶領大家了解大型分布式微服務架構的搭建過程,最后深入源碼加深對它的了解。

            本課程共分為四個部分:

            第一部分(第1-3課),初識 Spring Boot,掌握 Spring Boot 基礎知識,為后續入門 Spring Cloud 打好基礎 。

            第二部分(第4-13課),Spring Cloud 入門篇,主要介紹 Spring Cloud 常用模塊,包括服務發現、服務注冊、配置中心、鏈路追蹤、異常處理等。

            第三部分(第14-18課),Spring Cloud 進階篇,介紹大型分布式系統中事務處理、線程安全等問題,并以一個實例項目手把手教大家搭建完整的微服務系統。

            第四部分(第19-20課),Spring Cloud 高級篇,解析 Spring Cloud 源碼,并講解如何部署基于 Spring Cloud 的大型分布式系統。

            作者介紹

            李熠,從事 Java 后端開發6年,現任職某大型互聯網公司,擔任 Java 高級開發工程師,CSDN 博客專家,全棧工程師。

            課程內容

            導讀:什么是 Spring Cloud 及應用現狀

            Spring Cloud 是什么?

            在學習本課程之前,讀者有必要先了解一下 Spring Cloud。

            Spring Cloud 是一系列框架的有序集合,它利用 Spring Boot 的開發便利性簡化了分布式系統的開發,比如服務發現、服務網關、服務路由、鏈路追蹤等。Spring Cloud 并不重復造輪子,而是將市面上開發得比較好的模塊集成進去,進行封裝,從而減少了各模塊的開發成本。換句話說:Spring Cloud 提供了構建分布式系統所需的“全家桶”。

            Spring Cloud 現狀

            目前,國內使用 Spring Cloud 技術的公司并不多見,不是因為 Spring Cloud 不好,主要原因有以下幾點:

            1. Spring Cloud 中文文檔較少,出現問題網上沒有太多的解決方案。
            2. 國內創業型公司技術老大大多是阿里系員工,而阿里系多采用 Dubbo 來構建微服務架構。
            3. 大型公司基本都有自己的分布式解決方案,而中小型公司的架構很多用不上微服務,所以沒有采用 Spring Cloud 的必要性。

            但是,微服務架構是一個趨勢,而 Spring Cloud 是微服務解決方案的佼佼者,這也是作者寫本系列課程的意義所在。

            Spring Cloud 優缺點

            其主要優點有:

            1. 集大成者,Spring Cloud 包含了微服務架構的方方面面。
            2. 約定優于配置,基于注解,沒有配置文件。
            3. 輕量級組件,Spring Cloud 整合的組件大多比較輕量級,且都是各自領域的佼佼者。
            4. 開發簡便,Spring Cloud 對各個組件進行了大量的封裝,從而簡化了開發。
            5. 開發靈活,Spring Cloud 的組件都是解耦的,開發人員可以靈活按需選擇組件。

            接下來,我們看下它的缺點:

            1. 項目結構復雜,每一個組件或者每一個服務都需要創建一個項目。
            2. 部署門檻高,項目部署需要配合 Docker 等容器技術進行集群部署,而要想深入了解 Docker,學習成本高。

            Spring Cloud 的優勢是顯而易見的。因此對于想研究微服務架構的同學來說,學習 Spring Cloud 是一個不錯的選擇。

            Spring Cloud 和 Dubbo 對比

            Dubbo 只是實現了服務治理,而 Spring Cloud 實現了微服務架構的方方面面,服務治理只是其中的一個方面。下面通過一張圖對其進行比較:

            可以看出,Spring Cloud 比較全面,而 Dubbo 由于只實現了服務治理,需要集成其他模塊,需要單獨引入,增加了學習成本和集成成本。

            Spring Cloud 學習

            Spring Cloud 基于 Spring Boot,因此在研究 Spring Cloud 之前,本課程會首先介紹 Spring Boot 的用法,方便后續 Spring Cloud 的學習。

            本課程不會講解 SpringMVC 的用法,因此學習本課程需要讀者對 Spring 及 SpringMVC 有過研究。

            本課程共分為四個部分:

            • 第一部分初識 Spring Boot,掌握 Spring Boot 基礎知識,為后續入門 Spring Cloud 打好基礎 。

            • 第二部分 Spring Cloud 入門篇,主要介紹 Spring Cloud 常用模塊,包括服務發現、服務注冊、配置中心、鏈路追蹤、異常處理等。

            • 第三部分 Spring Cloud 進階篇,介紹大型分布式系統中事務處理、線程安全等問題,并以一個實例項目手把手教大家搭建完整的微服務系統。

            • 第四部分 Spring Cloud 高級篇,解析 Spring Cloud 源碼,并講解如何部署基于 Spring Cloud 的大型分布式系統。

            本課程的所有示例代碼均可在:https://github.com/lynnlovemin/SpringCloudLesson 下載。

            第01課:Spring Boot 入門

            什么是 Spring Boot

            Spring Boot 是由 Pivotal 團隊提供的基于 Spring 的全新框架,其設計目的是為了簡化 Spring 應用的搭建和開發過程。該框架遵循“約定大于配置”原則,采用特定的方式進行配置,從而使開發者無需定義大量的 XML 配置。通過這種方式,Spring Boot 致力于在蓬勃發展的快速應用開發領域成為領導者。

            Spring Boot 并不重復造輪子,而且在原有 Spring 的框架基礎上封裝了一層,并且它集成了一些類庫,用于簡化開發。換句話說,Spring Boot 就是一個大容器。

            下面幾張圖展示了 官網 上提供的 Spring Boot 所集成的所有類庫:

            Spring Boot 官方推薦使用 Maven 或 Gradle 來構建項目,本教程采用 Maven。

            第一個 Spring Boot 項目

            大多數教程都是以 Hello World 入門,本教程也不例外,接下來,我們就來搭建一個最簡單的 Spring Boot 項目。

            首先創建一個 Maven 工程,請看下圖:

            然后在 pom.xml 加入 Spring Boot 依賴:

            <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,則會看到下圖:

            我們可以注意到,沒有寫任何的配置文件,更沒有顯示的使用任何容器,它是如何啟動程序的呢,具體原理我將在第3課中具體分析。

            這里我們可以初步分析出,Spring Boot 提供了默認的配置,在啟動類里加入 @SpringBootApplication 注解,則這個類就是整個應用程序的啟動類。

            properties 和 yaml

            Spring Boot 整個應用程序只有一個配置文件,那就是 .properties.yml 文件。但是,在前面的示例代碼中,我們并沒有看到該配置文件,那是因為 Spring Boot 對每個配置項都有默認值。當然,我們也可以添加配置文件,用以覆蓋其默認值,這里以 .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 就完成了,但是對于一個大型項目,這是遠遠不夠的,Spring Boot 的詳細操作可以參照 官網

            下面展示一個最基礎的企業級 Spring Boot 項目的結構:

            其中,Application.java 是程序的啟動類,Startup.java 是程序啟動完成前執行的類,WebConfig.java 是配置類,所有 bean 注入、配置、攔截器注入等都放在這個類里面。

            以上實例只是最簡單的 Spring Boot 項目入門實例,后面會深入研究 Spring Boot。

            第02課:Spring Boot 進階

            上一篇帶領大家初步了解了如何使用 Spring Boot 搭建框架,通過 Spring Boot 和傳統的 SpringMVC 架構的對比,我們清晰地發現 Spring Boot 的好處,它使我們的代碼更加簡單,結構更加清晰。

            從這一篇開始,我將帶領大家更加深入的認識 Spring Boot,將 Spring Boot 涉及到東西進行拆解,從而了解 Spring Boot 的方方面面。學完本文后,讀者可以基于 Spring Boot 搭建更加復雜的系統框架。

            我們知道,Spring Boot 是一個大容器,它將很多第三方框架都進行了集成,我們在實際項目中用到哪個模塊,再引入哪個模塊。比如我們項目中的持久化框架用 MyBatis,則在 pom.xml 添加如下依賴:

            <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 中讀取配置信息,并加載到內存中。上一篇我們只是粗略的列舉了幾個配置項,其實 Spring Boot 的配置項是很多的,本文我們將學習在實際項目中常用的配置項(注:為了方便說明,配置項均以 properties 文件的格式寫出,后續的實際配置都會寫成 yaml 格式)。

            配置項 說明 舉例
            server.port 應用程序啟動端口 server.port=8080,定義應用程序啟動端口為8080
            server.context-path 應用程序上下文 server.port=/api,則訪問地址為: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 數據庫連接字符串 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

            下面是我參與的某個項目的 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

            以上列舉了常用的配置項,所有配置項信息都可以在 官網 中找到,本課程就不一一列舉了。

            多環境配置

            在一個企業級系統中,我們可能會遇到這樣一個問題:開發時使用開發環境,測試時使用測試環境,上線時使用生產環境。每個環境的配置都可能不一樣,比如開發環境的數據庫是本地地址,而測試環境的數據庫是測試地址。那我們在打包的時候如何生成不同環境的包呢?

            這里的解決方案有很多:

            1. 每次編譯之前手動把所有配置信息修改成當前運行的環境信息。這種方式導致每次都需要修改,相當麻煩,也容易出錯。
            2. 利用 Maven,在 pom.xml 里配置多個環境,每次編譯之前將 settings.xml 里面修改成當前要編譯的環境 ID。這種方式會事先設置好所有環境,缺點就是每次也需要手動指定環境,如果環境指定錯誤,發布時是不知道的。
            3. 第三種方案就是本文重點介紹的,也是作者強烈推薦的方式。

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

            spring:
              profiles:
                active: dev

            含義是指定當前項目的默認環境為 dev,即項目啟動時如果不指定任何環境,Spring 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

            這樣,我們就實現了多環境的配置,每次編譯打包我們無需修改任何東西,編譯為 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 設置一些配置,也可以通過代碼設置配置。

            如果我們要通過代碼設置配置,就必須在這個類上標注 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 來代替 Configuration。

            @Bean

            這個注解是方法級別上的注解,主要添加在 @Configuration@SpringBootConfiguration 注解的類,有時也可以添加在 @Component 注解的類。它的作用是定義一個Bean。

            請看下面代碼:

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

            那么,我們可以在 ApiInterceptor 里面注入其他 Bean,也可以在其他 Bean 注入這個類。

            @Value

            通常情況下,我們需要定義一些全局變量,都會想到的方法是定義一個 public static 變量,在需要時調用,是否有其他更好的方案呢?答案是肯定的。下面請看代碼:

            @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 文件上傳一般有如下幾個參數:appKey、appSecret、bucket、endpoint 等。不同環境的參數都可能不一樣,這樣便于區分。按照傳統的做法,我們在代碼里設置這些參數,這樣做的話,每次發布不同的環境包都需要手動修改代碼。

            這個時候,我們就可以考慮將這些參數定義到配置文件里面,通過前面提到的 @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 進行統一的攔截,比如進行接口的安全性校驗。

            本節,我會講解 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,代碼會顯得臃腫,寫起來也比較麻煩。

            我們可不可以提供一個公共的入口進行統一的異常處理呢?當然可以。方法很多,這里我們通過 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 的方法里,我們也需要對參數進行合法性校驗,傳統的做法是每個方法的參數都做一遍判斷,這種方式和上一節講的異常處理一個道理,不太優雅,也不易維護。

            其實,SpringMVC 提供了驗證接口,下面請看代碼:

            @GetMapping("authorize")
            public void authorize(@Valid AuthorizeIn authorize, BindingResult ret){
                if(result.hasFieldErrors()){
                        List<FieldError> errorList = result.getFieldErrors();
                        //通過斷言拋出參數不合法的異常
                        errorList.stream().forEach(item -> Assert.isTrue(false,item.getDefaultMessage()));
                    }
            }
            public class AuthorizeIn extends BaseModel{
            
                @NotBlank(message = "缺少response_type參數")
                private String responseType;
                @NotBlank(message = "缺少client_id參數")
                private String ClientId;
            
                private String state;
            
                @NotBlank(message = "缺少redirect_uri參數")
                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 的方法需要校驗的參數后面必須跟 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("參數錯誤!");
                    }
            
                }
            
                /**
                 * 將內容輸出到瀏覽器
                 *
                 * @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();
                    }
                }
            }

            這樣當我們傳入不合法的參數時就會進入 WebExceptionAspect 類,從而輸出友好參數。

            我們再把驗證的代碼單獨封裝成方法:

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

            這樣每次參數校驗只需要調用 validate 方法就行了,我們可以看到代碼的可讀性也大大的提高了。

            接口版本控制

            一個系統上線后會不斷迭代更新,需求也會不斷變化,有可能接口的參數也會發生變化,如果在原有的參數上直接修改,可能會影響線上系統的正常運行,這時我們就需要設置不同的版本,這樣即使參數發生變化,由于老版本沒有變化,因此不會影響上線系統的運行。

            一般我們可以在地址上帶上版本號,也可以在參數上帶上版本號,還可以再 header 里帶上版本號,這里我們在地址上帶上版本號,大致的地址如:http://api.example.com/v1/test,其中,v1 即代表的是版本號。具體做法請看代碼:

            @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,則地址中 v1 會找到 ApiVersion 為1的方法,v2 會找到 ApiVersion 為2的方法。

            自定義 JSON 解析

            Spring Boot 中 RestController 返回的字符串默認使用 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 的單元測試很簡單,直接看代碼:

            @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 依賴:

            <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課:消息總線

            第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

            我來評幾句
            登錄后評論

            已發表評論數()

            相關站點

            +訂閱
            熱門文章
            陕西高频十一选五