首页 >> 大全

[翻译]现代java开发指南 第三部分

2023-09-12 大全 26 作者:考证青年

增加下面代码到构建文件:

compile 'io.dropwizard:dropwizard-client:0.7.0'

导入以下库到 .Main:

import io.dropwizard.client.*;
import com.sun.jersey.api.client.Client;

增加下面代码到 :

@Valid @NotNull @JsonProperty JerseyClientConfiguration httpClient = new JerseyClientConfiguration();
public JerseyClientConfiguration getJerseyClientConfiguration() { return httpClient; }

我们将实例化客户端,并注册一个新服务,我们将其称为 ,并添加到 run 方法中:

Client client = new JerseyClientBuilder(env).using(cfg.getJerseyClientConfiguration()).build("client");
env.jersey().register(new ConsumerResource(client));

下面是我们的服务:

@Path("/consumer")
@Produces(MediaType.TEXT_PLAIN)
public static class ConsumerResource {private final Client client;public ConsumerResource(Client client) {this.client = client;}@Timed@GETpublic String consume() {Saying saying = client.resource(UriBuilder.fromUri("http://localhost:8080/hello-world").queryParam("name", "consumer").build()).get(Saying.class);return String.format("The service is saying: %s (id: %d)",  saying.getContent(), saying.getId());}
}

注意到方法返回的 JSON 对像是如何反序列化成 对象的;它也可以是 Map, 以及其他类型(使用的是 JAX-RS客户端的旧版本,新的API类似)。而且由于 开箱即用地支持 JAX-RS 客户端,因此会自动发持请求的性能指标。

要测试我们的新服务,启动我们的应用程序( run ,记住)并将浏览器指向 http://:8080/。

所以 JAX-RS 标准也标准化了客户端的 API。但是,正如我们之前所说的,当谈到客户端 API 时,我们也可以使用非标准的API。一个颇受欢迎的HTTP客户端 是由 提供的。如你所见,JAX-RS 客户端可以自动将 Java 对象序列化并反序列化为 JSON 对象(或 XML)。 把这种转化用在 Java/REST 转换上(这种转换并不总是一件好事;领域模型的转换通常具有抽象漏洞,但如果你只限于用于简单的协议,应该会很有帮助),包括服务 URL,而不仅仅是 JSON 到 Java 接口的转换。不幸的是, 使用与 JAX-RS(服务器)相同的注解名称。因此我们要在不同的包中定义,这会使我们的示例有点难看。幸运的是, 有 提供的称为 Feign 的克隆/衍生产品。Feign 和 之间的差异并对我来说并不完全清楚。尽管看起来 更广泛地被采用(它更成熟),而 Feign 更容易定制。无论如何,这两者非常相似,可以互换使用。

试试 Feign,将以下依赖添加到 build. :

compile 'com.netflix.feign:feign-core:6.1.2'
compile 'com.netflix.feign:feign-jaxrs:6.1.2'
compile 'com.netflix.feign:feign-jackson:6.1.2'

导入到 Main:

import feign.Feign;
import feign.jackson.*;
import feign.jaxrs.*;

我们用 Feign 代替 JAX-RS:

Feign.Builder feignBuilder = Feign.builder().contract(new JAXRSModule.JAXRSContract()) // we want JAX-RS annotations.encoder(new JacksonEncoder()) // we want Jackson because that's what Dropwizard uses already.decoder(new JacksonDecoder());
env.jersey().register(new ConsumerResource(feignBuilder));

现在我们的消费服务看起来如下:

@Path("/consumer")
@Produces(MediaType.TEXT_PLAIN)
public static class ConsumerResource {private final HelloWorldAPI hellowWorld;public ConsumerResource(Feign.Builder feignBuilder) {this.hellowWorld = feignBuilder.target(HelloWorldAPI.class, "http://localhost:8080");}@Timed@GETpublic String consume() {Saying saying = hellowWorld.hi("consumer");return String.format("The service is saying: %s (id: %d)",  saying.getContent(), saying.getId());}
}

