现代Java开发速度很快
XML地狱
新的希望
原文发布于return.co.de
Java 开发既快又有趣,信不信由你。
我在播客中经常听到对 Java 的抨击,其中大部分人是从未从事过专业 Java 开发的开发者,他们认为 Java 只是糟糕的 Java 浏览器插件或丑陋的跨平台桌面应用程序。
但我说的不是这个。我的意思是,如今用 Java 编写高质量、可用于生产环境的代码非常快捷。所以,下面我概述一下我日常使用的工具和框架。
接下来这一部分会简单介绍一下我的背景以及我过去在使用Java时遇到的问题。不过,您可以直接跳过这部分,阅读“新的希望”部分。
XML地狱
几年前,我几乎放弃了 Java。我用 Java ME(用的是 Java 1.3,简直糟透了)做了好几年的开发,也用 Java SE 和 Spring 2 做过后端开发。此外,我还得维护一些用 Struts 做前端的遗留系统。
<?xml version = "1.0" encoding = "UTF-8"?>
<beans xmlns = "http://www.springframework.org/schema/beans"
xmlns:xsi = "http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation = "http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.0.xsd">
<bean id = "helloWorld" class = "de.co.return.HelloWorld">
<property name = "message" value = "Hello World!"/>
</bean>
</beans>
真是个美人儿,不是吗?
我基本上已经厌倦了 Spring 2 和 Struts,它们堆积如山的 XML 代码让重构变得极其痛苦,而且难以阅读。
于是我开始了下一个后端项目,用的是纯 Java SE。我完全采用测试驱动开发。速度很快,用 IntelliJ 生成了所有样板代码。我非常认真地对待依赖注入,甚至有点过度设计。几乎每个类我都写了接口,即使只有一个实现。我没有使用任何模拟框架,因为我之前有过一些糟糕的经历——EasyMock 甚至 PowerMock(千万别用!),所有模拟都是我在测试中实现接口来实现的。我还写了一个巨大的主引导类,它负责创建生产实例并执行实际的依赖注入。这个类足足有 500 行代码。
MyConfiguration config = loadConfiguration();
FirstClass firstClass = new FirstClass(config);
SecondClass secondClass = new SecondClass(config, "someParam");
ThirdClass thirdClass = new ThirdClass(firstClass, secondClass);
(...)
OneHundredTwelfthClass oneHundredTwelfthClass = (...)
由于测试覆盖率非常高,这个应用从上线到运行的第一时间就一切正常。唯一没有覆盖到的地方,也就是因此存在 bug 的地方,就是 SQL 查询语句,你猜对了,这些语句是我用纯 JDBC 编写的。
全部都是手动操作。这一切都是因为我过去使用任何框架的糟糕经历造成的。
我学到了很多,但我不想一辈子都做这个。所以我一直在寻找一些小众工作,比如 Erlang 后端开发(我仍然很喜欢这门语言)。实际上,我拒绝了一个相当不错的 Java 开发职位,因为面试时他们告诉我他们正在使用 Spring 框架。最终,我被一家专注于 Java 的大公司录用了。
新的希望
我加入了一个新团队,我们可以自由选择想要使用的技术。我们尝试了纯 Java、Camel、Java EE、vert.x,后来又出现了 Spring 4、Spring Boot 和其他一些让我大开眼界的工具。
春天——奇迹正在发生
Spring Core
借助Spring核心,我显然能够用它的@Autowire神奇功能替换掉如此丑陋的引导类,而且最重要的是,无需任何 XML 代码:
@Service
public class FooService {
private final FooRepository repository;
@Autowired
public FooService(FooRepository repository) {
this.repository = repository
}
}
这就是 Spring 的核心:依赖注入。它就像个贴心的管家,把你需要的一切都送到你面前。你只需要开口求助。剧透一下:有了 Lombok,它的功能会更强大,详见下文。
实际上,除了需要多个实现的地方之外,我完全放弃了“每个类一个接口”的模式。
Spring Web MVC - 给我一些 REST
我不需要任何 Web 服务器设置或配置,我可以轻松编写一个@Controller返回 Java 类实例并序列化为 JSON 的程序,无需任何代码。
@Controller
@RequestMapping("/appointments")
public class AppointmentsController {
private final AppointmentBook appointmentBook;
@Autowired
public AppointmentsController(AppointmentBook appointmentBook) {
this.appointmentBook = appointmentBook;
}
@GetMapping
public Map<String, Appointment> get() {
return appointmentBook.getAppointmentsForToday();
}
@GetMapping("/{day}")
public Map<String, Appointment> getForDay(@PathVariable @DateTimeFormat(iso=ISO.DATE) Date day, Model model) {
return appointmentBook.getAppointmentsForDay(day);
}
@PostMapping
public String add(@Valid Appointment appointment) {
appointmentBook.addAppointment(appointment);
return "redirect:/appointments";
}
}
上面的代码发布了一个新的 REST 端点/appointments,以及两个GET用于检索所有预约和检索一天内所有预约的端点,还有一个POST用于创建新预约的端点。该类Appointment实际上可能是一个持久化实体,它会自动序列化为 JSON。路径变量将被正确解码并传递给 `types` 方法的参数。无需编写额外的代码。
请参考此处。
Spring Data
对于非常简单的 CRUD 接口,我可以使用Spring Data存储库,甚至Spring Data REST直接为我的实体创建 REST 接口。
public interface OrderRepository extends CrudRepository<Order, Long> { }
对于此存储库,Spring Data REST 在 处公开了一个集合资源
/orders。该路径源自所管理域类的非大写、复数形式的简单类名。它还根据 URI 模板 公开了存储库管理的每个项的项资源/orders/{id}。*
*详情请参见此处。
再次强调,这其中有很多技巧,但确实很酷。在扫描 Spring 管理的类路径时,它会找到接口并动态创建一个存储库的实现,你可以使用这个实现从数据库中获取实体(以及执行所有 CRUD 操作),此外,它还会创建一个 REST 控制器来处理对该存储库的请求。
Spring集成测试
使用Mockito在测试中模拟对象非常有效,使用AssertJ@SpringBootTest进行测试断言,甚至可以使用真正的 HTTP 模拟服务器和内存数据库进行所有设置的集成测试,从而在没有任何外部资源的情况下运行完整的应用程序测试。
@RunWith(SpringRunner.class)
@SpringBootTest
public class MyTests {
@MockBean
private RemoteService remoteService;
@Autowired
private MyService service;
@Autowired
private MockRestServiceServer remoteServer;
@Test
public void exampleTest() {
this.remoteServer.expect(requestTo("/greet/details"))
.andRespond(withSuccess("hello", MediaType.TEXT_PLAIN));
given(this.remoteService.someCall()).willReturn("world");
String greeting = this.service.callRestService();
assertThat(greeting).isEqualTo("hello world");
}
}
如您所见,这段代码包含很多内容。首先,启动一个名为 `<server_name>` 的实际 Web 服务器,MockRestServiceServer它会在收到请求后做出响应/greet/details。然后,模拟您的应用程序服务RemoteService,假设该服务由被测类调用MyService。当调用该方法时someCall,它将返回“world”。最后,使用AssertJ进行验证。
Spring Security
唯一的缺点是使用了Spring Security来进行身份验证和授权。它提供了一系列现成的实现,也允许用户编写自定义实现AuthenticationProvider。但恕我直言,我认为它的设计很糟糕(从可用性角度来看),文档更是糟糕透顶。一旦你开始对其进行哪怕是最轻微的自定义,它就永远不会按预期工作,最终你会花费数小时甚至数天的时间通过反复试验来修复问题。简而言之,Spring Security 大多数时候都很糟糕。我希望它能尽快进行彻底的改进。
Spring Boot
Spring Boot 让您轻松创建可独立运行的生产级 Spring 应用。我们对 Spring 平台和第三方库采取了明确的策略,让您能够以最小的麻烦快速上手。大多数 Spring Boot 应用几乎不需要任何 Spring 配置。
- 请参考此处。
Spring Boot 将所有组件整合在一起,应用合理的默认配置,一切就绪。
如果你想看一个非常有趣且非常棒的现场编码示例,我强烈推荐 Josh Long 的视频“Bootiful” Applications with Spring Boot - 哇,太棒了!
JPA/Hibernate
几年前我用 Hibernate 的时候,也遇到过我之前提到的 XML 配置地狱。但用最新的 JPA,你只需要……
@Entity
public class Customer {
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
private String firstname;
private String lastname;
// … methods omitted or replaced by Lombok
}
请参考此处。
结合 Spring 数据存储库,您可以轻松访问实体。这意味着,您只需描述正在处理的数据,而无需关心如何获取这些数据。
最棒的是,你不需要编写任何 SQL 代码。Hibernate 非常擅长创建优化的查询。它会从数据库中提取数据并将其映射到@Entitiy类上。如果你的表名或列名不太美观,你可以简单地为字段添加注解。上面的类需要一个CUSTOMER包含列 `I`、,`FISRTNAME`和and`LASTNAME` 的表。如果你需要访问旧版数据库,例如,你可以根据需要进行自定义。
龙目岛 - 不再有追捕者
Java 8 发布时,我对这门老语言的进化能力印象深刻。然而,在我看来,它还缺少两样东西:模式匹配和摆脱 getter 和 setter 方法的途径。
我们仍然没有模式匹配(例如像 Erlang 那样),但是——这真的很棒——我们可以通过使用库来摆脱 getter 和 setter。许多框架都依赖于 getter 和 setter,例如 Hibernate、Jackson 和 Spring 本身。
我第一次看到Lombok时,以为这肯定是什么代码生成魔法,或者更糟——某种运行时反射技巧。而且,它的网站看起来像是 90 年代的产物。但当我深入了解后,发现我完全错了。他们使用的是javac编译时注解这一特性。因此,在编译过程中,Lombok 的代码会被执行,并生成额外的字节码,这些字节码不会产生额外的运行时开销(与手动编写 getter 和 setter 方法相比),也不会产生任何中间 Java 代码。
记住FooService上面提到的内容。使用@AllArgsContructorLombok,它会为所有成员生成一个构造函数。
`java
@Service
@AllArgsConstructor
@Sl4j
public class FooService {
private final FooRepository repository;
private final MyProperties properties;
public getUri(String path) {
log.debug("Building uri from base Url {} and path {}", properties.getSomeUrl(), path)
return properties.getSomeUrl() + path;
}
}
`
使用@Sl4j,我们可以获得一个免费的日志记录器(而不是执行Logger.getLogger(...)),并且还有更多好东西,例如生成@ToString和@EqualsAndHashcode。
使用Spring 4.3,你甚至可以@Autowire完全放弃。
`java
@Configuration
@ConfigurationProperties(prefix = "my.app")
@Getter
@setter
public class MyProperties {
private String someUrl;
private Integer someTimeout;
}
`
以上代码可以用作 Spring 属性来配置应用程序。你可能会觉得这段代码片段中的注解比代码还多,这没错。但我仍然更喜欢这种方式,而不是自己编写所有重复的代码。
它允许您编写如下所示的 YAML 文件,并将其拖放到 jar 文件旁边以更改配置:
yaml
my.app:
some-url: http://foo.bar
some-timeout: 5000
是的,它会自动允许您使用正确的命名,例如some-timeout仍然知道如何将其映射到someTimeout。您将免费获得应用程序的类型安全配置。您甚至可以将其@Validated与例如一起添加@Max(1000)。
Maven
Maven 是一个库管理工具。好吧,Maven 的确需要编写大量的 XML 代码,但它用起来真的非常棒。我之前用过 Ant,但要达到同样的效果却非常痛苦。
我原本很期待在 Android 项目中使用 Gradle,结果天哪,真是太糟糕了。你需要编写可执行脚本,这些脚本可以执行任何操作(而且真的会执行任何操作,尤其是在你不希望它执行的情况下)。你永远不知道你调用的是方法还是配置。我最终花了几个小时甚至几天的时间用 printf 调试我的构建脚本,直到它按我的预期运行为止。
正是因为有了 XML,Maven 才如此完善,几乎不可能出现非预期的破坏行为。Maven 是库依赖管理的正确打开方式,尤其与其他加载代码、构建代码的“包管理器”(例如 Xcode、Erlang)或像 NPM 和 Bower 这样的垃圾工具相比,Maven 的优势更加明显。Maven 只是构建流程中不可或缺的一部分,无需像在pipPython 中那样预先下载库,它始终会为当前构建的项目提供所需的库版本。
Java
如上所述,Java 8 引入了一些函数式特性,Lambda 表达式虽然并非完全符合函数式编程的原则,但却非常实用。Stream 乍一看很棒,但大多数情况下,最终你会得到一堆混乱不堪的代码,没人能看懂(甚至一周后你自己也看不懂)。通常情况下,使用传统的 for 循环会更好。
但总的来说,我会尽可能地编写函数式代码(这从 Java 的第一个版本开始就可行了)。这意味着,一个方法应该始终返回一个值(而不是 void),并且对于相同的参数,它应该始终返回相同的值。这要求……
- 不要使用系统调用(例如
currentTimeMillis()) - 不与文件、数据库等进行交互。
- 使用类变量
显然,如果你完全这样做,你的应用程序就完全没用了,因为你既无法控制它,也无法获得任何输出。所以我尽量保持核心业务逻辑的简洁,并将副作用尽可能地移到外部。这极大地有利于测试。
集成开发环境
我过去用过很多IDE,从QBasic、Turbo Pascal、Visual Studio、Eclipse、NetBeans、Xcode等等vim。但我最终还是爱上了IntelliJ IDEA,它是我用过的最好的IDE。它界面美观、运行速度快,支持非常出色且直观的键盘快捷键,所有菜单(包括上下文菜单)都支持自动输入和自动筛选。它还提供了强大的重构功能和代码生成功能。此外,它还支持非常优秀的调试功能,并能在线显示代码覆盖率。
你现在可能对你的 IDE 很满意,但你至少应该试用一下 IDEA,并观看一段关于其功能的视频。
尤其是我用 Xcode 开发应用项目的时候,感觉跟 IntelliJ 比起来简直像回到了石器时代。可惜的是,即使是 JetBrains AppCode,有些功能也仍然需要 Xcode。
这是生态系统
所以,关键不在于语言,也不在于集成开发环境(IDE)或构建工具。关键在于所有因素的综合作用,正是这些因素使得Java开发既快速又有趣。
文章来源:https://dev.to/stealthmusic/modern-java-development-is-fast