快速入门

安装

JDK

毫无疑问,要想玩Java,就必须得先装Java JDK,目前公司主要使用的是Oracle JDK 8,安装完成后要配置环境才能正常使用,真蠢,不过也就那么一下下,认了吧。配置方法参考: http://www.runoob.com/java/java-environment-setup.html

IDE

个人认为Java最好用的IDE就是IntelliJ IDEA (后面会简称IDEA)。IDEA最大的优势就是可定制能力很高。同时有着各种各样的插件能提供不少的扩展和便利。但是个人是希望统一使用默认的配置和统一的代码风格,不然在代码审查和指导的时候,各种快捷键按了没反应,会很尴尬。默认配置也是经过考验的,基本不会有多少反人类的快捷键,习惯就好。

常用插件

  • PlantUML 使用代码绘画UML图
  • Python 编写Python
  • Markdown 编写Markdown文档
  • google protocol 微服务Grpc框架
  • Lombok 扩展Java的语法特性

配置(Ctrl+Shift+A)

  • Show Line Number -> on
  • Code Style -> Manager -> Import -> Google Style

Maven

目前使用Maven作为内部的依赖包管理工具,在使用之前需要配置内部的Maven私库,否则会默认连到外部的公有Maven仓库上。

Hello Java

个人认为TDD是个特别好的实践。所以从Hello Test开始吧。那么先了解一下Java的单元测试所用到的一些技术。

  • 测试用到的技术
    • JUnit 4:通用测试框架。官网
    • Mockito:在自动化单元测试中创建测试对象,为TDD或BDD提供支持。
    • PowerMock: 支持模拟静态方法、构造函数、final类和方法、私有方法以及移除静态初始化器的模拟工具。官网
    • AssertJ:支持流式断言提高测试的可读性。

Hallo Test

新建一个Maven项目,下一步,然后随便起个命名空间com.xudashu,再随便起个名hallo-test,然后随便起个方案名hallo-test,完成。 目录结构非常的清晰: HalloTest main里面是代码文件, test里面是测试文件, pom.xml是maven的依赖管理配置文件

首先在test的java里面编写SomeThingTest。 断言:assertThat(someThing.Do()).isEqualTo("Hallo Test"); 然后alt+enter生成java源码,在生成的SomeThing.Do里面return "Hallo Test";运行测试 mvn clean test。查看hallo-test源码

Hallo Grpc

目前公司使用的微服务的核心技术主要是使用Google开源的GRPC,GRPC使用的是google protocol,(注:在编写这篇文档时,刷了一下git log,最新的GRPC(1.0.0-pre2)加入了thrift的支持。)

使用GRPC,当然要先引入GRPC的依赖包

GRPC的依赖包pom配置

    <properties>
        <grpc.version>1.0.0-pre2</grpc.version><!-- CURRENT_GRPC_VERSION -->
    </properties>

    <dependencies>
        <!-- https://mvnrepository.com/artifact/io.grpc/grpc-all -->
        <dependency>
            <groupId>io.grpc</groupId>
            <artifactId>grpc-all</artifactId>
            <version>${grpc.version}</version>
        </dependency>

    </dependencies>

所有跨语言的RPC框架,几乎都会使用IDL接口描述语言,GRPC也不例外,GRPC的IDL接口描述语言语法大量参考了C++语法,所以相当简单易懂。

那么在使用之前,需要通过 protocol buffer 的编译器 protoc 以及一个特殊的 Maven插件将IDL转化成Java语言才能正常的使用。

Maven插件的pom配置

   <build>
       <extensions>
           <extension>
               <groupId>kr.motd.maven</groupId>
               <artifactId>os-maven-plugin</artifactId>
               <version>1.4.1.Final</version>
           </extension>
       </extensions>
       <plugins>
           <plugin>
               <groupId>org.xolstice.maven.plugins</groupId>
               <artifactId>protobuf-maven-plugin</artifactId>
               <version>0.5.0</version>
               <configuration>
                   <!--
                     The version of protoc must match protobuf-java. If you don't depend on
                     protobuf-java directly, you will be transitively depending on the
                     protobuf-java version that grpc depends on.
                   -->
                   <protocArtifact>com.google.protobuf:protoc:3.0.0:exe:${os.detected.classifier}</protocArtifact>
                   <pluginId>grpc-java</pluginId>
                   <pluginArtifact>io.grpc:protoc-gen-grpc-java:${grpc.version}:exe:${os.detected.classifier}</pluginArtifact>
               </configuration>
               <executions>
                   <execution>
                       <goals>
                           <goal>compile</goal>
                           <goal>compile-custom</goal>
                       </goals>
                   </execution>
               </executions>
           </plugin>
       </plugins>
   </build>

