适用于Java EE / Jakarta EE开发人员的Micronaut
城鎮中有一個名為Micronaut的新微服務框架。 在本文中,我將從Java EE / Jakarta EE的角度討論如何冒險使用Micronaut框架。 我是Java EE開發人員,因此使用諸如Eclipse MicroProfile之類的解決方案開發微服務更接近我的專業知識,但是Micronaut吸引了我的注意力,因為它具有以下功能:
–使用Java,Groovy或Kotlin開發
–易于通過Spock或JUnit進行測試..完全集成的測試
–嵌入式服務器和編譯時HTTP客戶端
–易于打包的Docker
–快速啟動時間,低內存消耗
–完全反應
作為內心的企業開發人員,我的第一個想法通常是數據庫,因為我編寫的大多數應用程序都使用RDBMS。 我發現將Micronaut與RDBMS一起使用的示例數量很少,因此我認為為該用例創建另一個示例可能對我有用。 在此示例中,我使用PostgreSQL。 但是,大多數其他RDBMS也受支持。 本文無意對安裝Micronaut或利用所有許多Micronaut功能進行完整說明。 相反,它是那些希望開始使用帶有關系數據庫的Micronaut的入門指南……尤其適合那些具有Java EE / Jakarta EE背景的人。
在我的特殊情況下,我對快速完善可測試,可擴展和高效的微服務感興趣。 盡管我可以使用MicroProfile或標準Java EE做到這一點,但我認為學習新知識并具有使用Groovy或Kotlin的能力將很有趣。 我還想將Java EE / Jakarta EE放在上面……所以我正在使用JPA來處理數據。 許多Micronaut示例都使用Groovy和GORM來實現持久性……但是我可能不會在我的任何應用程序中使用它。
該示例是使用Apache NetBeans 9.0和Micronaut隨附的命令行界面(CLI)開發的 。 此特定示例是針對Micronaut 1.0.0.M4編寫的。 在這種情況下,我保持簡單,只使用一個基本數據庫表在PostgreSQL數據庫中進行持久化。
首先,我通過發出以下命令利用CLI創建了一個應用程序:
mn create-app org.acme.books --features hibernate-jpa,jdbc-tomcat這只是在名為“ books”的目錄中為我的應用程序創建了一個框架,Application.java主類將放置在org.acme.books包中。 默認情況下,應用程序支持一些基本功能,但是在這種情況下,我添加了對Tomcat連接池的支持。 通過Java Persistence API(JPA)創建數據庫連接時,將利用此功能。 默認應用程序也會在支持Gradle構建系統的情況下生成。 因此,將創建一個build.gradle,即將在其中進行依賴項管理的文件。 請注意,也可以使用Apache Maven構建系統來生成應用程序,但是在Micronaut 1.0.0.M4下運行Maven項目時遇到了問題,因此在這個示例中我堅持使用Gradle。
如果使用Apache NetBeans 9.0,則可以安裝“ Groovy and Grails”和“ Gradle”插件(當前在NetBeans 8.2插件中心提供)以提供打開項目的支持。 一旦完成,就可以在NetBeans中打開項目并開始開發。 安裝插件并在Apache NetBeans中打開項目后,完整的項目結構應如下圖所示:
為了提供對PostgreSQL數據庫的支持,我在build.gradle中添加了依賴項:
compile group: 'org.postgresql', name: 'postgresql', version: '42.2.5'接下來,我打開了application.yml文件,并為該應用程序添加了數據源。 這是替換傳統Java EE應用程序中的persistence.xml的文件。 另外,通過此文件添加了JPA支持,指示哪個包包括實體類以及Hibernate的配置。 端口8080也已設置,因為默認情況下Micronaut將選擇一個隨機端口來啟動服務器。 application.xml的完整資源如下:
micronaut:application:name: books#Uncomment to set server portserver:port: 8080---datasources:default:url: jdbc:postgresql://localhost/postgresusername: postgrespassword: yourpassworddriverClassName: org.postgresql.DriverconnectionTimeout: 4000jpa:default:packages-to-scan:- 'org.acme.domain'properties:hibernate:hbm2ddl:auto: updateshow_sql: true現在配置已不復存在,我可以開始有趣的部分了……開發。 在此示例中,我創建了一項基本服務,該服務允許用戶在BOOK表中創建,讀取,更新或刪除記錄。 org.acme包中自動生成的Application類,用于啟動服務。
package org.acme;import io.micronaut.runtime.Micronaut;public class Application {public static void main(String[] args) {Micronaut.run(Application.class);}}要開始開發,請在應用程序中創建兩個用于組織源代碼的軟件包。 首先,創建org.acme.domain,它將包含實體類。 接下來,創建org.acme.book,其中將包含實現類。 在org.acme.domain包中創建一個Book.java類,它將是包含數據庫標準JPA映射的實體類。 在這種情況下,請注意,我將java.time.LocalDate用于日期字段,并將數據庫序列生成器用于主鍵填充。 來源如下:
package org.acme.domain;import java.time.LocalDate;import javax.persistence.Column;import javax.persistence.Entity;import javax.persistence.GeneratedValue;import javax.persistence.GenerationType;import javax.persistence.Id;import javax.persistence.SequenceGenerator;import javax.persistence.Table;import javax.validation.constraints.NotNull;/*** JPA Mappings for the BOOK database table.*/@Entity@Table(name="BOOK")public class Book {@Id@GeneratedValue(strategy=GenerationType.SEQUENCE,generator="book_generator")@SequenceGenerator(name="book_generator",sequenceName="book_s", allocationSize=1)private Long id;@Column(name="PUBLISH_DATE")@NotNullprivate LocalDate publishDate;@Column(name="TITLE")@NotNullprivate String title;@Column(name="AUTHOR_FIRST")@NotNullprivate String authorFirst;@Column(name="AUTHOR_LAST")@NotNullprivate String authorLast;private Long pages;public Book(){}public Book(@NotNull Long id, @NotNull LocalDate publishDate, @NotNull String title, String authorFirst, String authorLast, Long pages){this.id = id;this.publishDate = publishDate;this.title = title;this.authorFirst = authorFirst;this.authorLast = authorLast;this.pages = pages;}public Book(@NotNull LocalDate publishDate, @NotNull String title, String authorFirst, String authorLast, Long pages){this.publishDate = publishDate;this.title = title;this.authorFirst = authorFirst;this.authorLast = authorLast;this.pages = pages;}/*** @return the id*/public Long getId() {return id;}/*** @param id the id to set*/public void setId(Long id) {this.id = id;}/*** @return the publishDate*/public LocalDate getPublishDate() {return publishDate;}/*** @param publishDate the publishDate to set*/public void setPublishDate(LocalDate publishDate) {this.publishDate = publishDate;}/*** @return the title*/public String getTitle() {return title;}/*** @param title the title to set*/public void setTitle(String title) {this.title = title;}/*** @return the authorFirst*/public String getAuthorFirst() {return authorFirst;}/*** @param authorFirst the authorFirst to set*/public void setAuthorFirst(String authorFirst) {this.authorFirst = authorFirst;}/*** @return the authorLast*/public String getAuthorLast() {return authorLast;}/*** @param authorLast the authorLast to set*/public void setAuthorLast(String authorLast) {this.authorLast = authorLast;}/*** @return the pages*/public Long getPages() {return pages;}/*** @param pages the pages to set*/public void setPages(Long pages) {this.pages = pages;}@Overridepublic String toString() {return "Book{" +"id=" + id +", publishDate='" + publishDate + '\'' +", title='" + title + '\'' +", authorFirst='" + authorFirst + '\'' +", authorLast='" + authorLast + '\'' +", pages='" + pages +'}';}}在Micronaut應用程序中,需要將HTTP請求和響應封裝在Serializable類中進行處理,因此,生成一些簡單的“純舊Java對象”(POJO)來封裝將在數據庫操作中使用的數據是有意義的。 在同一個org.acme.domain包中,我創建了兩個此類,BookSaveOperation.java和BookUpdateOperation.java。 這些類將定義將數據從HTTP請求傳遞到控制器類所需的字段。 BookSaveOperation.java的源如下(有關完整源,請參見GitHub存儲庫):
package org.acme.domain;import java.time.LocalDate;import javax.validation.constraints.NotBlank;import javax.validation.constraints.NotNull;/**** @author Josh Juneau*/public class BookSaveOperation implements java.io.Serializable {@NotNullprivate LocalDate publishDate;@NotNull@NotBlankprivate String title;@NotNull@NotBlankprivate String authorFirst;@NotNull@NotBlankprivate String authorLast;private Long pages;public BookSaveOperation(){}public BookSaveOperation(LocalDate publishDate, String title,String authorFirst, String authorLast, Long pages){this.publishDate = publishDate;this.title = title;this.authorFirst = authorFirst;this.authorLast = authorLast;this.pages = pages;}// ...// getters and setters// ...}應用程序業務邏輯發生在一個類中,該類非常類似于EJB或DAO實現,并且該類必須實現定義了每個業務邏輯方法的接口。 在這種情況下,我創建了一個接口org.acme.book.BookRepository.java,并定義了一些標準的操作方法:
package org.acme.book;import java.time.LocalDate;import java.util.List;import java.util.Optional;import org.acme.domain.Book;/****/public interface BookRepository {Book save(LocalDate publishDate, String title, String authorFirst, String authorLast, Long pages);Optional<Book> findById(Long id);void deleteById(Long id);List<Book> findAll();int update(Long id, LocalDate publishDate, String title, String authorFirst, String authorLast, Long pages);}接下來,在名為org.acme.book.BookRepositoryImpl.java的類中實現該接口,并注釋為@Singleton。 由于這是將實現業務邏輯的類,因此請注入一個PersistenceContext,該持久性上下文提供將用于執行數據庫操作的JPA EntityManager。 只需實現BookRepository界面中概述的每個操作,并使用@Transactional(io.micronaut.spring.tx.annotation.Transactional)進行標記,就意味著僅對那些不會修改任何數據的方法進行只讀。 BookRepositoryImpl.java的源如下:
package org.acme.book;import io.micronaut.configuration.hibernate.jpa.scope.CurrentSession;import io.micronaut.spring.tx.annotation.Transactional;import java.time.LocalDate;import java.util.List;import java.util.Optional;import javax.inject.Singleton;import javax.persistence.EntityManager;import javax.persistence.PersistenceContext;import org.acme.domain.Book;/*** Business logic for the service.*/@Singletonpublic class BookRepositoryImpl implements BookRepository {@PersistenceContextprivate EntityManager entityManager;public BookRepositoryImpl(@CurrentSession EntityManager entityManager) {this.entityManager = entityManager;}@Override@Transactionalpublic Book save(LocalDate publishDate, String title, String authorFirst, String authorLast, Long pages) {Book book = new Book(publishDate, title, authorFirst, authorLast, pages);entityManager.persist(book);return book;}@Override@Transactional(readOnly = true)public Optional<Book> findById(Long id) {return Optional.ofNullable(entityManager.find(Book.class, id));}@Transactional(readOnly = true)public List<Book> findAll() {return entityManager.createQuery("SELECT b FROM Book b", Book.class).getResultList();}@Override@Transactionalpublic int update(Long id, LocalDate publishDate, String title, String authorFirst, String authorLast, Long pages) {return entityManager.createQuery("UPDATE Book b SET publishDate = :publishDate, title = :title, " +"authorFirst = :authorFirst, authorLast = :authorLast, pages = :pages where id = :id").setParameter("publishDate", publishDate).setParameter("title", title).setParameter("authorFirst", authorFirst).setParameter("authorLast", authorLast).setParameter("pages", pages).setParameter("id", id).executeUpdate();}@Override@Transactionalpublic void deleteById(Long id) {findById(id).ifPresent(book -> entityManager.remove(book));}}為了從Java EE角度解釋Micronaut應用程序基礎結構,我將比較該實現與一個簡單的JAX-RS應用程序。 Micronaut利用io.micronaut.http.annotation.Controller類來執行服務的請求-響應處理。 這非常類似于JAX-RS控制器類,但有一些細微的差異。 這讓我想起了Eclipse Krazo項目或Java EE的MVC 1.0。 例如,Micronaut而不是使用JAX-RS注釋javax.ws.rs.GET,javax.ws.rs.POST或javax.ws.rs.Path注釋方法,而是使用io.micronaut.http.annotation.Get和io.micronaut.http.annotation.Post等。 每個方法的URI路徑都可以通過@ Get,@ Post,@ Put,@ Delete批注直接聲明。 每個控制器類將實現服務的功能并處理請求-響應生命周期。 通過@Inject批注或構造函數注入將用于持久性的業務邏輯(包含在BookRepositoryImpl類中)注入到控制器類中。 在此示例的源代碼中,使用了構造函數注入。
package org.acme.book;import org.acme.domain.Book;import io.micronaut.http.HttpHeaders;import io.micronaut.http.HttpResponse;import io.micronaut.http.annotation.Body;import io.micronaut.http.annotation.Controller;import io.micronaut.http.annotation.Delete;import io.micronaut.http.annotation.Get;import io.micronaut.http.annotation.Post;import io.micronaut.http.annotation.Put;import io.micronaut.validation.Validated;import javax.validation.Valid;import java.net.URI;import java.util.List;import org.acme.domain.BookSaveOperation;import org.acme.domain.BookUpdateOperation;@Validated@Controller("/books")public class BookController {protected final BookRepository bookRepository;public BookController(BookRepository bookRepository) {this.bookRepository = bookRepository;}@Get("/")public List<Book> list() {return bookRepository.findAll();}@Put("/")public HttpResponse update(@Body @Valid BookUpdateOperation operation) {bookRepository.update(operation.getId(), operation.getPublishDate(),operation.getTitle(), operation.getAuthorFirst(), operation.getAuthorLast(), operation.getPages());return HttpResponse.noContent().header(HttpHeaders.LOCATION, location(operation.getId()).getPath());}@Get("/{id}")Book show(Long id) {return bookRepository.findById(id).orElse(null);}@Delete("/{id}")HttpResponse delete(Long id) {bookRepository.deleteById(id);return HttpResponse.noContent();}@Post("/")HttpResponse<Book> save(@Body @Valid BookSaveOperation operation) {Book book = bookRepository.save(operation.getPublishDate(), operation.getTitle(),operation.getAuthorFirst(), operation.getAuthorLast(), operation.getPages());return HttpResponse.created(book).headers(headers -> headers.location(location(book)));}protected URI location(Book book) {return location(book.getId());}protected URI location(Long id) {return URI.create("/books/" + id);}}測試應用
Micronaut可以使用Spock或JUnit以及嵌入式服務器輕松進行測試,從而可以輕松地為每個控制器創建測試。 在這種情況下,我利用JUnit來測試應用程序。 我在名為org.acme.BookControllerTest的項目的測試文件夾內創建了一個測試類。
package org.acme;import io.micronaut.context.ApplicationContext;import io.micronaut.core.type.Argument;import io.micronaut.http.HttpHeaders;import io.micronaut.http.HttpRequest;import io.micronaut.http.HttpResponse;import io.micronaut.http.HttpStatus;import io.micronaut.http.client.HttpClient;import io.micronaut.runtime.server.EmbeddedServer;import java.time.LocalDate;import java.util.ArrayList;import java.util.List;import org.acme.domain.Book;import org.acme.domain.BookSaveOperation;import org.acme.domain.BookUpdateOperation;import org.junit.AfterClass;import static org.junit.Assert.assertEquals;import org.junit.BeforeClass;import org.junit.Test;/*** Test cases for BookController*/public class BookControllerTest {private static EmbeddedServer server;private static HttpClient client;private? Book book;HttpRequest request;HttpResponse response;Long id;List<Long> bookIds = new ArrayList<>();@BeforeClasspublic static void setupServer() {server = ApplicationContext.run(EmbeddedServer.class);client = server.getApplicationContext().createBean(HttpClient.class, server.getURL());}@AfterClasspublic static void stopServer() {if (server != null) {server.stop();}if (client != null) {client.stop();}}@Testpublic void testInsertBooks() {request = HttpRequest.POST("/books", new BookSaveOperation(LocalDate.now(), "Java EE 8 Recipes", "Josh", "Juneau", new Long(750)));response = client.toBlocking().exchange(request);assertEquals(HttpStatus.CREATED, response.getStatus());request = HttpRequest.POST("/books", new BookSaveOperation(LocalDate.now(), "Java 9 Recipes", "Josh", "Juneau", new Long(600)));response = client.toBlocking().exchange(request);id = entityId(response, "/books/");assertEquals(HttpStatus.CREATED, response.getStatus());}@Testpublic void testBookRetrieve() {request = HttpRequest.GET("/books");List<Book> books = client.toBlocking().retrieve(request, Argument.of(List.class, Book.class));// Populate a book instance for laterfor(Book b:books){book = b;}assertEquals(2, books.size());}@Testpublic void testBookOperations() {request = HttpRequest.GET("/books");List<Book> books = client.toBlocking().retrieve(request, Argument.of(List.class, Book.class));// Populate a book instance for laterfor(Book b:books){book = b;}request = HttpRequest.PUT("/books/", new BookUpdateOperation(book.getId(),book.getPublishDate(),"Java 10 Recipes",book.getAuthorFirst(),book.getAuthorLast(),book.getPages()));response = client.toBlocking().exchange(request);assertEquals(HttpStatus.NO_CONTENT, response.getStatus());request = HttpRequest.GET("/books/" + book.getId());book = client.toBlocking().retrieve(request, Book.class);assertEquals("Java 10 Recipes", book.getTitle());testDelete();}public void testDelete(){request = HttpRequest.GET("/books");List<Book> books = client.toBlocking().retrieve(request, Argument.of(List.class, Book.class));// Populate a book instance for laterfor(Book b:books){request = HttpRequest.DELETE("/books/" + b.getId());response = client.toBlocking().exchange(request);assertEquals(HttpStatus.NO_CONTENT, response.getStatus());}}Long entityId(HttpResponse response, String path) {String value = response.header(HttpHeaders.LOCATION);if (value == null) {return null;}int index = value.indexOf(path);if (index != -1) {return Long.valueOf(value.substring(index + path.length()));}return null;}}考試邏輯導論
在運行@BeforeClass的方法中,將創建HTTP服務器和客戶端。 同樣,當測試完成執行時,將調用用@AfterClass注釋的方法,如果服務器正在運行,它將停止服務器。
在textInsertBooks()方法中,通過將填充了數據的新BookSaveOperation對象傳遞給可通過@Post命名的“ / books”路徑訪問的服務,來創建兩個新的書記錄。 在這種情況下,將調用控制器方法BookController.save()。 看一下save()方法,您可以看到該方法只是將BookSaveOperation的內容傳遞給BookRepository.save()業務方法(利用該接口),從而持久化對象。 最后,返回HttpResponse。
testBookRetrieve()方法調用包含@Get名稱的“ / books”路徑上可用的服務。 依次調用BookController.list()方法,該方法在BookRepository上執行findAll(),返回Book對象的List。
testBookOperations()方法負責對記錄進行更新。 首先,從BookController中檢索Book對象的列表,然后通過使用要更新的內容填充BookUpdateOperation對象,通過BookController.update()方法更新其中一本書。
**請記住,BookSaveOperation.java和BookUpdateOperation.java對象只是用于移動數據的POJO。
最后,調用testDelete()方法,該方法遍歷Book對象的List,通過對“ / books”路徑的服務調用來調用BookController.delete()方法,并調用指定為@Delete的方法。
要執行測試,只需在NetBeans中右鍵單擊該項目并選擇“ Test”,或使用命令行使用以下命令來調用
./gradlew test如果尚未創建數據庫表,則將為您生成該表。 請注意,您可能需要根據環境修改application.yml中的數據庫配置。
運行服務
Micronaut是獨立的,允許使用基于Netty構建的嵌入式服務器執行服務。 可以通過右鍵單擊Apache NetBeans中的項目并選擇“運行”來完成。 您也可以轉到命令行并使用以下命令進行調用:
./gradlew run您應該在終端或Apache NetBeans輸出面板的輸出中看到托管服務器的URL。
摘要
作為Java EE開發人員,我不得不說Micronaut確實有不同的開發方法。 它與Spring Boot或Grails并沒有太大區別,但又有足夠的區別,以至于我花了一些時間找到解決方法。 最后,我發現它是一個易于使用的框架,它具有快速開發服務或計劃任務的巨大潛力,同時仍然利用Java EE / Jakarta EE的一些概念。
我還沒有使用很多功能,例如通過Micronaut框架創建計劃任務,以及使用Groovy或Kotlin而不是Java進行開發。 我希望在以后的文章中繼續介紹對有興趣開始Micronaut之旅的Java EE和Jakarta EE開發人員的更多信息。
GitHub項目:https://github.com/juneau001/micronaut-books
翻譯自: https://www.javacodegeeks.com/2018/09/micronaut-for-java-ee-jakarta-ee-developers.html
總結
以上是生活随笔為你收集整理的适用于Java EE / Jakarta EE开发人员的Micronaut的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: linux系统系列教程电脑如何装linu
- 下一篇: 质高价低笔记本电脑可以购买么很便宜的笔记