用Spring构建企业Java应用程序
來源:SpringForAll社區
通過在本教程中構建一個簡單的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: applicationjson"
? ? -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: applicationjson"
? ? -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上找到。
原文鏈接:https://dzone.com/articles/building-enterprise-java-applications-the-spring-w
作者:Andriy Redko
譯者:xieed
·END·
?近期熱文:
深入聊一聊 Spring AOP 實現機制
Spring Cloud Stream 學習小清單
在一臺Mac上不同平臺同時使用多個Git賬號
Git 版本控制之 GitFlow
徹底搞懂 Git-Rebase
我說分布式事務之最大努力通知型事務
我說分布式事務之TCC
不可錯過的CMS學習筆記
在生產中使用Java 11:需要了解的重要事項
可能是最全面的G1學習筆記
看完,趕緊點個“好看”鴨
點鴨點鴨
↓↓↓↓
總結
以上是生活随笔為你收集整理的用Spring构建企业Java应用程序的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: IPNC开发笔记——关于IPNC_RDK
- 下一篇: Java基础【之】JDK环境配置(Win