名称解释

  • javaSE:是Java的基础。包含了基础的类库。主要用于程序开发。
  • javaEE:扩展了javaSE,除了包含se的内容,还包含了web开发需要的一些基础包,例如selvlet等,他是一个抽象的规范,具体由应用服务器实现(如GlassFish、WildFly、WebLogic) 。主要用于web开发。
  • SSM/SSH:架构已经过时,不建议学习
  • Spring:是一个独立的框架,没有严格遵循"JavaEE规范",他替代并改进了JavaEE的许多部分,但是允许使用所有JavaEE技术。推荐学习

vscode设置

// java源码支持中文
"code-runner.executorMap": {
    "java": "cd $dir && javac -encoding UTF-8 $fileName && java -Dfile.encoding=UTF-8 $fileNameWithoutExt",
},

第一个Java程序

public Test Hello {
    public static void main(String[] args) {
        System.out.println("Hello, world!");
    }
}

基础

// 一个程序只有一个这种入口
public static void main(String[] args) {
}

// println是print line的缩写

// 变量
StringBuilder sb = new StringBuilder();
// 编译器会根据赋值语句自动推断出变量sb的类型是StringBuilder
var sb = new StringBuilder();
// 实际上会自动变成:
StringBuilder sb = new StringBuilder();

构造方法

由于构造方法是如此特殊,所以构造方法的名称就是类名。构造方法的参数没有限制,在方法内部,也可以编写任意语句。但是,和普通方法相比,构造方法没有返回值(也没有void),调用构造方法,必须用new操作符。类似python的init

// Person zaza = new Person("zaza", 18);
class Person {
    private String name;
    private int age;

    public Person(String name, int age) {
        this.name = name;
        this.age = age;
    }
}

多构造方法

可以定义多个构造方法,在通过new操作符调用的时候,编译器通过构造方法的参数数量、位置和类型自动区分

class Person {
    private String name;
    private int age;

    public Person(String name, int age) {
        this.name = name;
        this.age = age;
    }

    public Person(String name) {
        this.name = name;
        this.age = 12;
    }

    public Person() {
    }
}

方法重载

类似多个构造方法。方法重载的目的是,功能类似的方法使用同一名字,更容易记住,因此,调用起来更简单。泛型处理?

class Hello {
    public void hello() {
        System.out.println("Hello, world!");
    }

    public void hello(String name) {
        System.out.println("Hello, " + name + "!");
    }

    public void hello(String name, int age) {
        if (age < 18) {
            System.out.println("Hi, " + name + "!");
        } else {
            System.out.println("Hello, " + name + "!");
        }
    }
}

静态字段

类似go的*类型,属于指针类型数据,多实例共享此数据。

虽然实例可以访问静态字段,但是它们指向的其实都是Person class的静态字段。所以,所有实例共享一个静态字段。

// 定义静态字段number:
public static int number;

静态方法

因为静态方法属于class而不属于实例,因此,静态方法内部,无法访问this变量,也无法访问实例字段,它只能访问静态字段。

// 静态方法经常用于工具类。例如:
Arrays.sort()
Math.random()

遍历List

常见写法

import java.util.List;
public class Main {
    public static void main(String[] args) {
        List<String> list = List.of("apple", "pear", "banana");
        for (String s : list) {
            System.out.println(s);
        }
    }
}

List和Array转换

