中文字幕一区二区人妻电影,亚洲av无码一区二区乱子伦as ,亚洲精品无码永久在线观看,亚洲成aⅴ人片久青草影院按摩,亚洲黑人巨大videos

Spring引導(dǎo)教程:REST服務(wù)和微服務(wù)

發(fā)布于:2021-01-25 11:01:19

0

96

0

docker 微服務(wù) spring boot 教程

javaee應(yīng)用服務(wù)器和單片軟件體系結(jié)構(gòu)的時代幾乎一去不復(fù)返了。硬件不再變得更快,但互聯(lián)網(wǎng)流量仍在增加。平臺必須支持向外擴(kuò)展。負(fù)載必須分配到多個主機(jī)。基于微服務(wù)的體系結(jié)構(gòu)可以為這一需求提供解決方案。除了更好的可擴(kuò)展性之外,微服務(wù)還提供了更快的開發(fā)周期、基于負(fù)載的動態(tài)可擴(kuò)展性和改進(jìn)的故障轉(zhuǎn)移行為。

在微服務(wù)體系結(jié)構(gòu)中,只需要擴(kuò)展需要更多資源的部分,而不是復(fù)制一個完整的系統(tǒng)來處理更高的需求。軟件可以解耦,軟件的維護(hù)也越來越容易。每一個不得不在一個單一應(yīng)用程序中更新hibernate版本的開發(fā)人員都知道讓一個單一應(yīng)用程序保持最新和減少技術(shù)債務(wù)的痛苦。使用microservices,您可以一步一步地完成這項(xiàng)工作。隨著服務(wù)數(shù)量的增加,每個開銷都需要最小化。繁重的應(yīng)用服務(wù)器不是用于此目的的工具。Web應(yīng)用程序或服務(wù)可以通過使用嵌入式Web服務(wù)器實(shí)現(xiàn)一段時間。不用安裝完整的JEE概要文件應(yīng)用服務(wù)器,也不用部署EAR或WAR文件,一個簡單的自運(yùn)行jar就可以完成這項(xiàng)工作。這并不新鮮,但為了快速創(chuàng)建此類服務(wù),需要一個模板或框架。這種框架在其他語言中非常突出,但對于Java來說,只有少數(shù)幾種可用的選項(xiàng)。情況變了。在Dropwizard、Play Framework或Spring Boot中,至少有3個框架在Java微服務(wù)世界中大量使用。

在本教程中,我將使用一個簡單的示例來演示如何使用springboot設(shè)置基于REST的springboot微服務(wù)。此示例基于一個服務(wù),該服務(wù)是為某些移動應(yīng)用程序構(gòu)建的后端。本教程中顯示的代碼已簡化。該服務(wù)本身通過使用其他已經(jīng)存在于后臺的服務(wù),為移動應(yīng)用程序提供了restapi。這意味著該服務(wù)僅充當(dāng)其他內(nèi)部服務(wù)的包裝器類型。該服務(wù)需要限制未經(jīng)授權(quán)的訪問,在這種情況下,意味著該服務(wù)需要用戶和客戶端(在這種情況下是移動應(yīng)用程序)的授權(quán)。

這就是我們添加OAuth2和JWT作為授權(quán)系統(tǒng)的原因。我們還為API的文檔添加了Swagger。服務(wù)本身是使用Docker部署到生產(chǎn)環(huán)境的。

如何設(shè)置初始Spring引導(dǎo)結(jié)構(gòu)

springboot是一個旨在簡化新服務(wù)創(chuàng)建的框架。對于最簡單的用例,所需的庫已經(jīng)捆綁在所謂的spring starters中的fitting組合和版本中。我們不必將應(yīng)用程序部署到應(yīng)用程序服務(wù)器中,相反,我們可以獨(dú)立運(yùn)行應(yīng)用程序或在Docker容器中運(yùn)行應(yīng)用程序,因?yàn)閼?yīng)用程序已經(jīng)包含服務(wù)器。