配置好环境就可以开工了,首先定义好接口契约。

IDL契约

syntax = "proto3";

option java_multiple_files = true;
option java_outer_classname = "HelloWorldProto";

package com.xudashu.helloworld;

// The greeting service definition.
service Greeter {
    // Sends a greeting
    rpc SayHello (SayHelloRequest) returns (SayHelloResponse) {}
}

// The request message containing the user's name.
message SayHelloRequest {
    string name = 1;
}

// The response message containing the greetings
message SayHelloResponse {
    string message = 1;
}

然后mvn clean install生成代码,在Service层新建GreeterImpl类,继承GreeterGrpc.GreeterImplBase

public class GreeterImpl extends GreeterGrpc.GreeterImplBase {
    @Override
    public void sayHello(SayHelloRequest request, StreamObserver<SayHelloResponse> responseObserver) {
        SayHelloResponse response = SayHelloResponse.newBuilder()
                .setMessage("Hallo " +  request.getName()).build();

        responseObserver.onNext(response);
        responseObserver.onCompleted();
    }
}

在Test里面,新建Application,使用NettyServer来启动Grpc服务。

public static void main(String[] args) throws Exception {
    int port = 8080;
    NettyServerBuilder.forPort(port)
            .addService(ServerInterceptors.intercept(new GreeterImpl()))
            .build().start();
}

启动服务后,使用NettyChannel来创建Grpc的通道,创建完成后,和调用本地应用一样简单方便了。

      channel = NettyChannelBuilder.forAddress("127.0.0.1", port)
              .negotiationType(NegotiationType.PLAINTEXT)
              .build();

      blockingStub = GreeterGrpc.newBlockingStub(channel);

      SayHelloRequest request = SayHelloRequest.newBuilder()
               .setName("许大叔")
               .build();

       SayHelloResponse response = blockingStub.sayHello(request);

       assertThat(response.getMessage()).isEqualTo("Hallo 许大叔");

查看hallo-grpc源码

代码风格

由于使用的是Google的GRPC,所以整体编码风格高度推荐 Google's Java Style 这个教程。写的最好的部分是 Programming Practices。绝对值得一读。

JavaBean

Java 传统的代码风格是被用来编写非常复杂的企业级 JavaBean。新的代码风格看起来会更加整洁,更加正确,并且更加简单。

对我们程序员来说,包装数据是最简单的事情之一。下面是传统的通过定义一个 JavaBean 的实现方式:

public class DataHolder {
    private String data;

    public DataHolder() {
    }

    public void setData(String data) {
        this.data = data;
    }

    public String getData() {
        return this.data;
    }
}

这种方式既繁琐又浪费代码。即使你的 IDE 可以自动生成这些代码,也是浪费。因此,别这么干.

相反,我更喜欢 C 语言保存数据的风格来写一个类:

public class DataHolder {
    public final String data;

    public DataHolder(String data) {
        this.data = data;
    }
}

这样不仅减少了近一半的代码行数。并且,这个类里面保存的数据除了你去继承它,否则不会改变,由于它不可变性,我们可以认为这会更加简单。

如果你想保存很容易修改的对象数据,像 Map 或者 List,你应该使用 ImmutableMap 或者 ImmutableList,这些会在不变性那一部分讨论。

构建器模式(The Builder Pattern)

如果有仔细阅读上面Hallo-grpc的代码,就会发现以下这类的代码,其实这就是Google用构造者模式来的方式构造的对象。

SayHelloRequest.newBuilder()
         .setName("许大叔")
         .build();

你可以建一个静态内部类来构建你的对象。构建器构建对象的时候,对象的状态是可变的,但是一旦你调用了 build 方法之后,构建的对象就变成了不可变的了。

想象一下我们有一个更复杂的 DataHolder。那么它的构建器看起来应该是这样的:

public class ComplicatedDataHolder {
    public final String data;
    public final int num;
    // lots more fields and a constructor

    public static class Builder {
        private String data;
        private int num;

        public Builder data(String data) {
            this.data = data;
            return this;
        }

        public Builder num(int num) {
            this.num = num;
            return this;
        }

        public ComplicatedDataHolder build() {
            return new ComplicatedDataHolder(data, num); // etc
        }  
    }
}

然后调用它:

final ComplicatedDataHolder cdh = new ComplicatedDataHolder.Builder()
    .data("set this")
    .num(523)
    .build();

这有关于构建器更好的例子,他会让你感受到构建器到底是怎么回事。它没有使用许多我们尽力避免使用的样板,并且它会给你不可变的对象和非常好用的接口。