import java.util.List;
public class Main {
    public static void main(String[] args) {
        List<Integer> list = List.of(12, 34, 56);
        // toArray(T[]) 可以保证类型信息不丢失
        Number[] array = list.toArray(new Number[list.size()]);
        // 更简洁的方法:Integer[] array = list.toArray(Integer[]::new);
        for (Number n : array) {
            System.out.println(n);
        }
    }

初始化List对象并同时赋值

// java 8
private List<User> users = new ArrayList<User>() {{
    add(new User(1, "zaza@163.com", "123456", "Zaza"));
    add(new User(2, "yaya@163.com", "123456", "Yaya"));
    add(new User(3, "xiaozhuang@163.com", "123456", "Xiaozhuang"));
}};

// java 11
private List<User> users = new ArrayList<>(List.of( // users:
    new User(1, "bob@example.com", "password", "Bob"), // bob
    new User(2, "alice@example.com", "password", "Alice"), // alice
    new User(3, "tom@example.com", "password", "Tom"))); // tom

一个.java文件只能包含一个public类,但可以包含多个非public类。

classpath

在系统环境变量中设置classpath环境变量,不推荐;

在启动JVM时设置classpath变量,推荐。

更好的做法是,不要设置classpath!默认的当前目录.对于绝大多数情况都够用了。

java -classpath .;C:\work\project1\bin;C:\shared abc.xyz.Hello
# 简写
java -cp .;C:\work\project1\bin;C:\shared abc.xyz.Hello

现在我们假设classpath.;C:\work\project1\bin;C:\shared,当JVM在加载abc.xyz.Hello这个类时,会依次查找:

  • <当前目录>\abc\xyz\Hello.class
  • C:\work\project1\bin\abc\xyz\Hello.class
  • C:\shared\abc\xyz\Hello.class

不要把任何Java核心库添加到classpath中!JVM根本不依赖classpath加载核心库!

jar包

如果有很多.class文件,散落在各层目录中,肯定不便于管理。如果能把目录打一个包,变成一个文件,就方便多了。

jar包就是用来干这个事的,它可以把package组织的目录层级,以及各个目录下的所有文件(包括.class文件和其他文件)都打成一个jar文件,这样一来,无论是备份,还是发给客户,就简单多了。

jar包实际上就是一个zip格式的压缩文件,而jar包相当于目录。如果我们要执行一个jar包的class,就可以把jar包放到classpath中:

java -cp ./hello.jar abc.xyz.Hello

java包运行原理

jar包还可以包含一个特殊的/META-INF/MANIFEST.MF文件,MANIFEST.MF是纯文本,可以指定Main-Class和其它信息。JVM会自动读取这个MANIFEST.MF文件,如果存在Main-Class,我们就不必在命令行指定启动的类名,而是用更方便的命令:

java -jar hello.jar

JavaBean

如果读写方法符合以下这种命名规范,那么这种class被称为JavaBean

// 读方法:
public Type getXyz()
// 写方法:
public void setXyz(Type value)

vscode插件

