https://mp.weixin.qq.com/s?__biz=MzU0MDEwMjgwNA==&mid=2247484965&idx=1&sn=ca6b847c65e5062036413ce203f77e97&chksm=fb3f1fdecc4896c805abffc1d4b294f2e10e7df7be02e20a95e6cfde2b8ae1ba705ced2a7008&scene=0&key=603c4c794d1e6753e5db1f9f14b6cc66b0ab45eaa6b2d94a7d2969275e5f606ac462a992e9841c07d7ddf86920207734839155714f7d9bba080fc36a92deabc821492123bccacd9b08b98cad7814bf5f&ascene=1&uin=MjgwMTEwNDQxNg%3D%3D&devicetype=Windows-QQBrowser&version=6103000b&lang=zh_CN&pass_ticket=9hOd6G19lRsZ4t2MwOpE8LWMk0vBDz2ra6SytTXe2yTgNcE5eevtRvju1kOKRf4r
https://github.com/reta/eclipse-microprofile-hammock
原文链接:https://dzone.com/articles/building-enterprise-java-applications-the-spring-w
作者:Andriy Redko
译者:xieed
通过在本教程中构建一个简单的RESTful web API,了解关于使用Java EE和Spring框架构建企业Java应用程序的更多信息。
我认为可以说Java EE在Java开发人员中获得了相当坏的名声。尽管多年来,它确实在各个方面都有所改善,甚至从Eclipse Foundation变成了JakartaEE,但它的苦味仍然相当强烈。另一方面,我们有Spring Framework(或者更好地反映现实,一个成熟的Spring Platform),这是一个出色的、轻量级的、快速的、创新的、高生产力的Java EE替代品。那么,为什么要为Java EE费心呢?我们将通过展示使用大多数Java EE规范构建现代Java应用程序是多么容易来回答这个问题。在这方面取得成功的关键因素是Eclipse Microprofile:J2EE的微服务时代。我们将要构建的应用程序是用于管理人员的RESTful web API;就这么简单。在Java中构建RESTful web服务的标准方法是使用JAX-RS 2.1 (JSR-370)。因此,CDI 2.0 (JSR-365)将负责依赖注入,而JPA 2.0 (JSR-317)将负责数据访问层。当然,Bean Validation 2.0 (JSR-380)正在帮助我们处理输入验证。我们唯一要依赖的非java EE规范是OpenAPI v3.0,它有助于提供关于RESTful web api的可用描述。那么,让我们从personentity域模型开始(省略getter和setter作为不太相关的细节):
-
@Entity
-
@Table(name = "people")
-
public class PersonEntity {
-
@Id @Column(length = 256)
-
private String email;
-
@Column(nullable = false, length = 256, name = "first_name")
-
private String firstName;
-
@Column(nullable = false, length = 256, name = "last_name")
-
private String lastName;
-
@Version
-
private Long version;
-
}
它只有一个绝对最小的属性集。JPA存储库非常简单,实现了一组典型的CRUD方法。
-
@ApplicationScoped
-
@EntityManagerConfig(qualifier = PeopleDb.class)
-
public class PeopleJpaRepository implements PeopleRepository {
-
@Inject @PeopleDb private EntityManager em;
-
@Override
-
@Transactional(readOnly = true)
-
public Optional<PersonEntity> findByEmail(String email) {
-
final CriteriaBuilder cb = em.getCriteriaBuilder();
-
final CriteriaQuery<PersonEntity> query = cb.createQuery(PersonEntity.class);
-
final Root<PersonEntity> root = query.from(PersonEntity.class);
-
query.where(cb.equal(root.get(PersonEntity_.email), email));
-
try {
-
final PersonEntity entity = em.createQuery(query).getSingleResult();
-
return Optional.of(entity);
-
} catch (final NoResultException ex) {
-
return Optional.empty();
-
}
-
}
-
@Override
-
@Transactional
-
public PersonEntity saveOrUpdate(String email, String firstName, String lastName) {
-
final PersonEntity entity = new PersonEntity(email, firstName, lastName);
-
em.persist(entity);
-
return entity;
-
}
-
@Override
-
@Transactional(readOnly = true)
-
public Collection<PersonEntity> findAll() {
-
final CriteriaBuilder cb = em.getCriteriaBuilder();
-
final CriteriaQuery<PersonEntity> query = cb.createQuery(PersonEntity.class);
-
query.from(PersonEntity.class);
-
return em.createQuery(query).getResultList();
-
}
-
@Override
-
@Transactional
-
public Optional<PersonEntity> deleteByEmail(String email) {
-
return findByEmail(email)
-
.map(entity -> {
-
em.remove(entity);
-
return entity;
-
});
-
}
-
}
事务管理(即@Transactionalannotation)需要一些解释。在典型的Java EE应用程序中,容器运行时负责管理事务。由于我们不想装载应用程序容器,而是保持精简,所以我们可以使用EntityManager来启动/提交/回滚事务。这当然是可行的,但它也会用样板污染代码。可以说,更好的选择是使用Apache DeltaSpikeCDI扩展用于声明性事务管理(这是@Transactional和@EntityManagerConfig注释的来源)。下面的代码片段说明了如何集成它。
-
@ApplicationScoped
-
public class PersistenceConfig {
-
@PersistenceUnit(unitName = "peopledb")
-
private EntityManagerFactory entityManagerFactory;
-
@Produces @PeopleDb @TransactionScoped
-
public EntityManager create() {
-
return this.entityManagerFactory.createEntityManager();
-
}
-
public void dispose(@Disposes @PeopleDb EntityManager entityManager) {
-
if (entityManager.isOpen()) {
-
entityManager.close();
-
}
-
}
-
}
太棒了——最难的部分已经过去了!接下来是person数据传输对象和服务层。
-
public class Person {
-
@NotNull private String email;
-
@NotNull private String firstName;
-
@NotNull private String lastName;
-
}
老实说,为了使示例应用程序尽可能小,我们可以完全跳过服务层,直接进入存储库。但总的来说,这不是一个很好的实践,所以让我们介绍PeopleServiceImpl。
-
@ApplicationScoped
-
public class PeopleServiceImpl implements PeopleService {
-
@Inject private PeopleRepository repository;
-
@Override
-
public Optional<Person> findByEmail(String email) {
-
return repository
-
.findByEmail(email)
-
.map(this::toPerson);
-
}
-
@Override
-
public Person add(Person person) {
-
return toPerson(repository.saveOrUpdate(person.getEmail(), person.getFirstName(), person.getLastName()));
-
}
-
@Override
-
public Collection<Person> getAll() {
-
return repository
-
.findAll()
-
.stream()
-
.map(this::toPerson)
-
.collect(Collectors.toList());
-
}
-
@Override
-
public Optional<Person> remove(String email) {
-
return repository
-
.deleteByEmail(email)
-
.map(this::toPerson);
-
}
-
private Person toPerson(PersonEntity entity) {
-
return new Person(entity.getEmail(), entity.getFirstName(), entity.getLastName());
-
}
-
}
剩下的部分是JAX-RS应用程序和资源的定义。
-
@Dependent
-
@ApplicationPath("api")
-
@OpenAPIDefinition(
-
info = @Info(
-
title = "People Management Web APIs",
-
version = "1.0.0",
-
license = @License(
-
name = "Apache License",
-
url = "https://www.apache.org/licenses/LICENSE-2.0"
-
)
-
)
-
)
-
public class PeopleApplication extends Application {
-
}
没什么好说的;这是尽可能简单的。但是JAX-RS资源实现更有趣(OpenAPI注释占据了大部分位置)。
-
@ApplicationScoped
-
@Path( "/people" )
-
@Tag(name = "people")
-
public class PeopleResource {
-
@Inject private PeopleService service;
-
@Produces(MediaType.APPLICATION_JSON)
-
@GET
-
@Operation(
-
description = "List all people",
-
responses = {
-
@ApiResponse(
-
content = @Content(array = @ArraySchema(schema = @Schema(implementation = Person.class))),
-
responseCode = "200"
-
)
-
}
-
)
-
public Collection<Person> getPeople() {
-
return service.getAll();
-
}
-
@Produces(MediaType.APPLICATION_JSON)
-
@Path("/{email}")
-
@GET
-
@Operation(
-
description = "Find person by e-mail",
-
responses = {
-
@ApiResponse(
-
content = @Content(schema = @Schema(implementation = Person.class)),
-
responseCode = "200"
-
),
-
@ApiResponse(
-
responseCode = "404",
-
description = "Person with such e-mail doesn't exists"
-
)
-
}
-
)
-
public Person findPerson(@Parameter(description = "E-Mail address to lookup for", required = true) @PathParam("email") final String email) {
-
return service
-
.findByEmail(email)
-
.orElseThrow(() -> new NotFoundException("Person with such e-mail doesn't exists"));
-
}
-
@Consumes(MediaType.APPLICATION_JSON)
-
@Produces(MediaType.APPLICATION_JSON)
-
@POST
-
@Operation(
-
description = "Create new person",
-
requestBody = @RequestBody(
-
content = @Content(schema = @Schema(implementation = Person.class)),
-
),
-
responses = {
-
@ApiResponse(
-
content = @Content(schema = @Schema(implementation = Person.class)),
-
headers = @Header(name = "Location"),
-
responseCode = "201"
-
),
-
@ApiResponse(
-
responseCode = "409",
-
description = "Person with such e-mail already exists"
-
)
-
}
-
)
-
public Response addPerson(@Context final UriInfo uriInfo,
-
@Parameter(description = "Person", required = true) @Valid Person payload) {
-
final Person person = service.add(payload);
-
return Response
-
.created(uriInfo.getRequestUriBuilder().path(person.getEmail()).build())
-
.entity(person)
-
.build();
-
}
-
@Path("/{email}")
-
@DELETE
-
@Operation(
-
description = "Delete existing person",
-
responses = {
-
@ApiResponse(
-
responseCode = "204",
-
description = "Person has been deleted"
-
),
-
@ApiResponse(
-
responseCode = "404",
-
description = "Person with such e-mail doesn't exists"
-
)
-
}
-
)
-
public Response deletePerson(@Parameter(description = "E-Mail address to lookup for", required = true ) @PathParam("email") final String email) {
-
return service
-
.remove(email)
-
.map(r -> Response.noContent().build())
-
.orElseThrow(() -> new NotFoundException("Person with such e-mail doesn't exists"));
-
}
-
}
这样,我们就完成了!但是,我们怎样才能把这些零件组装起来,然后用电线把它们连在一起呢?现在是 Microprofile进入舞台的时候了。有许多实现可供选择;我们将在这篇文章中使用的是Project Hammock 。我们要做的唯一一件事就是指定我们想要使用的CDI 2.0、JAX-RS 2.1和JPA 2.0实现,它们分别转换为Weld、Apache CXF和OpenJPA(通过 Project Hammock 依赖关系表示)。让我们来看看Apache Mavenpom.xml文件。
-
<properties>
-
<deltaspike.version>1.8.1</deltaspike.version>
-
<hammock.version>2.1</hammock.version>
-
</properties>
-
<dependencies>
-
<dependency>
-
<groupId>org.apache.deltaspike.modules</groupId>
-
<artifactId>deltaspike-jpa-module-api</artifactId>
-
<version>${deltaspike.version}</version>
-
<scope>compile</scope>
-
</dependency>
-
<dependency>
-
<groupId>org.apache.deltaspike.modules</groupId>
-
<artifactId>deltaspike-jpa-module-impl</artifactId>
-
<version>${deltaspike.version}</version>
-
<scope>runtime</scope>
-
</dependency>
-
<dependency>
-
<groupId>ws.ament.hammock</groupId>
-
<artifactId>dist-microprofile</artifactId>
-
<version>${hammock.version}</version>
-
</dependency>
-
<dependency>
-
<groupId>ws.ament.hammock</groupId>
-
<artifactId>jpa-openjpa</artifactId>
-
<version>${hammock.version}</version>
-
</dependency>
-
<dependency>
-
<groupId>ws.ament.hammock</groupId>
-
<artifactId>util-beanvalidation</artifactId>
-
<version>${hammock.version}</version>
-
</dependency>
-
<dependency>
-
<groupId>ws.ament.hammock</groupId>
-
<artifactId>util-flyway</artifactId>
-
<version>${hammock.version}</version>
-
</dependency>
-
<dependency>
-
<groupId>ws.ament.hammock</groupId>
-
<artifactId>swagger</artifactId>
-
<version>${hammock.version}</version>
-
</dependency>
-
</dependencies>
在没有进一步的ado的情况下,让我们立即构建和运行应用程序(如果您想知道应用程序使用的是什么关系数据存储,那么它是H2,在内存中配置了数据库)。
-
mvn clean package
-
java -jar target/eclipse-microprofile-hammock-0.0.1-SNAPSHOT-capsule.jar
确保RESTful web api功能完备的最佳方法是向它发送几个请求:
-
> curl -X POST http://localhost:10900/api/people -H "Content-Type: application\json" \
-
-d '{"email": "a@b.com", "firstName": "John", "lastName": "Smith"}'
-
HTTP/1.1 201 Created
-
Location: http://localhost:10900/api/people/a@b.com
-
Content-Type: application/json
-
{
-
"firstName":"John","
-
"lastName":"Smith",
-
"email":"a@b.com"
-
}
如何确保Bean Validation 工作正常?为了触发它,让我们发送部分准备好的请求。
-
> curl --X POST http://localhost:10900/api/people -H "Content-Type: application\json" \
-
-d '{"firstName": "John", "lastName": "Smith"}'
-
HTTP/1.1 400 Bad Request
-
Content-Length: 0
OpenAPI规范和预捆绑的Swagger UI发行版也可以通过http://localhost:10900/index.html?url=http://localhost:10900/api/openapi.json获得。到目前为止,一切都很好,但公平地说,我们根本没有谈到测试我们的应用程序。要为添加一个person的场景设计出集成测试有多难呢?事实证明,围绕Java EE应用程序测试的框架已经有了很大的改进。特别是,使用Arquillian测试框架(以及受欢迎的JUnit和REST Assured)非常容易完成。一个真实的例子抵得上千言万语。
-
@RunWith(Arquillian.class)
-
@EnableRandomWebServerPort
-
public class PeopleApiTest {
-
@ArquillianResource private URI uri;
-
@Deployment
-
public static JavaArchive createArchive() {
-
return ShrinkWrap
-
.create(JavaArchive.class)
-
.addClasses(PeopleResource.class, PeopleApplication.class)
-
.addClasses(PeopleServiceImpl.class, PeopleJpaRepository.class, PersistenceConfig.class)
-
.addPackages(true, "org.apache.deltaspike");
-
}
-
@Test
-
public void shouldAddNewPerson() throws Exception {
-
final Person person = new Person("a@b.com", "John", "Smith");
-
given()
-
.contentType(ContentType.JSON)
-
.body(person)
-
.post(uri + "/api/people")
-
.then()
-
.assertThat()
-
.statusCode(201)
-
.body("email", equalTo("a@b.com"))
-
.body("firstName", equalTo("John"))
-
.body("lastName", equalTo("Smith"));
-
}
-
}
不神奇吗?实际上,开发现代Java EE应用程序是非常有趣的,有人可能会说,用Spring的方式!事实上,与Spring的相似之处并非巧合,因为它很有启发性,很有启发性,而且无疑将继续激励Java EE生态系统中的创新。未来如何?我认为,无论对于雅加达EE还是Eclipse Microprofile来说,都是光明的。后者刚刚接近2.0版本,提供了大量新的规范,这些规范旨在满足微服务体系结构的需求。目睹这些转变真是太棒了。项目的完整源代码可以在GitHub上找到。