為了創(chuàng)建一個簡單的REST服務(wù),只需要幾行代碼。從一個可用的Spring啟動示例或Spring初始化器開始(http://start.spring.io),我們只需要添加一個javadto和注釋一個控制器,我們就有了第一個端點(diǎn)。

清單1

@RestController
public class RegistrationController {

   @RequestMapping(method = RequestMethod.POST,
                   value = "/register",
                   produces = APPLICATION_JSON_VALUE)
   public UserData register(@RequestBody User user) {
       ...
       if(usernameAlreadyExists) {
           throw new IllegalArgumentException("error.username");
       }
       ...
       return new UserData(...);
   }
   
   @ExceptionHandler
   void handleIllegalArgumentException(
                     IllegalArgumentException e,
                     HttpServletResponse response) throws IOException {

       response.sendError(HttpStatus.BAD_REQUEST.value());

   }
}

清單1顯示了一個控制器??刂破靼ㄓ糜谧缘姆椒ǎ摲椒捎蒔OST請求觸發(fā)。該方法處理JSON并返回JSON。從JSON到j(luò)avadto的轉(zhuǎn)換對Java開發(fā)人員來說是完全透明的,反之亦然。解析器的配置由springboot處理。彈簧靴支持Maven和Gradle。在Gradle的情況下,bootRun命令將啟動服務(wù)。

清單2

...

public class User {

   private String mail;
   private String password;
   private String lastName;
   private String name;
   private String address;

   public Registration() {}

   //... getter and setter
}

清單3展示了SpringBoot的另一個重要概念。在springboot中,只要在主類中添加一個簡單的注釋,就可以完成應(yīng)用程序的許多擴(kuò)展。注釋背后的底層基礎(chǔ)結(jié)構(gòu)是隱藏的。這很好,因?yàn)榭梢栽趯?shí)現(xiàn)業(yè)務(wù)邏輯而不是技術(shù)上投入更多的時間,但有時spring引導(dǎo)特性背后的魔力可能會很可怕,調(diào)試意外行為可能需要很多時間。注解@SpringBootApplication足以在嵌入式tomcat中啟動應(yīng)用程序。至少對于小型服務(wù)來說,通過混合使用XML片段、注釋和代碼來設(shè)置應(yīng)用程序上下文的復(fù)雜性已經(jīng)消失了。關(guān)于spring作為一個沉重而復(fù)雜的框架的舊印象已經(jīng)不再突出。從本例中啟動服務(wù)后,POST調(diào)用可以觸發(fā)注冊。清單4顯示了一個簡單的示例。

清單3

...
@SpringBootApplication
public class Application {

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

清單4

curl -X POST -H "Content-Type: application/json"
   http://localhost:8080/register -d
      '{
          "mail": "test@test.de",
          "password": "password",
          "lastName": "lastName",
          "name": "name",
          "address": "somewhere"
       }'

springboot提供了一種將異常映射到HTTP狀態(tài)碼的簡單方法。這樣我們就可以很容易地保證某種類型的異??偸菍?dǎo)致相同的錯誤代碼。在本例中(清單1),異常處理程序捕獲異常并返回符合HTTP標(biāo)準(zhǔn)的響應(yīng)。參數(shù)錯誤的請求不會導(dǎo)致500錯誤,相反,將返回一個400狀態(tài)代碼,其中包含有用的錯誤ID。清單5顯示了這樣一個響應(yīng)。當(dāng)然,我們可以用同樣的方法實(shí)現(xiàn)GET、PUT、DELETE請求或處理XML而不是JSON。從一個模板、一個現(xiàn)有的示例或初始化器開始,我們可以立即開始編寫代碼。整個基礎(chǔ)設(shè)施,如JAR的打包、HTTP服務(wù)器的啟動、庫的設(shè)置和其他初始化工作都從一開始就得到了解決。新rest服務(wù)的初始設(shè)置需要幾分鐘。

清單5

{
      "timestamp":1458746952449,
      "status":400,
      "error":"Bad Request",
      "exception":"java.lang.IllegalArgumentException",
      "message":"error.username",
      "path":"/register"
   }

如何保護(hù)restapi