最后,我们添加 接口,该接口把 REST API 说明定入代码中(你可以将接口定义放在我们的 Main 类中;不需要创建新的Java文件):

interface HelloWorldAPI {@GET @Path("/hello-world")Saying hi(@QueryParam("name") String name);@GET @Path("/hello-world")Saying hi();
}

此接口使用 JAX-RS 注解说明如何把方法转换 http 为请求。实际执行转换是由 Feign(或)自动完成的。

启动应用后,访问 :8080/ 以测试新的服务。

如果想看到更复杂的 REST API 是如何转换为Java代码的, 这个简单的例子演示使用 消费 的API,还有这里使用 Feign。 和 Feign 功能都非常丰富,可以很好地控制请求的转换和执行方式。此时,我会推荐 而不是 Feign,因为 更成熟,它利用了高效的 NIO 网络 API,而 Feign使用慢速的 API( 更好的传输机制可以添加进 Feign 中,但我还没有找到)。

还有其他一些较底层的 HTTP 客户端 API(例如 HTTP , 也直接支持),但在大多数情况下,我们刚才试过的高层次的 API(JAX-RS 或 / Feign)效果最佳。

数据库访问

JDK 包含用于(关系)数据库访问的标准 API,称为 JDBC (Java数据库连接)。几乎所有的 SQL 数据库都支持 JDBC。但是 JDBC 是一个非常低级的 API,有时可能会令人厌烦。Java 还有一个标准的高级数据库访问 API - 实际上是一个ORM--被 JSR-220 和 JSR-317 叫做 JPA(Java API)。JPA的知名实现包括 , 和 。请不要使用他们,我相信以后你会感谢我。并不是说他们工作的不好,是因为他们往往比他们的带来麻烦比价值更多。ORM 鼓励复杂的对象图和复杂的模式,这往往会导致生成非常复杂的 SQL 语句,这些语句很难优化。另外,ORM 并不以其出色的性能而闻名。

直接使用 JDBC 通常更好,但也许最好的方法是使用我们现在提供的工具之一。它位于低级 JDBC 和高级的 ORM 之间。它不是标准的,这意味着每个工具都有它自己的 API。但正如我们所说的,不使用标准 API 适合于客户端 API。在下面我们的例子中,我们使用H2 嵌入式数据库。

我们将从 JDBI 开始,这也由 直接支持。要有效地使用 JDBI ,你需要权衡最佳模式和简单代码,直到您达到一个很好的中间地带(JDBI 对于非常复杂的模式并不理想)。

我们添加这些依赖关系:

compile 'io.dropwizard:dropwizard-db:0.7.0'
compile 'io.dropwizard:dropwizard-jdbi:0.7.0'
runtime 'com.h2database:h2:1.4.178'

并且导入:

import io.dropwizard.db.*;
import io.dropwizard.jdbi.*;
import org.skife.jdbi.v2.*;
import org.skife.jdbi.v2.util.*;

然后,我们增加 工厂类到 :

DBI dbi = new DBIFactory().build(env, cfg.getDataSourceFactory(), "db");
env.jersey().register(new DBResource(dbi));

为了配置数据库,我们需要将以下内容添加到 .yml:

database:driverClass: org.h2.Driverurl: jdbc:h2:mem:testuser: upassword: p

最后,让我们创建数据库资源:

@Path("/db")
@Produces(MediaType.APPLICATION_JSON)
public static class DBResource {private final DBI dbi;public DBResource(DBI dbi) {this.dbi = dbi;try (Handle h = dbi.open()) {h.execute("create table something (id int primary key auto_increment, name varchar(100))");String[] names = { "Gigantic", "Bone Machine", "Hey", "Cactus" };Arrays.stream(names).forEach(name -> h.insert("insert into something (name) values (?)", name));}}@Timed@POST @Path("/add")public Map add(String name) {try (Handle h = dbi.open()) {int id = h.createStatement("insert into something (name) values (:name)").bind("name", name).executeAndReturnGeneratedKeys(IntegerMapper.FIRST).first();return find(id);}}@Timed@GET @Path("/item/{id}")public Map find(@PathParam("id") Integer id) {try (Handle h = dbi.open()) {return h.createQuery("select id, name from something where id = :id").bind("id", id).first();}}@Timed@GET @Path("/all")public List> all(@PathParam("id") Integer id) {try (Handle h = dbi.open()) {return h.createQuery("select * from something").list();}}
}

