发布于 2026-01-06 7 阅读
0

现代Java开发速度飞快,XML地狱:新的希望

现代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>
Enter fullscreen mode Exit fullscreen mode

真是个美人儿,不是吗?

我基本上已经厌倦了 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 = (...)
Enter fullscreen mode Exit fullscreen mode

由于测试覆盖率非常高,这个应用从上线到运行的第一时间就一切正常。唯一没有覆盖到的地方,也就是因此存在 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
    }
}
Enter fullscreen mode Exit fullscreen mode

这就是 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";
    }
}
Enter fullscreen mode Exit fullscreen mode

上面的代码发布了一个新的 REST 端点/appointments,以及两个GET用于检索所有预约和检索一天内所有预约的端点,还有一个POST用于创建新预约的端点。该类Appointment实际上可能是一个持久化实体,它会自动序列化为 JSON。路径变量将被正确解码并传递给 `types` 方法的参数。无需编写额外的代码。

参考此处。

Spring Data

对于非常简单的 CRUD 接口,我可以使用Spring Data存储库,甚至Spring Data REST直接为我的实体创建 REST 接口。

public interface OrderRepository extends CrudRepository<Order, Long> { }
Enter fullscreen mode Exit fullscreen mode

对于此存储库,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");
    }
}
Enter fullscreen mode Exit fullscreen mode

如您所见,这段代码包含很多内容。首先,启动一个名为 `<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
} 
Enter fullscreen mode Exit fullscreen mode

参考此处。

结合 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;
}
Enter fullscreen mode Exit fullscreen mode

}
`

使用@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;
Enter fullscreen mode Exit fullscreen mode

}
`

以上代码可以用作 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