因?yàn)槲覀兊臏y試服務(wù)接受用戶注冊,所以我們必須考慮數(shù)據(jù)的保護(hù)。在用戶可以檢索其數(shù)據(jù)之前,他必須對自己進(jìn)行授權(quán)。在休息服務(wù)的世界里,古典意義上的會話是不存在的。每個呼叫都必須經(jīng)過授權(quán)才能訪問資源。

有幾種做法很常見。通常,服務(wù)受基本身份驗(yàn)證的保護(hù)。Basic Auth要求客戶端在每個請求中發(fā)送用戶名和密碼(Base64編碼為頭信息的一部分)。嗅探器可以利用這些信息來授權(quán)他的通話。即使我們將通過SSL保護(hù)我們的服務(wù),我們也認(rèn)為Basic Auth不是我們服務(wù)的正確方法。另一種方法是使用令牌,它將隨每個請求而更改。成功的請求將返回下一個令牌和響應(yīng)。在這種情況下,并行請求或錯誤很容易導(dǎo)致注銷。這就是為什么我們決定使用OAuth2.0。OAuth2.0僅在初始登錄時使用密碼。OAuth登錄將返回2個令牌。以后的請求必須使用第一個令牌(訪問令牌)執(zhí)行。此令牌在給定的時間段內(nèi)替換密碼。如果有人能夠攔截流量,他可以在該時間段內(nèi)使用該令牌,直到令牌過期。一旦令牌過期,客戶機(jī)就可以使用第二個令牌(刷新令牌)檢索新令牌。

這個概念并不強(qiáng)迫客戶機(jī)一直發(fā)送真正的密碼,并行執(zhí)行調(diào)用也是可行的。即使訪問令牌過期,客戶端也可以通過使用刷新令牌確保用戶永久登錄。發(fā)送給客戶機(jī)的令牌需要持久化,以便將客戶機(jī)發(fā)送的令牌與生成的令牌進(jìn)行比較。在我們的用例中,我們希望去掉任何數(shù)據(jù)庫,因?yàn)檫@個服務(wù)只是包含真正邏輯的其他微服務(wù)的包裝。

為了解決這個問題,我們有三個選擇。我們可以使用內(nèi)存中的數(shù)據(jù)庫,它在多個實(shí)例之間共享。第二種選擇是使用負(fù)載平衡,它總是將來自會話的所有請求發(fā)送到本地(例如內(nèi)存中)存儲令牌的同一實(shí)例。第一種選擇將導(dǎo)致不必要的努力,第二種選擇打破了云本地微服務(wù)架構(gòu)的整體概念,因?yàn)檫@樣我們就不再有無狀態(tài)的應(yīng)用程序了。每次停機(jī)都意味著客戶注銷。所以我們決定用另一種方法。OAuth之上的JWT(jsonwebtokens)擴(kuò)展允許在不存儲令牌的情況下進(jìn)行授權(quán)。訪問令牌不僅僅是隨機(jī)生成的,而是使用私鑰對用戶ID、到期日期和其他元云本機(jī)進(jìn)行簽名,并作為Oauth令牌添加到頭信息中。這樣每個實(shí)例都可以在不存儲令牌的情況下驗(yàn)證令牌的有效性并檢索用戶信息,并且信息是加密的。JWT完全符合OAuth格式,這意味著所有oauth2客戶機(jī)都應(yīng)該能夠使用JWT,即使不知道該令牌是JWT令牌而不是經(jīng)典的oauth2.0令牌。格式保持不變,令牌只是稍微長一點(diǎn)。在我們的例子中,客戶機(jī)不必做任何需要的更改,即使在REST服務(wù)中,所需的自適應(yīng)也是最小的。不是將接收到的令牌與存儲的令牌進(jìn)行比較,而是調(diào)用JWT存儲庫來驗(yàn)證令牌。存儲庫解密令牌,其行為與使用數(shù)據(jù)庫的存儲庫相同。令牌包含用戶ID,但不包含用戶數(shù)據(jù)本身。這意味著用戶數(shù)據(jù)的存儲必須獨(dú)立解決。在我們的例子中,服務(wù)通過rest將用戶數(shù)據(jù)路由到用戶服務(wù)。清單6顯示了將OAuth2.0與JWT結(jié)合使用所需的Spring引導(dǎo)配置。它還顯示了一些額外的配置選項(xiàng)。