  • Extension Pack for JavaPreview
  • Doxygen Documentation Generator
  • Todo Tree

jar包

方案一

# javac 变成成二进制class文件
# -d 指定生成class的根目录
# -verbose 打印模式
# -classpath 指定依赖包(需要完整路径) linux为冒号,windows为分号
javac -classpath /root/javaTest/lib/amqp-client-5.7.1.jar:/root/javaTest/lib/slf4j-api-1.7.26.jar:/root/javaTest/lib/slf4j-simple-1.7.26.jar -d ../bin/ -verbose com/zaza/ops/main/App.java ./com/zaza/ops/person/Student.java

# 打包(e参数直接指定入库main函数)
jar -cvfe hello.jar com.zaza.main.App -C bin/ .

# 查看class加载信息(-verbose:class)
# java -verbose:class -cp /root/javaTest/lib/amqp-client-5.7.1.jar -jar hello.jar
java -verbose:class -jar hello.jar

# 运行指定依赖的jar包(注意发布的时候需要将lib目录一同发布)
java -Xbootclasspath/a:lib/amqp-client-5.7.1.jar:lib/slf4j-api-1.7.26.jar:lib/slf4j-simple-1.7.26.jar -jar hello.jar

方案二(推荐)

不用包管理工具的最佳实践

# javac 变成成二进制class文件
# -d 指定生成class的根目录
# -verbose 打印 class 加载
# -classpath 指定依赖包(需要完整路径) linux为冒号,windows为分号
# 需要指定所有java源码依赖文件,否则报错
cd src && mkdir -pv ../bin/
# javac -verbose -classpath /root/javaTest/lib/amqp-client-5.7.1.jar:/root/javaTest/lib/slf4j-api-1.7.26.jar:/root/javaTest/lib/slf4j-simple-1.7.26.jar -d ../bin/ com/zaza/ops/*/*.java
javac -classpath /root/javaTest/lib/amqp-client-5.7.1.jar:/root/javaTest/lib/slf4j-api-1.7.26.jar:/root/javaTest/lib/slf4j-simple-1.7.26.jar -d ../bin/ com/zaza/ops/*/*.java

# 打包
# 根据需求创建入口函数和依赖包信息
# 其它信息会自动创建,如:Manifest-Version、Created-By
echo 'Main-Class: com.zaza.main.App' > Manifest.txt
echo 'Class-Path: lib/amqp-client-5.7.1.jar lib/slf4j-api-1.7.26.jar lib/slf4j-simple-1.7.26.jar' >> Manifest.txt
# m指定Manifest文件
jar -cvfm hello.jar Manifest.txt -C bin/ .

# 查看class加载信息(-verbose:class)
java -verbose:class -jar hello.jar

# 直接运行
java -jar hello.jar

# 添加参数运行
# java -Dlog4j2.formatMsgNoLookups=true -Xms512m -Xmx512m -jar hello.jar --logging.config=./log4j2-spring.xml

方案三(推荐)

建议使用 Maven 帮助我们管理构建(可以方便的打JAR包)

Maven

jar包构建

安装

D:\Program Files\maven
# 新建环境变量
# MAVEN_HOME,赋值 D:\Program Files\maven
# 编辑环境变量Path,追加
# %MAVEN_HOME%\bin

镜像配置

~/.m2/settings.xml

依赖包保存目录:~/.m2/repository

<settings>
    <mirrors>
        <mirror>
            <id>aliyun</id>
            <name>aliyun</name>
            <mirrorOf>central</mirrorOf>
            <!-- 国内推荐阿里云的Maven镜像 -->
            <url>https://maven.aliyun.com/repository/central</url>
        </mirror>
    </mirrors>
</settings>

命名规则

dubbo/pom.xml拥有父ID以及自己是下面子模块的父ID,很不错

hadoop/pom.xml同上

<groupId>com.zaxxer</groupId>
<artifactId>HikariCP</artifactId>

<groupId>com.qcloud</groupId>
<artifactId>cos_migrate_tool</artifactId> <!-- 不是很标准? -->

<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>

<!-- 拥有父ID的,druid是apache下面的一个子项目 -->
<parent>
    <groupId>org.apache</groupId>
    <artifactId>apache</artifactId>
    <version>21</version>
</parent>

<groupId>org.apache.druid</groupId>
<artifactId>druid</artifactId>
<!-- 拥有父ID的,druid是apache下面的一个子项目 -->

<groupId>com.rabbitmq</groupId>
<artifactId>amqp-client</artifactId>

<!-- 带版本信息? -->
<groupId>org.apache.spark</groupId>
<artifactId>spark-parent_2.12</artifactId>

创建项目

Maven Quickstart Archetype-实例指定了版本

建议使用命令:mvn archetype:generate -DarchetypeVersion=“1.4”,这样会使用最新版本的配置创建pom.xml

# 需要指定参数:-DarchetypeVersion="1.4",这样才会显式的生成 build 配置信息
# 否则生成的配置是老版本的:project created from Old (1.x) Archetype
# 优势:这样可以动态的调整编译插件的版本:maven-xxx-plugin

# 交互式创建:mvn archetype:generate -DarchetypeVersion="1.4"
# archetype建议使用默认版本(内容最精简,后续一步一步的配置,熟悉后生成自己的pom.xml即可)
mvn archetype:generate -DarchetypeVersion="1.4" -DarchetypeArtifactId="maven-archetype-quickstart" -DinteractiveMode="false" -DgroupId="com.zaza" -DartifactId="xstack-agent"

# jdk修改成1.8
    <maven.compiler.source>1.7</maven.compiler.source>
    <maven.compiler.target>1.7</maven.compiler.target>
# 改成
    <maven.compiler.source>1.8</maven.compiler.source>
    <maven.compiler.target>1.8</maven.compiler.target>

# vscode 创建(建议使用此方法创建):
# ctrl+shift+p 输入:>create java --> Maven --> 选择:maven-archetype-quickstart
# 这里pom.xml多了build编译的插件信息:maven-xxx-plugin  (影响参数:-DarchetypeVersion="1.4")

# vscode 实际创建命令:
# mvn org.apache.maven.plugins:maven-archetype-plugin:3.1.2:generate -DarchetypeArtifactId="maven-archetype-quickstart" -DarchetypeGroupId="org.apache.maven.archetypes" -DarchetypeVersion="1.4" -DgroupId="com.example" -DartifactId="demo"

插件搜索

还可以查看每个插件的pom文件内容

“Latest Version” “Updated” 之间有一个版本数量点击进去,再点击对应的"Version"后,左上角就有:Apache Maven的dependency相关内容

Maven Central Repository Search

打包插件

打包:我们日常使用的以maven-assembly-plugin为最多,因为大数据项目中往往有很多shell脚本、SQL脚本、.properties及.xml配置项等,采用assembly插件可以让输出的结构清晰而标准化。

参考:https://github.com/tencentyun/cos_migrate_tool_v5/blob/master/pom.xml

<!-- 依赖信息:dependencies下不用配置 -->
<!-- 编译配置,注意不要配置到:build->pluginManagement下面了 -->
  <build>
    <plugins>
      <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-assembly-plugin</artifactId>
        <version>3.3.0</version>
        <executions>
          <execution>
            <id>make-assembly</id>
            <phase>package</phase>
            <goals>
              <goal>single</goal>
            </goals>
          </execution>
        </executions>
        <configuration>
          <!-- <descriptor>src/main/assembly/assembly.xml</descriptor> -->
          <finalName>${project.name}</finalName>
          <descriptorRefs>
            <!-- 将依赖的jar打入目标jar包,这样可以实现jar单包而不需要拷贝lib包 -->
            <descriptorRef>jar-with-dependencies</descriptorRef>
          </descriptorRefs>
          <archive>
            <manifest>
              <mainClass>com.zaza.App</mainClass>
            </manifest>
          </archive>
        </configuration>
      </plugin>
    </plugins>
  </build>

log4j2插件

安装

使用最新版本,log4j出了远程执行的严重漏洞

Log4j – Maven, Ivy, Gradle, and SBT Artifacts (apache.org)

最佳实践

在开发阶段,始终使用Commons Logging接口来写入日志,并且开发阶段无需引入Log4j。如果需要把日志写入文件, 只需要把正确的配置文件和Log4j相关的jar包放入classpath,就可以自动把日志切换成使用Log4j写入,无需修改任何代码。

  • log4j-jcl:commons-logging到log4j2的桥梁 (Commons Logging是一个第三方提供的库),默认common-logging会自动检查是否使用log4j
  • JDK自带的Logging其实是一个鸡肋,竟然没有debug的日志级别,差评。。。
<dependencies>
  <dependency>
    <groupId>commons-logging</groupId>
    <artifactId>commons-logging</artifactId>
    <version>1.2</version>
  </dependency>
  <dependency>
    <groupId>org.apache.logging.log4j</groupId>
    <artifactId>log4j-api</artifactId>
    <version>2.17.1</version>
  </dependency>
  <dependency>
    <groupId>org.apache.logging.log4j</groupId>
    <artifactId>log4j-core</artifactId>
    <version>2.17.1</version>
  </dependency>
  <!-- 这个是依赖包:SLF4J: Failed to load class "org.slf4j.impl.StaticLoggerBinder". -->
  <dependency>
    <groupId>org.apache.logging.log4j</groupId>
    <artifactId>log4j-slf4j-impl</artifactId>
    <version>2.17.1</version>
  </dependency>
  <!-- commons-logging到log4j2的桥梁 -->  
  <dependency>
    <groupId>org.apache.logging.log4j</groupId>
    <artifactId>log4j-jcl</artifactId>
    <version>2.17.1</version>
  </dependency>    
</dependencies>

properties配置

Log4j – Configuring Log4j 2 (apache.org)

log4j 1.x 默认路径:src/main/resources/log4j.properties

log4j 2.x 默认路径:src/main/resources/log4j2.properties

status = error
dest = err
name = PropertiesConfig
 
property.filename = logs/rollingtest.log
 
filter.threshold.type = ThresholdFilter
filter.threshold.level = debug
 
appender.console.type = Console
appender.console.name = STDOUT
appender.console.layout.type = PatternLayout
appender.console.layout.pattern = %d [%-5p] [%t:%r] [%c:%x] [%F:%L] %m%n
appender.console.filter.threshold.type = ThresholdFilter
appender.console.filter.threshold.level = debug

appender.rolling.type = RollingFile
appender.rolling.name = RollingFile
appender.rolling.fileName = ${filename}
# appender.rolling.filePattern = target/rolling2/test1-%d{MM-dd-yy-HH-mm-ss}-%i.log.gz
appender.rolling.filePattern = logs/$${date:yyyy-MM}/app-%d{MM-dd-yyyy}-%i.log.gz
appender.rolling.layout.type = PatternLayout
appender.rolling.layout.charset = UTF-8

# appender.rolling.layout.pattern = %d %p %C{1.} [%t] %m%n ==> # 2022-01-11 04:10:43,304 ERROR c.e.App [main] 严重信息
# 2022-01-11 04:10:58 [INFO ] [main:1176] [com.example.App:[]] [App.java:16] 一般信息
appender.rolling.layout.pattern = %d [%-5p] [%t:%r] [%c:%x] [%F:%L] %m%n
appender.rolling.policies.type = Policies
appender.rolling.policies.time.type = TimeBasedTriggeringPolicy
appender.rolling.policies.time.interval = 2
appender.rolling.policies.time.modulate = true
appender.rolling.policies.size.type = SizeBasedTriggeringPolicy
appender.rolling.policies.size.size = 100MB
appender.rolling.strategy.type = DefaultRolloverStrategy
appender.rolling.strategy.max = 10
 
logger.rolling.name = com.example.App
logger.rolling.level = debug
logger.rolling.additivity = false
logger.rolling.appenderRef.rolling.ref = RollingFile

rootLogger.level = info
rootLogger.appenderRef.stdout.ref = STDOUT

log4j2示例代码

package com.example;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

/**
 * Hello world!
 *
 */
public class App {
    private static final Log log = LogFactory.getLog(App.class);

