作者都是各自领域经过审查的专家,并撰写他们有经验的主题. 我们所有的内容都经过同行评审,并由同一领域的Toptal专家验证.
Dusan是一名后端Java开发人员,拥有8年的Java开发经验,参与过许多大型项目.
我们都见证了…越来越受欢迎 microservice architectures. 在微服务架构中,Dropwizard占有非常重要的地位. 它是用于构建RESTful web服务或web服务的框架, more precisely, 一组用于构建RESTful web服务的工具和框架.
它允许开发人员快速启动项目. 这可以帮助您打包应用程序,以便作为独立服务轻松地部署到生产环境中. 如果你曾经在需要启动项目的情况下 Spring framework例如,你可能知道它有多痛苦.
使用Dropwizard,只需添加一个Maven依赖项即可.
In this blog, 我将指导您完成编写简单Dropwizard RESTful服务的完整过程. 完成之后,我们将拥有一个服务,用于“部件”上的基本CRUD操作.” It doesn’t really matter what “part” is; it can be anything. It just came to mind first.
We will store the data in a MySQL database, using JDBI for querying it, and will use following endpoints:
GET /parts
-to retrieve all parts from DBGET /part/{id}
to get a particular part from DBPOST /parts
-to create new partPUT /parts/{id}
-to edit an existing partDELETE /parts/{id}
-to delete the part from a DB我们将使用OAuth来验证我们的服务,最后,向它添加一些单元测试.
而不是包括单独构建REST服务所需的所有库并配置每个库, Dropwizard does that for us. 以下是默认Dropwizard自带的库列表:
Apart from the above list, 还有许多像Joda Time这样的库, Liquibase, Apache HTTP Client, 以及由Dropwizard用于构建REST服务的Hibernate验证器.
Dropwizard officially supports Maven. Even if you can use other build tools, 大多数指南和文档都使用Maven, so we’re going to use it too here. 如果您不熟悉Maven,可以看看这个 Maven tutorial.
这是创建Dropwizard应用程序的第一步. 请在您的Maven中添加以下条目 pom.xml
file:
io.dropwizard
dropwizard-core
${dropwizard.version}
在添加上述条目之前,您可以添加 dropwizard.version
as below:
1.1.0
That’s it. 您已经完成了Maven配置的编写. 这将把所有必需的依赖项下载到项目中. The current Dropwizard version is 1.1.0, so we will be using it this guide.
现在,我们可以继续编写第一个真正的Dropwizard应用程序.
Dropwizard stores configurations in YAML files. You will need to have the file configuration.yml
in your application root folder. 然后将该文件反序列化为应用程序配置类的实例并进行验证. 应用程序的配置文件是Dropwizard的配置类(io.dropwizard.Configuration
).
Let’s create a simple configuration class:
import javax.validation.Valid;
import javax.validation.constraints.NotNull;
import com.fasterxml.jackson.annotation.JsonProperty;
import io.dropwizard.Configuration;
import io.dropwizard.db.DataSourceFactory;
公共类dropwizard dblogconfiguration扩展Configuration {
private static final String DATABASE = " DATABASE ";
@Valid
@NotNull
private DataSourceFactory = new DataSourceFactory();
@JsonProperty(DATABASE)
getDataSourceFactory() {
return dataSourceFactory;
}
@JsonProperty(DATABASE)
setDataSourceFactory(最终DataSourceFactory) {
this.dataSourceFactory = dataSourceFactory;
}
}
YAML配置文件看起来像这样:
database:
driverClass: com.mysql.cj.jdbc.Driver
url: jdbc: mysql: / / localhost / dropwizard_blog
user: dropwizard_blog
password: dropwizard_blog
maxWaitForConnection: 1s
validationQuery: "SELECT 1"
validationQueryTimeout: 3s
minSize: 8
maxSize: 32
checkConnectionWhileIdle: false
evictionInterval: 10s
minIdleTime: 1 minute
checkConnectionOnBorrow: true
上述类将从YAML文件中反序列化,并将YAML文件中的值放入该对象.
现在我们应该去创建主应用程序类. 这个类将把所有的包放在一起,启动应用程序并使其运行.
下面是Dropwizard中一个应用程序类的例子:
import io.dropwizard.Application;
import io.dropwizard.auth.AuthDynamicFeature;
import io.dropwizard.auth.oauth.OAuthCredentialAuthFilter;
import io.dropwizard.setup.Environment;
import javax.sql.DataSource;
import org.glassfish.jersey.server.filter.RolesAllowedDynamicFeature;
import org.skife.jdbi.v2.DBI;
import com.toptal.blog.auth.DropwizardBlogAuthenticator;
import com.toptal.blog.auth.DropwizardBlogAuthorizer;
import com.toptal.blog.auth.User;
import com.toptal.blog.config.DropwizardBlogConfiguration;
import com.toptal.blog.health.DropwizardBlogApplicationHealthCheck;
import com.toptal.blog.resource.PartsResource;
import com.toptal.blog.service.PartsService;
public class DropwizardBlogApplication extends Application {
private static final String SQL = "sql";
private static final String DROPWIZARD_BLOG_SERVICE = "Dropwizard博客服务";
private static final String BEARER = " BEARER ";
public static void main(String[] args)抛出异常{
new DropwizardBlogApplication().run(args);
}
@Override
公共无效运行(dropwizard dblogconfiguration配置,环境环境){
// Datasource configuration
final DataSource dataSource =
configuration.getDataSourceFactory().build(environment.metrics(), SQL);
DBI dbi = new DBI(dataSource);
// Register Health Check
dropwizard dblogapplicationhealthcheck healthCheck =
新DropwizardBlogApplicationHealthCheck (dbi.onDemand(PartsService.class));
environment.healthChecks().注册(DROPWIZARD_BLOG_SERVICE healthCheck);
// Register OAuth authentication
environment.jersey()
.新增OAuthCredentialAuthFilter.Builder()
.setAuthenticator(新DropwizardBlogAuthenticator ())
.setAuthorizer(新DropwizardBlogAuthorizer ()).setPrefix(BEARER).buildAuthFilter()));
environment.jersey().register(RolesAllowedDynamicFeature.class);
// Register resources
environment.jersey().register(new PartsResource(dbi.onDemand(PartsService.class)));
}
}
上面实际做的是覆盖Dropwizard运行方法. In this method, we’re instantiating a DB connection, 注册自定义运行状况检查(稍后讨论), 初始化服务的OAuth身份验证, and finally, registering a Dropwizard resource.
All of these will be explained later on.
现在我们必须开始考虑我们的REST API以及我们的资源的表示形式. 我们必须设计JSON格式和相应的表示类,将其转换为所需的JSON格式.
让我们来看看这个简单表示类示例的JSON格式:
{
"code": 200,
"data": {
"id": 1,
"name": "Part 1",
"code": "PART_1_CODE"
}
}
对于上述JSON格式,我们将创建如下表示类:
import org.hibernate.validator.constraints.Length;
import com.fasterxml.jackson.annotation.JsonProperty;
public class Representation {
private long code;
@Length(max = 3)
private T data;
public Representation() {
// Jackson deserialization
}
public Representation(long code, T data) {
this.code = code;
this.data = data;
}
@JsonProperty
public long getCode() {
return code;
}
@JsonProperty
public T getData() {
return data;
}
}
This is fairly simple POJO.
资源就是REST服务的全部内容. 它只是一个端点URI,用于访问服务器上的资源. 在这个例子中,我们将有一个资源类,它带有一些用于请求URI映射的注释. 由于Dropwizard使用JAX-RS实现,因此我们将使用 @Path
annotation.
下面是我们Dropwizard示例的一个资源类:
import java.util.List;
import javax.annotation.security.RolesAllowed;
import javax.validation.Valid;
import javax.validation.constraints.NotNull;
import javax.ws.rs.DELETE;
import javax.ws.rs.GET;
import javax.ws.rs.POST;
import javax.ws.rs.PUT;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.Produces;
import javax.ws.rs.core.MediaType;
import org.eclipse.jetty.http.HttpStatus;
import com.codahale.metrics.annotation.Timed;
import com.toptal.blog.model.Part;
import com.toptal.blog.representation.Representation;
import com.toptal.blog.service.PartsService;
@Path("/parts")
@Produces(MediaType.APPLICATION_JSON)
@RolesAllowed("ADMIN")
public class PartsResource {
private final PartsService partsService;;
public PartsResource(PartsService) {
this.partsService = partsService;
}
@GET
@Timed
public Representation> getParts() {
return new Representation>(HttpStatus.OK_200, partsService.getParts());
}
@GET
@Timed
@Path("{id}")
public Representation getPart(@PathParam("id") final int id) {
return new Representation(HttpStatus.OK_200, partsService.getPart(id));
}
@POST
@Timed
public Representation createPart(@NotNull @Valid final Part part) {
return new Representation(HttpStatus.OK_200, partsService.createPart(part));
}
@PUT
@Timed
@Path("{id}")
public Representation editPart(@NotNull @Valid final Part part,
@PathParam("id") final int id) {
part.setId(id);
return new Representation(HttpStatus.OK_200, partsService.editPart(part));
}
@DELETE
@Timed
@Path("{id}")
public Representation deletePart(@PathParam("id") final int id) {
return new Representation(HttpStatus.OK_200, partsService.deletePart(id));
}
}
你可以看到所有的端点都是在这个类中定义的.
我现在回到主应用类. 您可以在该类的末尾看到,我们已经注册了要用服务运行初始化的资源. 我们需要对应用程序中可能拥有的所有资源这样做. 这是负责的代码片段:
// Register resources
environment.jersey().register(new PartsResource(dbi.onDemand(PartsService.class)));
以获得正确的异常处理和独立于数据存储引擎的能力, 我们将引入一个“中间层”服务类. 这是我们将从资源层调用的类,我们不关心底层是什么. 这就是为什么我们在资源层和DAO层之间有这个层. Here is our service class:
import java.util.List;
import java.util.Objects;
import javax.ws.rs.WebApplicationException;
import javax.ws.rs.core.Response.Status;
import org.skife.jdbi.v2.exceptions.UnableToExecuteStatementException;
import org.skife.jdbi.v2.exceptions.UnableToObtainConnectionException;
import org.skife.jdbi.v2.sqlobject.CreateSqlObject;
import com.toptal.blog.dao.PartsDao;
import com.toptal.blog.model.Part;
public abstract class PartsService {
private static final String PART_NOT_FOUND = "部件id %s未找到。.";
私有静态最终字符串DATABASE_REACH_ERROR =
"Could not reach the MySQL database. 数据库可能已关闭,或者存在网络连接问题. Details: ";
DATABASE_CONNECTION_ERROR =
"无法创建到MySQL数据库的连接。. 数据库配置可能不正确. Details: ";
database_expected_error =
"试图访问数据库时发生意外错误. Details: ";
private static final String SUCCESS = "成功...";
ununtited_error = "删除部件时发生意外错误.";
@CreateSqlObject
abstract PartsDao partsDao();
public List getParts() {
return partsDao().getParts();
}
public Part getPart(int id) {
Part part = partsDao().getPart(id);
if (Objects.isNull(part)) {
throw new WebApplicationException(String.format(PART_NOT_FOUND, id), Status.NOT_FOUND);
}
return part;
}
public Part createPart(Part part) {
partsDao().createPart(part);
return partsDao().getPart(partsDao().lastInsertId());
}
public Part editPart(Part part) {
if (Objects.isNull(partsDao().getPart(part.getId()))) {
throw new WebApplicationException(String.format(PART_NOT_FOUND, part.getId()),
Status.NOT_FOUND);
}
partsDao().editPart(part);
return partsDao().getPart(part.getId());
}
public String deletePart(final int id) {
int result = partsDao().deletePart(id);
switch (result) {
case 1:
return SUCCESS;
case 0:
throw new WebApplicationException(String.format(PART_NOT_FOUND, id), Status.NOT_FOUND);
default:
抛出新的WebApplicationException(ununtited_error).INTERNAL_SERVER_ERROR);
}
}
public String performHealthCheck() {
try {
partsDao().getParts();
} catch (unletoobtainconnectionexception ex) {
返回checkUnableToObtainConnectionException (ex);
} catch (UnableToExecuteStatementException ex) {
返回checkUnableToExecuteStatementException (ex);
} catch (Exception ex) {
return DATABASE_UNEXPECTED_ERROR + ex.getCause().getLocalizedMessage();
}
return null;
}
private String checkunletoobtainconnectionexception (unletoobtainconnectionexception ex) {
if (ex.getCause() instanceof java.sql.SQLNonTransientConnectionException) {
return DATABASE_REACH_ERROR + ex.getCause().getLocalizedMessage();
} else if (ex.getCause() instanceof java.sql.SQLException) {
return DATABASE_CONNECTION_ERROR + ex.getCause().getLocalizedMessage();
} else {
return DATABASE_UNEXPECTED_ERROR + ex.getCause().getLocalizedMessage();
}
}
checkUnableToExecuteStatementException(UnableToExecuteStatementException ex) {
if (ex.getCause() instanceof java.sql.SQLSyntaxErrorException) {
return DATABASE_CONNECTION_ERROR + ex.getCause().getLocalizedMessage();
} else {
return DATABASE_UNEXPECTED_ERROR + ex.getCause().getLocalizedMessage();
}
}
}
它的最后一部分实际上是一个健康检查实现,我们将在后面讨论.
Dropwizard supports JDBI and Hibernate. 它是独立的Maven模块,所以让我们首先将它添加为依赖项以及MySQL连接器:
io.dropwizard
dropwizard-jdbi
${dropwizard.version}
mysql
mysql-connector-java
${mysql.connector.version}
对于简单的CRUD服务,我个人更喜欢jdbc,因为它更简单,实现起来也快得多. 我创建了一个简单的MySQL模式,其中只有一个表,仅在我们的示例中使用. 您可以在源代码中找到模式的初始化脚本. jdbc通过使用注释(如用于读取的@SqlQuery和用于写入数据的@SqlUpdate)提供简单的查询写入. Here is our DAO interface:
import java.util.List;
import org.skife.jdbi.v2.sqlobject.Bind;
import org.skife.jdbi.v2.sqlobject.BindBean;
import org.skife.jdbi.v2.sqlobject.SqlQuery;
import org.skife.jdbi.v2.sqlobject.SqlUpdate;
import org.skife.jdbi.v2.sqlobject.customizers.RegisterMapper;
import com.toptal.blog.mapper.PartsMapper;
import com.toptal.blog.model.Part;
@RegisterMapper(PartsMapper.class)
public interface PartsDao {
@SqlQuery("select * from parts;")
public List getParts();
@SqlQuery("select * from parts where id =:id")
public Part getPart(@Bind("id") final int id);
@SqlUpdate("插入部分(名称,代码)值(:名称,:代码)")
void createPart(@BindBean的最终部分部分);
@SqlUpdate("更新部件集名称= coalesce(:名称, name), code = coalesce(:code, code) where id = :id")
void editPart(@BindBean final Part part);
@SqlUpdate("删除id =:id的部分")
int deletePart(@Bind("id") final int id);
@SqlQuery("select last_insert_id();")
public int lastInsertId();
}
As you can see, it’s fairly simple. 然而,我们需要将SQL结果集映射到模型,这可以通过注册一个映射器类来实现. Here is our mapper class:
import java.sql.ResultSet;
import java.sql.SQLException;
import org.skife.jdbi.v2.StatementContext;
import org.skife.jdbi.v2.tweak.ResultSetMapper;
import com.toptal.blog.model.Part;
public class PartsMapper implements ResultSetMapper {
private static final String ID = "id";
private static final String NAME = "name";
private static final String CODE = "code";
public Part map(int i, ResultSet, ResultSet, StatementContext)
throws SQLException {
return new Part(resultSet.getInt(ID), resultSet.getString(NAME), resultSet.getString(CODE));
}
}
And our model:
import org.hibernate.validator.constraints.NotEmpty;
public class Part {
private int id;
@NotEmpty
private String name;
@NotEmpty
private String code;
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getCode() {
return code;
}
public void setCode(String code) {
this.code = code;
}
public Part() {
super();
}
public Part(int id, String name, String code) {
super();
this.id = id;
this.name = name;
this.code = code;
}
}
Dropwizard为运行状况检查提供原生支持. In our case, 在说我们的服务运行正常之前,我们可能希望检查数据库是否已启动并运行. 我们所做的实际上是执行一些简单的DB操作,例如从DB获取部件并处理潜在的结果(成功或异常)。.
下面是我们在Dropwizard中的健康检查实现:
import com.codahale.metrics.health.HealthCheck;
import com.toptal.blog.service.PartsService;
DropwizardBlogApplicationHealthCheck扩展HealthCheck {
private static final String HEALTHY = " Dropwizard博客服务可以正常读写";
private static final String不健康= " Dropwizard博客服务不健康. ";
private static final String MESSAGE_PLACEHOLDER = "{}";
private final PartsService partsService;
公共dropwizard dblogapplicationhealthcheck (PartsService PartsService) {
this.partsService = partsService;
}
@Override
public Result check() throws Exception {
String mySqlHealthStatus = partsService.performHealthCheck();
if (mySqlHealthStatus == null) {
return Result.healthy(HEALTHY);
} else {
return Result.不健康(不健康+ MESSAGE_PLACEHOLDER, mySqlHealthStatus);
}
}
}
Dropwizard支持基本身份验证和 OAuth. Here. 我将向您展示如何使用OAuth保护您的服务. However, due to complexity, 我省略了底层的DB结构,只展示了它是如何包装的. 从这里开始,全面实施应该不是问题. Dropwizard有两个我们需要实现的重要接口.
The first one is Authenticator. Our class should implement the authenticate
方法,该方法应检查给定的访问令牌是否有效. 所以我把它称为应用程序的第一扇门. If succeeded, it should return a principal. 这个主体是我们的实际用户及其角色. 这个角色对于我们需要实现的另一个Dropwizard接口很重要. This one is Authorizer, 它负责检查用户是否有足够的权限来访问某个资源. So, 如果你回去查一下我们的资源类, 您将看到它需要管理员角色来访问它的端点. These annotations can be per method also. Dropwizard授权支持是一个单独的Maven模块,所以我们需要将它添加到依赖项中:
io.dropwizard
dropwizard-auth
${dropwizard.version}
下面是我们示例中的类,它们实际上没有做任何聪明的事情, 但这是全面OAuth授权的框架:
import java.util.Optional;
import io.dropwizard.auth.AuthenticationException;
import io.dropwizard.auth.Authenticator;
public class DropwizardBlogAuthenticator implements Authenticator {
@Override
public Optional authenticate(String token) throws AuthenticationException {
if ("test_token".equals(token)) {
return Optional.of(new User());
}
return Optional.empty();
}
}
import java.util.Objects;
import io.dropwizard.auth.Authorizer;
public class DropwizardBlogAuthorizer implements Authorizer {
@Override
公共布尔授权(用户主体,字符串角色){
// Allow any logged in user.
if (Objects.nonNull(principal)) {
return true;
}
return false;
}
}
import java.security.Principal;
public class User implements Principal {
private int id;
private String username;
private String password;
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
@Override
public String getName() {
return username;
}
}
让我们在应用程序中添加一些单元测试. 我将坚持测试Dropwizard代码的特定部分, in our case Representation and Resource. 我们需要将以下依赖项添加到Maven文件中:
io.dropwizard
dropwizard-testing
${dropwizard.version}
org.mockito
mockito-core
${mockito.version}
test
为了测试表示,我们还需要一个示例JSON文件进行测试. So let’s create fixtures/part.json
under src/test/resources
:
{
"id": 1,
"name": "testPartName",
"code": "testPartCode"
}
And here is the JUnit test class:
import static io.dropwizard.testing.FixtureHelpers.fixture;
import static org.assertj.core.api.Assertions.assertThat;
import org.junit.Test;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.toptal.blog.model.Part;
import io.dropwizard.jackson.Jackson;
public class RepresentationTest {
ObjectMapper MAPPER = Jackson.newObjectMapper();
PART_JSON = "fixture /part . json ".json";
private static final String TEST_PART_NAME = "testPartName";
private static final String TEST_PART_CODE = "testPartCode";
@Test
public void serializesToJSON()抛出异常{
最终部分部分=新部分(1,TEST_PART_NAME, TEST_PART_CODE);
final String expected =
MAPPER.writeValueAsString(MAPPER.readValue(fixture(PART_JSON), Part.class));
assertThat(MAPPER.writeValueAsString(part)).isEqualTo(expected);
}
@Test
公共void deserializesFromJSON()抛出异常{
最终部分部分=新部分(1,TEST_PART_NAME, TEST_PART_CODE);
assertThat(MAPPER.readValue(fixture(PART_JSON), Part.class).getId()).isEqualTo(part.getId());
assertThat(MAPPER.readValue(fixture(PART_JSON), Part.class).getName())
.isEqualTo(part.getName());
assertThat(MAPPER.readValue(fixture(PART_JSON), Part.class).getCode())
.isEqualTo(part.getCode());
}
}
When it comes to testing resources, 测试Dropwizard的主要要点是,您实际上是作为HTTP客户端进行操作的, sending HTTP requests against resources. 所以,你不是像通常情况下那样测试方法. Here is the example for our PartsResource
class:
public class PartsResourceTest {
private static final String SUCCESS = "成功...";
private static final String TEST_PART_NAME = "testPartName";
private static final String TEST_PART_CODE = "testPartCode";
private static final String PARTS_ENDPOINT = "/parts";
PartsService = mock(PartsService . net.class);
@ClassRule
ResourceTestRule资源=
ResourceTestRule.builder().addResource(新PartsResource (partsService)).build();
private final Part Part = new Part(1, TEST_PART_NAME, TEST_PART_CODE);
@Before
public void setup() {
when(partsService.getPart(eq(1))).thenReturn(part);
List parts = new ArrayList<>();
parts.add(part);
when(partsService.getParts()).thenReturn(parts);
when(partsService.createPart(any(Part.class))).thenReturn(part);
when(partsService.editPart(any(Part.class))).thenReturn(part);
when(partsService.deletePart(eq(1))).thenReturn(SUCCESS);
}
@After
public void tearDown() {
reset(partsService);
}
@Test
public void testGetPart() {
Part partResponse = resources.target(PARTS_ENDPOINT + "/1").request()
.get(TestPartRepresentation.class).getData();
assertThat(partResponse.getId()).isEqualTo(part.getId());
assertThat(partResponse.getName()).isEqualTo(part.getName());
assertThat(partResponse.getCode()).isEqualTo(part.getCode());
verify(partsService).getPart(1);
}
@Test
public void testGetParts() {
List parts =
resources.target(PARTS_ENDPOINT).request().get(TestPartsRepresentation.class).getData();
assertThat(parts.size()).isEqualTo(1);
assertThat(parts.get(0).getId()).isEqualTo(part.getId());
assertThat(parts.get(0).getName()).isEqualTo(part.getName());
assertThat(parts.get(0).getCode()).isEqualTo(part.getCode());
verify(partsService).getParts();
}
@Test
public void testCreatePart() {
Part newPart = resources.target(PARTS_ENDPOINT).request()
.post(Entity.entity(part, MediaType.APPLICATION_JSON_TYPE), TestPartRepresentation.class)
.getData();
assertNotNull(newPart);
assertThat(newPart.getId()).isEqualTo(part.getId());
assertThat(newPart.getName()).isEqualTo(part.getName());
assertThat(newPart.getCode()).isEqualTo(part.getCode());
verify(partsService).createPart(any(Part.class));
}
@Test
public void testEditPart() {
Part editedPart = resources.target(PARTS_ENDPOINT + "/1").request()
.put(Entity.entity(part, MediaType.APPLICATION_JSON_TYPE), TestPartRepresentation.class)
.getData();
assertNotNull(editedPart);
assertThat(editedPart.getId()).isEqualTo(part.getId());
assertThat(editedPart.getName()).isEqualTo(part.getName());
assertThat(editedPart.getCode()).isEqualTo(part.getCode());
verify(partsService).editPart(any(Part.class));
}
@Test
public void testDeletePart() {
assertThat(resources.target(PARTS_ENDPOINT + "/1").request()
.delete(TestDeleteRepresentation.class).getData()).isEqualTo(SUCCESS);
verify(partsService).deletePart(1);
}
private static class TestPartRepresentation extends Representation {
}
private static class TestPartsRepresentation extends Representation> {
}
private static class TestDeleteRepresentation extends Representation {
}
}
最佳实践是构建单个FAT JAR文件,其中包含所有的 .运行应用程序所需的类文件. 可以将相同的JAR文件部署到从测试到生产的不同环境中,而无需对依赖库进行任何更改. 开始将我们的示例应用程序构建为一个胖JAR, 我们需要配置一个Maven插件Maven -shade. 您必须在您的插件部分添加以下条目.xml file.
下面是构建JAR文件的示例Maven配置.
4.0.0
com.endava
dropwizard-blog
0.0.1-SNAPSHOT
Dropwizard Blog example
1.1.0
2.7.12
6.0.6
1.8
1.8
io.dropwizard
dropwizard-core
${dropwizard.version}
io.dropwizard
dropwizard-jdbi
${dropwizard.version}
io.dropwizard
dropwizard-auth
${dropwizard.version}
io.dropwizard
dropwizard-testing
${dropwizard.version}
org.mockito
mockito-core
${mockito.version}
test
mysql
mysql-connector-java
${mysql.connector.version}
org.apache.maven.plugins
maven-shade-plugin
2.3
true
*:*
META-INF/*.SF
META-INF/*.DSA
META-INF/*.RSA
package
shade
com.endava.blog.DropwizardBlogApplication
Now, we should be able to run the service. 如果您已经成功构建了JAR文件, 你所需要做的就是打开命令提示符,然后运行下面的命令来执行JAR文件:
java -jar target/dropwizard-blog-1.0.0.jar server configuration.yml
如果一切正常,那么你会看到这样的内容:
INFO [2017-04-23 22:51:14,471] org.eclipse.jetty.util.log: Logging initialized @962ms to org.eclipse.jetty.util.log.Slf4jLog
INFO [2017-04-23 22:51:14,537] io.dropwizard.server.DefaultServerFactory:注册带有根路径前缀:/的球衣处理程序
INFO [2017-04-23 22:51:14,538] io.dropwizard.server.DefaultServerFactory:注册带有根路径前缀:/的管理处理程序
INFO [2017-04-23 22:51:14,681] io.dropwizard.server.DefaultServerFactory:注册带有根路径前缀:/的球衣处理程序
INFO [2017-04-23 22:51:14,681] io.dropwizard.server.DefaultServerFactory:注册带有根路径前缀:/的管理处理程序
INFO [2017-04-23 22:51:14,682] io.dropwizard.server.ServerFactory:启动dropwizard dblogapplication
INFO [2017-04-23 22:51:14,752] org.eclipse.jetty.setuid.SetUIDListener:打开application@7d57dbb5{HTTP/1.1,[http/1.1]}{0.0.0.0:8080}
INFO [2017-04-23 22:51:14,752] org.eclipse.jetty.setuid.SetUIDListener:打开admin@630b6190{HTTP/1.1,[http/1.1]}{0.0.0.0:8081}
INFO [2017-04-23 22:51:14,753] org.eclipse.jetty.server.Server: jetty-9.4.2.v20170220
INFO [2017-04-23 22:51:15,153] io.dropwizard.jersey.dropwizardresourcecconfig:为配置的资源找到以下路径:
GET /parts (com.toptal.blog.resource.PartsResource)
POST /parts (com.toptal.blog.resource.PartsResource)
DELETE /parts/{id} (com.toptal.blog.resource.PartsResource)
GET /parts/{id} (com.toptal.blog.resource.PartsResource)
PUT /parts/{id} (com.toptal.blog.resource.PartsResource)
INFO [2017-04-23 22:51:15,154] org.eclipse.jetty.server.handler.ContextHandler: Started i.d.j.MutableServletContextHandler@58fa5769 {/, null,可用}
INFO [2017-04-23 22:51:15,158] io.dropwizard.setup.AdminEnvironment: tasks =
POST /tasks/log-level (io.dropwizard.servlets.tasks.LogConfigurationTask)
POST /tasks/gc (io.dropwizard.servlets.tasks.GarbageCollectionTask)
INFO [2017-04-23 22:51:15,162] org.eclipse.jetty.server.handler.ContextHandler: Started i.d.j.MutableServletContextHandler@3fdcde7a {/, null,可用}
INFO [2017-04-23 22:51:15,176] org.eclipse.jetty.server.AbstractConnector:已启动application@7d57dbb5{HTTP/1.1,[http/1.1]}{0.0.0.0:8080}
INFO [2017-04-23 22:51:15,177] org.eclipse.jetty.server.AbstractConnector:已启动admin@630b6190{HTTP/1.1,[http/1.1]}{0.0.0.0:8081}
INFO [2017-04-23 22:51:15,177] org.eclipse.jetty.server.Server: Started @1670ms
现在,您已经拥有了自己的Dropwizard应用程序,该应用程序在端口8080上侦听应用程序请求,在端口8081上侦听管理请求.
Note that server configuration.yml
用于启动HTTP服务器并将YAML配置文件位置传递给服务器.
Excellent! 最后,我们使用Dropwizard框架实现了一个微服务. 现在我们休息一下,喝杯茶吧. You have done really good job.
您可以使用任何HTTP客户端,如POSTMAN或其他任何客户端. 您应该能够通过点击访问服务器 http://localhost:8080/parts
. 您应该会收到一条消息,表明访问服务需要凭据. To authenticate, add Authorization
header with bearer test_token
value. 如果操作成功,您应该看到如下内容:
{
"code": 200,
"data": []
}
meaning that your DB is empty. 通过将HTTP方法从GET切换到POST来创建第一部分,并提供以下有效负载:
{
"name":"My first part",
"code":"code_of_my_first_part"
}
所有其他端点都以相同的方式工作,所以继续玩并享受.
默认情况下,Dropwizard应用程序将在 /
. For example, 如果您没有提及任何有关应用程序的上下文路径, by default, 可以从URL访问应用程序 http://localhost:8080/
. 如果您想为您的应用程序配置自己的上下文路径, 那么请将以下条目添加到您的YAML文件中.
server:
applicationContextPath: /application
现在,当您启动并运行Dropwizard REST服务时, 让我们总结一下使用Dropwizard作为REST框架的一些主要优点或缺点. 从这篇文章中可以明显看出,Dropwizard提供了非常快的项目引导. 这可能是使用Dropwizard最大的优势.
Also, 它将包括您在开发服务时需要的所有尖端库/工具. 所以你完全不需要担心这个. 它还提供了非常好的配置管理. 当然,Dropwizard也有一些缺点. 通过使用Dropwizard,你只能使用Dropwizard提供或支持的内容. 在开发过程中,您可能会失去一些自由. But still, I wouldn’t even call it a disadvantage, 因为这正是使Dropwizard成为它的原因-易于设置, easy to develop, 而且是一个非常健壮和高性能的REST框架.
In my opinion, 通过支持越来越多的第三方库来增加框架的复杂性也会在开发中引入不必要的复杂性.
具象状态传输(Representational State Transfer, REST)是一种架构风格(不能与标准集混用),它基于一组描述如何定义和处理网络资源的原则.
REST端点是REST服务的公开HTTP入口点. 在上面的示例中,GET /parts是一个端点,而POST /parts是另一个端点.
REST web服务是按照REST架构风格构建的任何web应用程序. 它监听HTTP或HTTPs端口上的请求,并将其端点公开给客户端.
它是一个松散耦合服务的体系结构. 但是,这些服务一起实现整个系统的业务逻辑. 微服务的最大好处在于,每个小服务都更容易开发, understand, 在不影响系统其余部分的情况下进行部署或维护.
Located in Belgrade, Serbia
Member since February 8, 2017
Dusan是一名后端Java开发人员,拥有8年的Java开发经验,参与过许多大型项目.
World-class articles, delivered weekly.
World-class articles, delivered weekly.
Join the Toptal® community.