清單6

@Configuration
public class OAuth2ServerConfiguration {
   ...
   @Bean
   public JwtAccessTokenConverter getTokenConverter() {
       JwtAccessTokenConverter tokenConverter = new JwtAccessTokenConverter();
       //  for asymmetric signing/verification use  
       //  tokenConverter.setKeyPair(...);
       tokenConverter.setSigningKey("aTokenSigningKey");
       tokenConverter.setVerifierKey("aTokenSigningKey");
       return tokenConverter;
   }
   ...
   @Configuration
   @EnableResourceServer
   protected static class ResourceServerConfiguration extends
           ResourceServerConfigurerAdapter {

       ...
       @Override
       public void configure(HttpSecurity http) throws Exception {

           http.authorizeRequests()
                   .antMatchers("/register/**")
                   .permitAll()
                   .antMatchers("/user/**")
                   .access("#oauth2.hasScope('read')");
       }

   }

   @Configuration
   @EnableAuthorizationServer
   protected static class AuthorizationServerConfiguration extends
           AuthorizationServerConfigurerAdapter {

       ...
       @Override
       public void configure(ClientDetailsServiceConfigurer clients)
           throws Exception {
               clients
                   .inMemory()
                   .withClient("aClient")
                   .authorizedGrantTypes("password", "refresh_token")
                   .authorities("USER")
                   .scopes("read", "write")
                   .resourceIds(RESOURCE_ID)
                   .secret("aSecret");
       }
       ...
   }

}

OAuth2.0支持第三方的授權(quán)。即使在這種情況下未使用此功能,我們也可以將REST服務(wù)的使用限制為某些客戶機(jī)或合作伙伴。在登錄階段,不僅要傳輸用戶的用戶名和密碼,還需要客戶端和客戶端密碼。在我們的案例中,客戶端是不同的應(yīng)用程序。springboot提供了一個簡單的角色和權(quán)限模型。但在本教程中我們將不詳細(xì)介紹。在我們的示例中,注冊是不安全的,但是只有在成功登錄之后才能訪問用戶數(shù)據(jù)。在本例中,登錄是檢索訪問OAuth-2.0令牌的請求。清單7顯示了使用OAuth令牌登錄后的登錄和用戶信息檢索。清單8是OAuth登錄的可能響應(yīng),包括訪問令牌和刷新令牌。為了完成這個示例,清單9顯示了控制器。

清單7

curl -vu aClient:aSecret -X POST 'http://localhost:8080/oauth/token?username=test@test.de&password=aPassword&grant_type=password'
curl -i -H "Authorization: Bearer eyJh...Fpao" http://localhost:8080/user

清單8

{ "access_token":"eyJh...Fpao",
 "token_type":"bearer",
 "refresh_token":"eyJh...4clI",
 "expires_in":43199,
 "scope":"read write",
 "jti":"6e0...b31"
}

清單9

@RestController
public class UserController {

   @RequestMapping(method = RequestMethod.GET,
                   value = "/user",
                   produces = APPLICATION_JSON_VALUE)
   public UserData getUser() {

       Authentication auth =  
                SecurityContextHolder.getContext().getAuthentication();
       String userid = auth.getName();
       ...
   }
}

如何記錄restapi

restapi的可維護(hù)文檔需要盡可能接近代碼,并且在理想情況下,應(yīng)該從代碼生成(或者應(yīng)該從API描述生成代碼)。Swagger是一個功能強(qiáng)大的框架,它包括圍繞API文檔主題的多個工具和庫。例如,工具集的一部分是從API描述生成代碼的工具。