    public static void main(String[] args) {
        log.info("一般信息");
        log.warn("警示信息");
        log.error("严重信息");
    }
}

日志格式

# Log4j2 的日志格式
17:24:11.194 [main] ERROR com.zaza.app.App - 严重信息

# commons-logging 的日志格式
Jan 11, 2022 4:17:40 AM com.example.App main
SEVERE: 严重信息

SLF4J+Logback插件

安装

<!-- https://mvnrepository.com/artifact/org.slf4j/slf4j-api -->
<dependency>
  <groupId>org.slf4j</groupId>
  <artifactId>slf4j-api</artifactId>
  <version>1.7.32</version>
</dependency>
<!-- https://mvnrepository.com/artifact/ch.qos.logback/logback-classic -->
<dependency>
  <groupId>ch.qos.logback</groupId>
  <artifactId>logback-classic</artifactId>
  <version>1.2.10</version>
  <!-- <scope>test</scope> 注意注释这行-->
</dependency>

logback配置

保存路径:src/main/resources/logback.xml

log4j转换成logback配置

运行使用自定义配置:java -Dlogback.configurationFile =/path/to/config.xml

故事篇:终于给老婆讲明白什么是logback了

SpringBoot 日志 logback-spring.xml 配置技巧:使用 springProfile 分环境打印日志到不同目标-很不错

<?xml version="1.0" encoding="UTF-8"?>
<configuration>
    <!-- property :用来定义变量值 -->
    <property name="PATTERN" value="[%d{yyyy-MM-dd HH:mm:ss SSS}] [%X] [%-5p] %logger{0}.%M\\(%L\\) - %msg%n" />
    <property name="LOG_HOME" value="logs" />