对于那些了解 JDBC 的人,这些代码有很多熟悉的和不同的地方。JDBI 有一个流畅的接口,并且方法返回Java集合,并将其自动地序列化为 JSON 对象。总之,这就像一个有趣的"现代"JDBC。

启动应用程序并将浏览器指向 :8080/db/all 以查看所有条目,或者在:8080/db/item/ 2 处查看第二个条目。然后,您也可以通过控制台创建新的条目:

curl --data Velouria http://localhost:8080/db/add

JDBI 还可以像 一样,提供一个数据库使用量身定制的定制界面。通过将 JDBI 将表行映射为 Java 对象,我们还可以获得一些小技巧。

这是我们的对象:

public static class Something {@JsonProperty public final int id;@JsonProperty public final String name;public Something(int id, String name) {this.id = id;this.name = name;}
}

@ 注释将确保这个属性自动将它 JSON 序列化,但为了使 JDBI 能够与 一起工作,我们还需要创建一个 ,它 将JDBC 转换为 对象:

public static class SomethingMapper implements ResultSetMapper {public Something map(int index, ResultSet r, StatementContext ctx) throws SQLException {return new Something(r.getInt("id"), r.getString("name"));}
}

现在有意思的事情就要开始了!这是我们的 DAO 类(或JDBI说法中的 SQL 对象 ) - JDBI SQL 对象是数据库就像 对于 REST 的改造:

@RegisterMapper(SomethingMapper.class)
interface ModernDAO {@SqlUpdate("insert into something (name) values (:name)")@GetGeneratedKeysint insert(@Bind("name") String name);@SqlQuery("select * from something where id = :id")Something findById(@Bind("id") int id);@SqlQuery("select * from something")List all();
}

那现在,我们新的数据库资源可以这样写:

@Path("/db")
@Produces(MediaType.APPLICATION_JSON)
public static class DBResource {private final ModernDAO dao;public DBResource(DBI dbi) {this.dao = dbi.onDemand(ModernDAO.class);try (Handle h = dbi.open()) {h.execute("create table something (id int primary key auto_increment, name varchar(100))");String[] names = { "Gigantic", "Bone Machine", "Hey", "Cactus" };Arrays.stream(names).forEach(name -> h.insert("insert into something (name) values (?)", name));}}@Timed@POST @Path("/add")public Something add(String name) {return find(dao.insert(name));}@Timed@GET @Path("/item/{id}")public Something find(@PathParam("id") Integer id) {return dao.findById(id);}@Timed@GET @Path("/all")public List all(@PathParam("id") Integer id) {return dao.all();}
}

JDBI并不是一个完整的 ORM 解决方案:它不会自动生成 SQL 语句,也不会自动生成完整的对象图,但它确使我们获得了数据库访问的快捷方法,其量级远低于任何 JPA 实现。

使用 JDBI 时, 会自动添加一个运行状况检查( :8081/),用于测试数据库的连通性,并用监控 DAO 的性能指标:

下面我们会看到的数据库访问库 jOOQ,它与 JDBI 流畅 API 类似(它没有与 JDB I的 SQL 对象类似的API),但它采用了不同的方法:它使用方法调用链而不是字符串,生成 SQ L语句(并且它可以生成的SQL兼容的多种数据库)。

我们将添加这个依赖关系:

compile 'org.jooq:jooq:3.3.2'

导入库:

import org.jooq.Record;
import org.jooq.RecordMapper;
import static org.jooq.impl.DSL.*;

在 run 方法中,注册数据库资源:

DataSource ds = cfg.getDataSourceFactory().build(env.metrics(), "db"); // Dropwizard will monitor the connection pool
env.jersey().register(new DBResource(ds));

我们的新 如下所示:

@Path("/db")
@Produces(MediaType.APPLICATION_JSON)
public static class DBResource {private final DataSource ds;private static final RecordMapper toSomething =record -> new Something(record.getValue(field("id", Integer.class)), record.getValue(field("name", String.class)));public DBResource(DataSource ds) throws SQLException {this.ds = ds;try (Connection conn = ds.getConnection()) {conn.createStatement().execute("create table something (id int primary key auto_increment, name varchar(100))");String[] names = { "Gigantic", "Bone Machine", "Hey", "Cactus" };DSLContext context = using(conn);Arrays.stream(names).forEach(name -> context.insertInto(table("something"), field("name")).values(name).execute());}}@Timed@POST @Path("/add")public Something add(String name) throws SQLException {try (Connection conn = ds.getConnection()) {// this does not workint id = using(conn).insertInto(table("something"), field("name")).values(name).returning(field("id")).fetchOne().into(Integer.class);return find(id);}}@Timed@GET @Path("/item/{id}")public Something find(@PathParam("id") Integer id) throws SQLException {try (Connection conn = ds.getConnection()) {return using(conn).select(field("id"), field("name")).from(table("something")).where(field("id", Integer.class).equal(id)).fetchOne().map(toSomething);}}@Timed@GET @Path("/all")public List all(@PathParam("id") Integer id) throws SQLException {try (Connection conn = ds.getConnection()) {return using(conn).select(field("id"), field("name")).from(table("something")).fetch().map(toSomething);}}
}

现在,jOOQ 还没有实现 DDL(像 table 这样的 SQL 语句),所以你会注意到我们使用 JDBC 创建表。不过这也很好,因为 jOOQ 是作为一个JDBC包装器实现的,无论如何都需要 JDBC(我还没有能使add的正确工作的方法(可能是因为自动生成的主键的原因)jOOQ 的开发人员:如果你正在阅读这个,请帮帮忙)。

这个例子实际上并没有正确的使用 JOOQ 正义,因为它的最大优点是能够从数据库的 生成 class,并且能够以类型安全的方式执行我们之前完成的所有操作 - 以及更复杂的操作。对我个人来说,JOOQ 有点太智能了,但是如果你的模式很复杂,它可能是一个非常有用的工具。

_指南怎么翻译_java指南pdf

依赖注入

依赖注入是否有用或无用取决于你问的对象。我相信 DI 在复杂的代码库中非常有用;对于简单代码来说,这不必要。Java 有一个由 JSR-330 指定的简单标准 DI API。JSR-330 有以下实现: IoC , Guice , , Sisu (建立在Guice之上)和 HK2 。这些实现都是由大公司或组织开发的。鉴于这种情况,人们往往面临着两难选择。我认为你不要害怕:如果你坚持JSR-330标准,或者稍有偏差的实现,您可以随时更改您的DI解决方案。但如果你想让你的应用程序完全由用户配置(XML文件的形式),选择(这就是为什么我们选择 for );如果都不是,那么从开始,只有当它不再满足你的需求时才去找别的东西。

我们来看看。首先,让我们添加依赖关系:

compile 'com.squareup.dagger:dagger:1.2.1'
compile 'com.squareup.dagger:dagger-compiler:1.2.1'

为了保持整洁,我们只留下 。不过,这一次,我们不手动创建服务并将配置对象传递给它,而是使用 从 YAML 文件读取我们的配置,然后将它们注入到我们的服务中。

这是服务代码:

@Path("/hello-world")
@Produces(MediaType.APPLICATION_JSON)
public static class HelloWorldResource {private final AtomicLong counter = new AtomicLong();@Inject @Named("template") String template;@Inject @Named("defaultName") String defaultName;HelloWorldResource() {}@Timed // monitor timing of this service with Metrics@GETpublic Saying sayHello(@QueryParam("name") Optional name) throws InterruptedException {final String value = String.format(template, name.or(defaultName));Thread.sleep(ThreadLocalRandom.current().nextInt(10, 500));return new Saying(counter.incrementAndGet(), value);}
}

请注意@ 和 @Named 注释。这些是 JSR-330 标准的一部分,所以无论我们使用哪种 DI 工具,我们的服务代码都将保持不变。要实际连接并注入依赖关系,我们使用 Dagge r特定的模式。 在模块类中指定了依赖配置。这是我们的:

@Module(injects = HelloWorldResource.class)
class ModernModule {private final JModernConfiguration cfg;public ModernModule(JModernConfiguration cfg) {this.cfg = cfg;}@Provides @Named("template") String provideTemplate() {return cfg.getTemplate();}@Provides @Named("defaultName") String provideDefaultName() {return cfg.getDefaultName();}
}

最有用的功能之一是它在编译时使用注释处理器验证所有依赖关系是否满足。例如,如果我们忘记定义 ,那么当我们键入时,这就 是 中显示的内容:

为了获得完整配置的 实例,我们在应用程序的 run 方法中放入了这个实例:

ObjectGraph objectGraph = ObjectGraph.create(new ModernModule(cfg));
env.jersey().register(objectGraph.get(HelloWorldResource.class));

你会发现, 类复制 的一些行为。使用 @ 简单注解 ,以及使用 @ 注解 和 方法非常简单。 禁止子类型注解。

高级主题:阻塞与非阻塞 VS 同步与异步

在这个话题上,我们需要对阻塞与非阻塞 API 的更多理论讨论。阻塞或同步是方法会阻塞调用线程直到它们完成。当然,阻塞(或非阻塞)的概念只有在这些方法可能需要很长时间才能完成时(例如几十毫秒到几十秒)才有意义。另一种类型的API,通常称为非阻塞,但在这里我们称它们为半阻塞(或半异步),它是在操作期间不会阻塞调用线程的方法。他们只启动一项操作并返回 对象。 对象用于等待待等待操作成然后在方便的时间完成后面的操作。最后,第三种类型的 API-真正的非阻塞或异步 API,它也不会阻塞调用线程。但它的方法需要一个额外的参数 - 一个回调函数 ,它是在操作完成时将执行的代码(在某个未知的线程上)。有时候,Java API 混合了最后两种类型,既有回调又有 返因对象。

必须明确:异步 API 的总是比阻塞的 API 更复杂(即使语言本身试图使回调更容易使用,通过使用如 ,, monad 等函数式方案)。除了支持多线程的 之外,异步的问题在 Java 这样的语言中尤其糟糕,包括基本上所有其他的 JVM 语言。我们在这里不会详细讨论 不限制副作用的问题。在这些语言中使用非阻塞 API 需要严格的规范,并且需要对复杂的并发问题有清晰的理解。阻塞 API 则没有这些问题。

为什么有人会使用异​​步 API?答案很简单:性能。更深刻一点,内核线程进行任务切换的成本不可忽略(这里不是说可以快速释放线程内存堆栈,快速释放线程堆栈这将更好地用于数据高速缓存)。现代 Web 应用程序通常会将实际处理委托给无数的服务,有些会做离线 map-,其他可能会做一些在线处理,面向客户端的 Web 服务器的主要功能是协调:它调用许多其他服务并组装数据。它几乎不做任何处理,但它执行大量的 IO 操作 - 有些可以并行完成,有些需要连续调用。这意味着 Web 服务器在相对较少的 CPU 工作时间内会生成很多线程调度事件(线程阻塞和解除阻塞),这种时候,操作系统的线程调度开销变得繁重。因此,人们为了解决这个内核线程调度性能问题而将代码置于异步 API 这种不自然的扭曲之中。一些现代 We b框架/库也非常喜欢使用非阻塞 API(我们没有讨论过其中的任何一个,因为我们说明,他们都是错误的)。

这是错误的方法 。为了迎合不合理的实现,人们放弃了适当的抽象(线程),而不是简单地修复不合理实现。轻量级(或用户级)线程已在 ,Go 中使用,现在通过 库在 JVM 中使用 - 可让您使用简单的阻塞 API,而不存在任何性能问题。

这种情况在计算机科学中非常罕见的。一种充满了折衷和警告的导步方法几乎总是击败另一种同步方法。异步代码与同步代码相比具有许多缺点和绝对劣势。即使轻量级线程的不完美实现也比异步编程更好,特别是当语言对共享状态突变不做防范时。这个规则可能有一些例外(毕竟,在 C S中,即使绝对不是绝对如此),但它们远少于建议使用 goto 语句时的情况。

同步和异步是可以相互转换的(每个都可以使用“恒定时间”转换转换为另一个),但同步对人类来说是更好的抽象,我可以证明这一点。我们来看两个 API:

interface Sync {Object pull();
}

和:

interface Async {void push(Callback cb);
}interface Callback {void got(Object obj);
}

现在让我们使用 Sync 实现 Async:

Async syncToAsync(Sync sync) {return new Async() {public void push(final Callback cb) {new Thread(() -> {for(;;)cb.got(sync.pull());}).start();}}
}

现在,用您最喜欢的编程语言实现相反的功能,即将 Async 转换为 Sync 。这将更加棘手,总是需要引入一些中间数据存储,如队列或缓冲区。当然,你需要考虑到 .got 可以在任何线程上调用,所以你需要考虑与该数据结构的并发性。因此,从Async 到 Sync 的转换不仅不那么简单,而且引入了不必要的数据存储:如果真没有引入多余的数据存储,是因为它可能已经内置到系统中(例如以IO缓冲区的形式)。所以 Async 使用 Sync 简单的实现,但是相反的转换既浪费又浪费时间,并且需要管理并发。但这对限制或管理副作用的语言(如 或 )来说不是什么问题。

项目将标准(和非标准但良好的) 相关 API 与 (轻量级线程)集成在一起。 的下一个版本将支持本文讨论的工具(可能有 jOOQ 和 / Feign例外),这样你就可以编写相同简单的阻塞代码,但可以获得异步代码的性能和可伸缩性优势。在未来的博客文章中,我们将展示 如何不破坏你的代码,同时让您的应用程序具更好的可伸缩性。

高级主题:使用Web Actor与Web服务交互

虽然通常你应该坚持使用标准的服务器 API,但有时候替代方案会带来显着的优势。这里没有涉及的主题之一是使用 或 SSE 等技术的交互式 Web 服务。虽然 Java 的标准 API 支持两者,但是特别是使用 可能会导致复杂的并发问题,因为标准 Java API( JSR-356 )是异步的。这意味着 消息可能会同时到达服务器端,比如来自同一用户的 HTTP 请求。这样的化,异步 API 要管理可变的共享状态,这种情状很糟糕。 提供了一种称为 Web 的 API,它能为每一个用户对话分配一个 actor,它意味着接收同步化,使得状态管理更容易。要了解有关Web 的更多信息,请阅读介绍性博客文章。

结论

这篇就结束了“现代 Java 开发的意见指南”(尽管我可能会发布一个回应反馈的文章)。我希望你喜欢阅读它,就像我喜欢写它一样。我希望我能够传达出 Java 生态系统不仅是巨大的,而且还充满活力和与时俱进:用 和流代替冗长的数据操作代码; 取代 HTML;fiber, 和 actor 取代锁和回调;简单的嵌入式服务器取代了重量级,笨重的应用服务器。在所有这些功能下面,是强大,灵活的 JVM,它强调性能和监控,它能支持运行时代码注入和替换。

原文地址:An Guide to Java, Part 3: Web

水平有限,如果看不懂请直接看英文版。

关于我们

最火推荐

小编推荐

联系我们


版权声明:本站内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至 88@qq.com 举报,一经查实,本站将立刻删除。备案号:桂ICP备2021009421号
Powered By Z-BlogPHP.
复制成功
微信号:
我知道了