但對于我們的用例來說更重要的是lib,它在運(yùn)行時根據(jù)代碼生成JSON文檔。另一個工具用JSON文檔創(chuàng)建可執(zhí)行的HTML文檔。即使使用默認(rèn)設(shè)置,Swagger庫通常也能提供很好的結(jié)果。為RESTAPI添加可執(zhí)行文檔可以使用單個注釋(@EnableSwagger2)完成。清單10就是一個例子。默認(rèn)情況下,Swagger搜索應(yīng)用程序中所有現(xiàn)有的REST定義。在清單10中,我們還可以看到如何將API文檔限制為現(xiàn)有API的一個子集。在這種情況下,文檔中將只顯示用戶信息和登錄名。此外,我還向文檔中添加了標(biāo)題和版本信息。

清單10

@Configuration
@EnableSwagger2
@EnableAutoConfiguration
public class SwaggerConfig {

   @Bean
   public Docket api() {
       return new Docket(DocumentationType.SWAGGER_2)
              .select()
              .apis(RequestHandlerSelectors.any())
              .paths(PathSelectors.regex("/user.*|/register.*|/oauth/token.*"))    
              //PathSelectors.any() for all
              .build().apiInfo(apiInfo());
   }

   private ApiInfo apiInfo() {
       ApiInfo apiInfo = new ApiInfo(
               "aTitle",
               "aDescription",
               "aVersion",
               "a url to terms and services",
               "aContact",
               "a License of API",
               "a license URL");
       return apiInfo;
   }

}

springboot可以自動使用其他端點(diǎn)來豐富自定義API,例如健康檢查、度量或調(diào)試信息。這些端點(diǎn)將由Swagger自動檢測(如果不受限制)。這是非常強(qiáng)大的,但是您應(yīng)該考慮保護(hù)這些信息(例如,通過將其移動到防火墻后面的其他端口)。

圖1和圖2是清單10中配置的結(jié)果。Swagger幾乎檢測所有端點(diǎn)。OAuth2.0的配置不是在控制器中完成的,而是在配置類中完成的。Swagger無法完全檢測OAuth所需的所有信息。這就是為什么我在清單11中的方法中添加了頭描述。如果沒有這個描述,頭信息將不會顯示在可執(zhí)行的Swagger文檔中(圖2)。圖3顯示了包含頭信息的結(jié)果。通過向靜態(tài)資源的文件夾中添加自定義UI,可以覆蓋Swagger UI。

清單11

@ApiImplicitParams({
           @ApiImplicitParam(name = "Authorization",
           value = "Bearer access_token",
           required = true,
           dataType = "string",
           paramType = "header"),
   })
   @RequestMapping(method = RequestMethod.GET,
                   value = "/user",
                   produces = APPLICATION_JSON_VALUE)
   public User getUser() {

       Authentication auth =  
               SecurityContextHolder.getContext().getAuthentication();
               User aUser =
                   userRepository.getUser(auth.getName());
       if(auth != null && aUser != null) {
           return aUser;
       } else {
           throw new IllegalArgumentException("error.username");
       }
   }

生成的招搖UI  沒有標(biāo)題信息的API文檔  包含標(biāo)題信息的API文檔。

如何將應(yīng)用程序嵌入Docker容器

將自動運(yùn)行的jar嵌入Docker容器很簡單。彈簧靴支持Maven和Gradle。因?yàn)镚radle更精簡、更易于擴(kuò)展、更易于使用和更快,所以我更喜歡Gradle用于我所有的項(xiàng)目。清單12展示了如何使用Gradle Docker插件將Spring引導(dǎo)應(yīng)用程序打包到Docker容器中。Docker容器只需要一個Java運(yùn)行時來運(yùn)行jar。

清單13是一個簡單的Dockerfile。Docker容器基于另一個容器,該容器已經(jīng)包含Java運(yùn)行時,并將我們的應(yīng)用程序添加為Jar。如果您啟動容器,那么應(yīng)用程序?qū)⒈O(jiān)聽端口8080,并且在我們的示例中,相同的端口將公開。清單14展示了如何構(gòu)建和啟動容器。

清單12

...
buildscript {
   ...
   dependencies {
       ...
       classpath 'se.transmode.gradle:gradle-docker:1.2'
   }
}
...
apply plugin: 'docker'

