用Spring构建企业Java应用程序

通过在本教程中构建一个简单的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

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,142评论 6 498
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,298评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 162,068评论 0 351
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,081评论 1 291
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,099评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,071评论 1 295
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,990评论 3 417
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,832评论 0 273
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,274评论 1 310
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,488评论 2 331
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,649评论 1 347
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,378评论 5 343
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,979评论 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,625评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,796评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,643评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,545评论 2 352

推荐阅读更多精彩内容