可以考虑下在众多的库中选择一个来帮你生成构建器,取代你亲手去写构建器的方式。

Lombok

Lombok 是一个很有意思的库。它可以让你以注解的方式减少 Java 中糟糕的样板代码。

想为你的类的变量添加 setter 和 getter 方法吗?像这样:

@Getter
@Setter
public class Foo {
   private int var;
}

现在你就可以这么用了:

final Foo foo = new Foo();
foo.setVar(5);

又或者创建一个构建器,像这样:

@Getter
@Builder
public class Foo {
   private int var;
}

这还有很多例子。最不可思议的是使用代码生成器的方式而不是反射,所以对性能没有任何副作用。虽然我已经做了大量的测试和检验,并且迫不及待的想在团队里推广,但很遗憾,没有想象中那么顺利。如果你的团队允许的情况下,个人是高度推荐使用,相信我,你会爱上它的。

避免使用空值

使用GRPC的时候就会注意到,如果set一个null值就会报错。google的思想就是尽一切可能避免使用空值。不要返回 null 的集合,你应该返回一个 empty 的集合。如果你确实准备使用 null 请考虑使用 @Nullable 注解。IntelliJ IDEA 内置支持 @Nullable 注解。

如果你使用的是 Java 8,你可以用新出的优秀的 Optional 类型。如果有一个值你不确定是否存在,你可以在类中用 Optional 包裹住它们:

private final Optional<Bar> bar;

这样,Optional 有如 isPresent 这样的方法,可以用来检查是否为 null,虽然感觉并不能省什么工作,但是至少起了一个明示的作用。

Maven

Maven 是构建,打包和测试的标准。有很多不错的替代工具,如 Gradle,但是他们同样都没有像 Maven 那样的适应性。如果你是 Maven 新手,你应该从Maven 实例这里开始。

我喜欢用一个根 POM(Project Object Model,项目对象模型)来管理所有用到的外部依赖。它会像这个样子。这个根 POM 仅仅包含一个外部依赖,但是如果你的产品足够大,你将会有几十个外部依赖了。你的根 POM 应该像其他 Java 项目一样采用版本控制和发布的方式,有一个自己的项目。

如果你认为你的根 POM 每添加一个外部依赖都打上一个标签很麻烦,那你肯定没有遇到过为了排查依赖错误引起的问题,浪费一周的时间翻遍整个项目的情况。

你所有的 Maven 项目都应该包含你的根 POM,以及这些项目的所有版本信息。这样你会清除地了解到你们公司选择的每一个外部依赖的版本,以及所有正确的 Maven 插件。如果你要引入很多的外部依赖,它将会是这样子的:

<dependencies>
    <dependency>
        <groupId>org.third.party</groupId>
        <artifactId>some-artifact</artifactId>
    </dependency>
</dependencies>

如果你想使用内部依赖,它应该被每一个单独项目的 部分来管理。否则那将会很难保持根 POM 的版本号是正常的。

依赖检查

Java 最好的一方面就是拥有大量的第三方库可以做任何事。基本上每一个 API 或者工具包都有一个 Java SDK,可以很方便的用 Maven 引入。

并且这些第三方 Java 库本身依赖特定版本的其他的库。如果你引入足够多的库,你会发现有些库的版本是冲突的,像这样:

Foo library depends on Bar library v1.0
Widget library depends on Bar library v0.9

你的项目到底要引入哪一个版本呢?

如果你的项目依赖于不同版本的同一个库,使用 Maven 依赖趋同插件构建时将会报错。然后你有两个方案来解决这个冲突:

  1. 在你的 dependencyManagement 部分明确地支出你所使用的 Bar 的版本号
  2. 在 FOO 或者 Widget 中排除对 Bar 的依赖。

这两个方案到底选哪一个要看你面对的是什么情况:如果你想跟踪一个项目的版本,那么选择排除的方案是不错的。另一方面,如果你想明确地指出它,你可以选择一个版本,尽管你在需要更新其他依赖的时候也需要更新它。

IntelliJ内置的Maven

在使用IntelliJ内置的Maven工具来clean或者install的时候,出现各种各样的奇怪的异常时,不要着急。淡定的使用Terminal输入mvn clean install -U 来执行和运行代码。

Guava

Guava 是谷歌优秀的对 Java 标准库缺少的特性进行补充的扩展库。虽然这很难提炼总结出我有多喜欢这个库,但是我会尽力的。