    <!-- 输出到控制台 -->
    <appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
        <encoder>
            <pattern>${PATTERN}</pattern>
        </encoder>
    </appender>

    <!-- 输出到文件 -->
    <appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
            <fileNamePattern>${LOG_HOME}/log-%d{yyyy-MM-dd}.%i.log</fileNamePattern>
            <!-- 日志保留天数 -->
            <MaxHistory>100</MaxHistory>
            <!-- 日志文件最大值 -->
            <maxFileSize>100MB</maxFileSize>
        </rollingPolicy>
        <encoder>
          <charset>UTF-8</charset>
          <pattern>${PATTERN}</pattern>
        </encoder>
    </appender>

    <logger name="com.example.App"/>

    <root level="trace">
      <appender-ref ref="CONSOLE" />
      <appender-ref ref="FILE" />
    </root>
</configuration>

示例代码

package com.example;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * Hello world!
 *
 */
public class App {
    final static Logger logger = LoggerFactory.getLogger(App.class);

    public static void main(String[] args) {
        logger.trace("logback 成功了");
        logger.debug("logback 成功了");
        logger.info("logback 成功了");
        logger.warn("logback {}", "成功了");
        logger.error("logback 成功了");
    }
}

mysql插件

<dependency>
  <groupId>mysql</groupId>
  <artifactId>mysql-connector-java</artifactId>
  <version>8.0.27</version>
</dependency>

rabbitmq插件

根据需求添加

<dependency>
  <groupId>com.rabbitmq</groupId>
  <artifactId>amqp-client</artifactId>
  <version>5.14.0</version>
</dependency>

打jar包

# 代码规范校验
mvn checkstyle::check

# 关闭校验:<skip>${skipTests}</skip> 改成:<skip>true</skip>

# 打包
# mvn assembly:single
mvn package

Nexus私有仓库

Nexus下载地址

常见地址

# 中央仓库客户端请求的时候,自动缓存
http://localhost:8081/repository/maven-central/
# 私有快照
http://localhost:8081/repository/maven-snapshots/
# 私有发布版本
http://localhost:8081/repository/maven-releases/

mvn配置

pom.xml

<project>
......
<distributionManagement>
    <repository>
      <id>zaza</id>
      <name>Releases</name>
      <url>http://localhost:8081/repository/maven-releases</url>
    </repository>
    <snapshotRepository>
      <id>zaza</id>
      <name>Snapshot</name>
      <url>http://localhost:8081/repository/maven-snapshots</url>
    </snapshotRepository>
  </distributionManagement>
</project>

~/.m2/settings.xml

<settings>
    <mirrors>
        <mirror>
            <id>zaza</id>
            <name>zaza</name>
            <mirrorOf>central</mirrorOf>
            <!-- 国内推荐阿里云的Maven镜像 -->
            <!-- <url>https://maven.aliyun.com/repository/central</url> -->
            <url>http://localhost:8081/repository/maven-central/</url>
        </mirror>
    </mirrors>
    <servers>
      <server>
        <id>zaza</id>
        <username>admin</username>
        <password>71382b73-942a-4328-99d2-0d83155fa83b</password>
      </server>
  </servers>  
</settings>

发布

mvn deploy
# 快照版本 <version>1.0-SNAPSHOT</version>
# 正式版本 <version>1.0.0</version>

数据库连接池

JBoss 过时了

Druid或者HikariCP,建议Druid ? HikariCP?

spring

Spring Initializr-源码包生成器

Vue + Spring Boot 项目实战(一)

结构

vscode扩展Spring Boot Extension Pack指定java版本

  1. Open Visual Studio Code
  2. CRTL + , to open the settings
  3. Search spring-boot.ls.java.home
  4. Select ‘Edit in settings.json’
  5. Your Java 11 installation → “spring-boot.ls.java.home”: “C:\Program Files\Java\jdk-17.0.2”
  6. Save and restart VS Code
  7. Prereq: Have a Java 11 installation

参考