group = 'agroup'
...
task buildDocker(type: Docker, dependsOn: build) {
   //push = true
   applicationName = jar.baseName
   println('Application:' + applicationName)
   println('Group:' + project.group)
   dockerfile = file('Dockerfile')
   doFirst {
       copy {
           from jar
           into stageDir
       }
   }
}

...

清單13

FROM frolvlad/alpine-oraclejdk8:latest
MAINTAINER <YOUR MAIL>
EXPOSE 8080
ADD spring-boot-app-service-example.jar /app/spring-boot-app-service-example.jar
ENTRYPOINT java -jar /app/spring-boot-app-service-example.jar --server.port=8080

清單14

$ ./gradlew buildDocker
$ docker run -p 8080:8080 -t agroup/spring-boot-app-service-example

配置

我們已經(jīng)了解了如何建立基于REST的微服務(wù),包括身份驗(yàn)證、文檔以及如何將其嵌入Docker容器。配置Spring引導(dǎo)應(yīng)用程序的最簡單方法是屬性文件(應(yīng)用程序?qū)傩?在應(yīng)用程序的資源文件夾中。您可以在應(yīng)用程序啟動時重寫屬性(如果需要的話),例如通過java-jar這樣的命令示例.jar–spring.config.location=/configuration.屬性。更改Docker容器中應(yīng)用程序的配置可以通過將配置文件裝入容器來完成。這可能是部署自動化的一部分。在bean中使用配置可以通過使用單個注釋來完成。

準(zhǔn)備好應(yīng)用云

將應(yīng)用程序嵌入Docker容器后,可以使用所有可用的Docker工具將應(yīng)用程序部署到云中(例如Kubernetes)。彈簧靴由樞軸驅(qū)動。Pivotal是企業(yè)PAAS解決方案Pivotal Cloud Foundry背后的驅(qū)動程序。SpringBoot應(yīng)用程序可以很容易地集成到云計(jì)算解決方案中,而無需Docker。除了一些元信息,這些應(yīng)用程序可以部署到CloFoundrydry而無需修改。Cloud Foundry可以作為開放源代碼或不同的企業(yè)版本(例如,F(xiàn)oundry)提供,并且可以安裝在您的數(shù)據(jù)中心中。還有公共云代工產(chǎn)品(如Pivotal Web Services和IBM Bluemix)。

結(jié)論

對于大多數(shù)用例,springboot簡化了基于Java的微服務(wù)的構(gòu)建。與Dropwizard等框架不同,它更易于使用,并提供了更豐富的功能集。springboot提供了大量的附加庫和集成,比如Ribbon、Zuul、Hystrix,以及與MongoDB、Redis、GemFire、Elasticsearch、Cassandra或Hazelcast等數(shù)據(jù)庫的集成。

Maven和Gradle為Java開發(fā)人員提供了強(qiáng)大且廣泛支持的構(gòu)建系統(tǒng),與Play框架等框架的專用構(gòu)建系統(tǒng)相比,這些構(gòu)建系統(tǒng)更常見,更容易集成到現(xiàn)有結(jié)構(gòu)中。與經(jīng)典的Web應(yīng)用程序相比,springboot是精簡的。對于大多數(shù)項(xiàng)目,向項(xiàng)目中添加依賴項(xiàng)足以從一開始就獲得良好的結(jié)果,而無需調(diào)整默認(rèn)配置。

但并非所有的一切都是完美的春季開機(jī)生態(tài)系統(tǒng)。如果要調(diào)整庫的設(shè)置,很可能還必須調(diào)整其他庫的設(shè)置。其中一個例子是OAuth的集成。Swagger沒有自動檢測到標(biāo)題信息。為了嵌入Hystrix,您只需添加兩個注釋,依賴項(xiàng)和所有度量都會被自動檢測到。例如,如果您更改了健康檢查的URL,那么您也必須更改Hystrix的配置。調(diào)試隱藏在底層Spring魔術(shù)中的這些問題可能需要時間,但是Spring Boot提供的優(yōu)勢是值得的。