Cache 让你可以用很简单的方法,实现把网络访问,磁盘访问,缓存函数或者其他任何你想要缓存的内容,缓存到内存当中。你仅仅只需要实现 CacheBuilder 类并且告诉 Guava 怎么样构建你的缓存,一切就搞定了!

Immutable 集合。它有许多如:ImmutableMapImmutableList,或者甚至 ImmutableSortedMultiSet 等不可变集合可以使用,如果你喜欢用这种风格的话。

我也喜欢用 Guava 的方式来写一些可变的集合:

// Instead of
final Map<String, Widget> map = new HashMap<>();

// You can use
final Map<String, Widget> map = Maps.newHashMap();

它还有一些静态类如 ListsMapsSets 等。使用起来它们显得更整洁,并且可读性更强。

如果你坚持使用 Java 6 或者 7 的话,你可以使用 Collections2 这个类,它有一些像 filter 和 transform 这样的方法。能够让你没有 Java 8 的 Stream 的支持也能写出流畅的代码。

Guava 也可以做一些很简单的事情,比如 Joiner 类可以用来用分隔符把字符串拼接起来,并且可以用忽略的方式来处理打断程序的数据。

Java Tuples

Java 令我比较烦恼的问题之一 Java 标准库中没有内置对元组的支持。幸运的是,Java tuples 项目解决了这个问题。

它使用用起来很简单,很棒:

Pair<String, Integer> func(String input) {
    // something...
    return Pair.with(stringResult, intResult);
}

Javaslang

Javaslang 是一个函数式编程库,它被设计用来弥补本应该出现在 Java 8 中但缺失的一些特性。它有这样的一些特点:

  • 一个全新函数式集合库
  • 紧密集成的元组功能
  • 模式匹配
  • 通过不可变性保证线程安全
  • 饥汉式和懒汉式的数据类型
  • 通过 Option 实现了 null 的安全性
  • 通过 Try 更好的实现异常处理

有一些 Java 库依赖于原始的 Java 集合类。它们通过以面向对象和被设计为可变的方式来保证和其他的类的兼容性。而 Javaslang 的集合的设计灵感来源于 Haskell, Clojure 和 Scala,是一个全新的飞跃。它们被设计为函数式风格并且遵循不可变性的设计风格。

像下面这样的代码就可以自动实现线程安全,并且不用 try-catch 语句处理异常:

// Success/Failure containing the result/exception
public static Try<User> getUser(int userId) {
    return Try.of(() -> DB.findUser(userId))
        .recover(x -> Match.of(x)
            .whenType(RemoteException.class).then(e -> ...)
            .whenType(SQLException.class).then(e -> ...));
}

// Thread-safe, reusable collections
public static List<String> sayByeBye() {
    return List.of("bye", "bye", "collect", "mania")
               .map(String::toUpperCase)
               .intersperse(" ");
}

Joda-Time

虽然Java 8 终于加了自己的新的 时间处理库 ,但不知什么原因,团队依旧建议使用Joda-Time 库。或许Joda-Time更简单直接吧。

SLF4J

有很多 Java 日志解决方案。我最喜欢的是 SLF4J,因为它拥有非常棒的可插拔性,同时能够和很多的日志框架想结合。有没有做过同时使用 java.util.logging,JCL,和 log4j 的奇葩项目?SLF4J 就是为你而生。

两页手册足够你可以开始入门使用 SLF4J 了。

MyBatis

代码检查

即使遵循着最佳实践的原则,即使是最好的开发者,也都会犯错误。这有很多工具,你可以使用它们验证你的代码从而检查代码是否有问题。下面是选出的最流行的一部分工具。很多这些工具都可以和流行的 IDE 如 Eclipse 或者 IntelliJ 集成,可以让你更快地发现代码中的错误。

  • Checkstyle:一个静态代码分析工具,它主要着力于保证你的代码符合代码标准。检查规则在一个 XML 文件中定义,你可以把它检入你的版本控制工具,和你的代码放在一起。
  • FindBugs:主要集中于发现你的代码中可能导致产生 bug 或者错误的部分。虽然作为独立的进程运行,但是对流行的 IDE 和构建工具的支持也很好。
  • PMD:和 FindBugs 很相似,PMD 着力于发现你代码中的错误和整理的你的代码。你可以把针对你的代码的检查规则控制在 XML 文件中,和你的代码放在一块儿提交。
  • SonarQube:和前面所述的工具不同,它是在本地运行的,SonarQube 启动一个服务器,你把你代码提交到这个服务器来进行分析。它提供了 web 界面,你可以看到你的代码的健康状况信息,如不好的做法,潜在的 bug,测试覆盖率百分比,和你写代码的技术水平

results matching ""

    No results matching ""