适用于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 port

server:

port: 8080

---

datasources:

default:

url: jdbc:postgresql://localhost/postgres

username: postgres

password: yourpassword

driverClassName: org.postgresql.Driver

connectionTimeout: 4000

jpa:

default:

packages-to-scan:

- 'org.acme.domain'

properties:

hibernate:

hbm2ddl:

auto: update

show_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")

@NotNull

private LocalDate publishDate;

@Column(name="TITLE")

@NotNull

private String title;

@Column(name="AUTHOR_FIRST")

@NotNull

private String authorFirst;

@Column(name="AUTHOR_LAST")

@NotNull

private 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;

}

@Override

public 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 {

@NotNull

private LocalDate publishDate;

@NotNull

@NotBlank

private String title;

@NotNull

@NotBlank

private String authorFirst;

@NotNull

@NotBlank

private 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.

*/

@Singleton

public class BookRepositoryImpl implements BookRepository {

@PersistenceContext

private EntityManager entityManager;

public BookRepositoryImpl(@CurrentSession EntityManager entityManager) {

this.entityManager = entityManager;

}

@Override

@Transactional

public 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

@Transactional

public 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

@Transactional

public 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<>();

@BeforeClass

public static void setupServer() {

server = ApplicationContext.run(EmbeddedServer.class);

client = server.getApplicationContext().createBean(HttpClient.class, server.getURL());

}

@AfterClass

public static void stopServer() {

if (server != null) {

server.stop();

}

if (client != null) {

client.stop();

}

}

@Test

public 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());

}

@Test

public void testBookRetrieve() {

request = HttpRequest.GET("/books");

List<Book> books = client.toBlocking().retrieve(request, Argument.of(List.class, Book.class));

// Populate a book instance for later

for(Book b:books){

book = b;

}

assertEquals(2, books.size());

}

@Test

public void testBookOperations() {

request = HttpRequest.GET("/books");

List<Book> books = client.toBlocking().retrieve(request, Argument.of(List.class, Book.class));

// Populate a book instance for later

for(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 later

for(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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值